x-pack/platform/packages/shared/ai-infra/anonymization-ui/README.md
Shared UI package for anonymization profile configuration in Stack Management and embedded solution surfaces.
This package provides reusable anonymization UI components, hooks, and shared contracts.
For the section-level API (AnonymizationProfilesSection), hosts provide:
fetch: HttpSetup['fetch']spaceId: active Kibana space identifiercanShow: whether the section should be visiblecanManage: whether create/edit/delete actions are enabledlistTrustedNerModelsfetchPreviewDocumentonCreateSuccess / onUpdateSuccess / onDeleteSuccessonCreateConflict / onOpenConflictErrorHosts are responsible for deriving these values from their capabilities/services context.
When listTrustedNerModels is provided:
0 models: NER rule creation/editing is disabled and a warning is shown.1 model: the model is auto-applied and rendered read-only in the NER panel.>1 models: users can select a model from the dropdown (current behavior).Package-owned:
manage, readOnly, hidden) from host context.src/anonymization_profiles_section: section-level orchestration and list/delete flows.src/profile_form: controlled form composition (ProfileFormProvider, ProfileFormContent, ProfileFormFooter) and form-specific hooks.src/profile_form/panels/*_panel: panel UI implementations and colocated tests
(for example field_rules_panel, regex_rules_panel, ner_rules_panel, preview_panel).src/common/services: shared API clients and React Query hooks used by section and form surfaces.src/index.ts: package public surface; section hooks are exported from anonymization_profiles_section and form primitives from profile_form.Host-owned:
The package contains the active anonymization profile UI implementation used by GenAI Settings.
Import from @kbn/anonymization-ui:
AnonymizationProfilesSectionuseProfilesListViewuseDeleteProfileFlowProfileFormProviderProfileFormContentProfileFormFooterProfileFormProfileFlyoutcreateAnonymizationProfilesClientcreateAnonymizationReplacementsClientuseResolveAnonymizedValuesAvoid deep imports from src/*; use the package public surface.
AnonymizationProfilesSection for the full profiles experience with host-provided capabilities/services wiring.ProfileFormProvider + ProfileFormContent + ProfileFormFooter when you need full control over container and orchestration.<AnonymizationProfilesSection
fetch={services.http.fetch}
spaceId={activeSpaceId}
canShow={canShowAnonymization}
canManage={canManageAnonymization}
listTrustedNerModels={listTrustedNerModels}
fetchPreviewDocument={fetchPreviewDocument}
onCreateSuccess={onCreateSuccess}
onUpdateSuccess={onUpdateSuccess}
onDeleteSuccess={onDeleteSuccess}
onCreateConflict={onCreateConflict}
onOpenConflictError={onOpenConflictError}
/>
The profile editing/creation flow is available as container-agnostic building blocks:
ProfileFormProviderProfileFormContentProfileFormFooterProfileForm (inline convenience wrapper)The form remains fully controlled. ProfileFormProvider takes the same controlled props as ProfileFlyout (isEdit, values, validation errors, callbacks, fetch, onCancel, onSubmit).
Shared surfaces that need to render de-anonymized values can support both paths below without breaking existing integrations:
replacementsId and let the UI resolve
tokens from /internal/inference/anonymization/replacements/{id}.inlineDeanonymizations
(entity.mask -> entity.value) when replacement IDs are not available in the host payload.When both are provided, ID-based replacements take precedence and inline metadata remains a non-breaking compatibility path.
replacementsId comes fromreplacementsId is produced by the Inference chat anonymization pipeline and returned in response
metadata (metadata.anonymization.replacementsId). It is not generated by @kbn/anonymization-ui.
Typical host flow:
metadata.anonymization.replacementsId from chat response/events.replacementsId into shared anonymization UI surfaces that need de-anonymized rendering./internal/inference/anonymization/replacements/{id}.If no replacementsId is available (for example legacy payloads or host-specific transports),
hosts can pass inlineDeanonymizations as the non-breaking fallback path.
<ProfileFormProvider {...controlledProps}>
<EuiFlyout onClose={controlledProps.onCancel} ownFocus size="m">
<EuiFlyoutHeader hasBorder></EuiFlyoutHeader>
<EuiFlyoutBody>
<ProfileFormContent />
</EuiFlyoutBody>
<EuiFlyoutFooter>
<ProfileFormFooter />
</EuiFlyoutFooter>
</EuiFlyout>
</ProfileFormProvider>
<ProfileFormProvider {...controlledProps}>
<EuiModal onClose={controlledProps.onCancel}>
<EuiModalHeader></EuiModalHeader>
<EuiModalBody>
<ProfileFormContent />
</EuiModalBody>
<EuiModalFooter>
<ProfileFormFooter />
</EuiModalFooter>
</EuiModal>
</ProfileFormProvider>
<ProfileFormProvider {...controlledProps}>
<EuiPageTemplate.Section>
<ProfileFormContent />
<ProfileFormFooter />
</EuiPageTemplate.Section>
</ProfileFormProvider>
ProfilesTableUse useProfileEditor to load an existing profile by id and drive controlled form props:
const { form, isLoadingProfile, loadError } = useProfileEditor({
client,
context: { spaceId },
profileId,
});
if (isLoadingProfile) {
return <EuiLoadingSpinner />;
}
if (loadError) {
return <EuiCallOut color="danger" title={loadError.message} />;
}
return (
<ProfileFormProvider
isEdit={form.isEdit}
isManageMode={isManageMode}
name={form.values.name}
description={form.values.description}
targetType={form.values.targetType}
targetId={form.values.targetId}
fieldRules={form.values.fieldRules}
regexRules={form.values.regexRules}
nerRules={form.values.nerRules}
nameError={form.validationErrors.name}
targetIdError={form.validationErrors.targetId}
fieldRulesError={form.validationErrors.fieldRules}
regexRulesError={form.validationErrors.regexRules}
nerRulesError={form.validationErrors.nerRules}
submitError={form.submitError}
isSubmitting={form.isSubmitting}
onNameChange={form.setName}
onDescriptionChange={form.setDescription}
onTargetTypeChange={form.setTargetType}
onTargetIdChange={form.setTargetId}
onFieldRulesChange={form.setFieldRules}
onRegexRulesChange={form.setRegexRules}
onNerRulesChange={form.setNerRules}
fetch={fetch}
onCancel={onCancel}
onSubmit={async () => {
await form.submit();
}}
>
<ProfileFormContent />
<ProfileFormFooter />
</ProfileFormProvider>
);