Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7c492b4
package-lock committing for pull
wadesdev Mar 18, 2026
fa7ce0f
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev Mar 18, 2026
62d829f
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev Mar 23, 2026
300333f
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev Apr 2, 2026
d80fd79
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev Apr 5, 2026
c8ca49b
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev Apr 10, 2026
141d002
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev Apr 16, 2026
19b4c1a
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev Apr 20, 2026
ee88f35
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev Apr 23, 2026
b0c4a15
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev Apr 23, 2026
7ff0c69
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev Apr 24, 2026
3940ab0
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev Apr 25, 2026
25c50b2
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev Apr 29, 2026
af89afa
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev Apr 29, 2026
7e6bdfc
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev May 4, 2026
3305b7f
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev May 7, 2026
bd3042e
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev May 12, 2026
df53512
Merge branch 'main' of https://github.com/ethyca/fides
wadesdev May 14, 2026
c74b8c1
feat(ENG-3832): render multi-select for taxonomy custom fields with […
wadesdev May 15, 2026
4b26162
changelog: add entry for ENG-3832 multi-select taxonomy custom fields…
wadesdev May 15, 2026
f4b7891
feat(ENG-3832): add selection mode UI for taxonomy custom field defin…
wadesdev May 15, 2026
4b6f69b
fix(ENG-3832): prettier format destructuring in useCreateOrUpdateCust…
wadesdev May 15, 2026
6a1d902
fix(ENG-3832): strip [] suffix before taxonomy name lookup in custom …
wadesdev May 15, 2026
631fd2b
fix(ENG-3832): address code review feedback on frontend custom field …
wadesdev May 15, 2026
4dade3c
fix(ENG-3832): address second round code review feedback
wadesdev May 15, 2026
729fbbb
Fix secret list formatting
wadesdev May 18, 2026
a2113c9
Merge branch 'main' into ENG-3832/multi-select-taxonomy-custom-fields
galvana May 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog/8197-multi-select-taxonomy-custom-fields.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type: Added
description: Support multi-select taxonomy custom fields via the "taxonomy_key[]" field_type convention — frontend now renders a multi-select dropdown for [] fields on taxonomy detail pages
pr: 8197
labels: []
3 changes: 2 additions & 1 deletion clients/admin-ui/src/features/common/custom-fields/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ export const useCustomFields = ({
activeCustomFieldDefinition.forEach((value) => {
const customField = definitionIdToCustomField.get(value.id || "");
if (customField) {
if (!!value.allow_list_id && value.field_type === "string[]") {
// Preserve array values for any multi-value field_type (legacy "string[]" or taxonomy "risk[]")
if (value.field_type.endsWith("[]")) {
Comment thread
gilluminate marked this conversation as resolved.
values[customField.custom_field_definition_id] = customField.value;
} else {
values[customField.custom_field_definition_id] =
Expand Down
47 changes: 41 additions & 6 deletions clients/admin-ui/src/features/custom-fields/CustomFieldForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ const CustomFieldForm = ({
const [form] = Form.useForm<CustomFieldsFormValues>();
const valueType = Form.useWatch("value_type", form);
const selectedTemplate = Form.useWatch("template", form);
const isTaxonomyTemplate =
!!selectedTemplate && selectedTemplate !== CUSTOM_TEMPLATE_VALUE;
const router = useRouter();
const { resource_type: queryResourceType } = router.query;

Expand Down Expand Up @@ -185,17 +187,26 @@ const CustomFieldForm = ({
return undefined;
}
const fieldType = getCustomFieldType(field);
const template =
const isCustomTemplate =
fieldType === FieldTypes.OPEN_TEXT ||
fieldType === FieldTypes.SINGLE_SELECT ||
fieldType === FieldTypes.MULTIPLE_SELECT
? CUSTOM_TEMPLATE_VALUE
: undefined;
fieldType === FieldTypes.MULTIPLE_SELECT;

// Detect taxonomy multi-select: field_type ends with "[]" and is not a standard type
const isTaxonomyMulti =
!isCustomTemplate && field.field_type.endsWith("[]");
const taxonomyBaseKey = isTaxonomyMulti
? field.field_type.slice(0, -2)
: field.field_type;

const template = isCustomTemplate ? CUSTOM_TEMPLATE_VALUE : taxonomyBaseKey;

return {
...field,
value_type: field.field_type,
value_type: taxonomyBaseKey,
template,
field_type: fieldType,
selection_mode: isTaxonomyMulti ? "multiple" : "single",
resource_type: parseResourceType(field.resource_type),
options: allowList?.allowed_values ?? [],
};
Expand All @@ -205,10 +216,11 @@ const CustomFieldForm = ({

const initialValues = queryResourceType
? {
selection_mode: "single" as const,
...defaultInitialValues,
resource_type: `taxonomy:${queryResourceType}`,
}
: defaultInitialValues;
: { selection_mode: "single" as const, ...defaultInitialValues };

if (isLoading || isAllowListLoading || isLocationsLoading) {
return <SkeletonCustomFieldForm />;
Expand Down Expand Up @@ -256,8 +268,10 @@ const CustomFieldForm = ({
onChange={(value) => {
if (value === CUSTOM_TEMPLATE_VALUE) {
form.setFieldValue("value_type", undefined);
form.setFieldValue("selection_mode", undefined);
} else {
form.setFieldValue("value_type", value);
form.setFieldValue("selection_mode", "single");
}
}}
data-testid="select-template"
Expand All @@ -277,6 +291,27 @@ const CustomFieldForm = ({
/>
</Form.Item>

{isTaxonomyTemplate && (
<Form.Item
Comment thread
gilluminate marked this conversation as resolved.
label="Selection mode"
name="selection_mode"
rules={[
{ required: true, message: "Please select a selection mode" },
]}
>
<Select
options={[
{ label: "Single select", value: "single" },
{ label: "Multiple select", value: "multiple" },
]}
data-testid="select-selection-mode"
getPopupContainer={(trigger) =>
trigger.parentElement || document.body
}
/>
</Form.Item>
)}

{selectedTemplate === CUSTOM_TEMPLATE_VALUE && (
<>
<Form.Item
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface CustomFieldsFormValues extends Omit<
field_type?: string;
template?: string;
value_type: string;
selection_mode?: "single" | "multiple";
}

export const CUSTOM_TEMPLATE_VALUE = "create-custom-values";
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ const useCreateOrUpdateCustomField = () => {
values.resource_type as string,
);
if (values.field_type === FieldTypes.OPEN_TEXT) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { selection_mode: selectionMode, ...rest } = values;
const payload = {
...values,
...rest,
field_type: LegacyAllowedTypes.STRING,
id: initialField ? initialField.id : undefined,
resource_type: normalizedResourceType,
Expand All @@ -67,7 +69,8 @@ const useCreateOrUpdateCustomField = () => {
values.field_type === FieldTypes.MULTIPLE_SELECT
) {
if (!initialField) {
const { options, ...rest } = values;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { options, selection_mode: selectionMode, ...rest } = values;
// first create the allow list
const allowListPayload = {
name: generateNewAllowListName(),
Expand All @@ -92,7 +95,8 @@ const useCreateOrUpdateCustomField = () => {
}
// update the allow list if it's changed
let allowListResult: RTKResult | undefined;
const { options, ...rest } = values;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { options, selection_mode: selectionMode, ...rest } = values;
if (!isEqual(initialAllowList?.allowed_values, options)) {
const allowListPayload = {
...initialAllowList,
Expand Down Expand Up @@ -123,11 +127,15 @@ const useCreateOrUpdateCustomField = () => {
return result;
}
// field type is a taxonomy
const { value_type: valueType, ...rest } = values;
const {
value_type: valueType,
selection_mode: selectionMode,
...rest
} = values;
const payload = {
...rest,
id: initialField ? initialField.id : undefined,
field_type: valueType,
field_type: selectionMode === "multiple" ? `${valueType}[]` : valueType,
resource_type: normalizedResourceType,
};
let result: RTKResult | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,15 @@ const useCustomFieldsTable = () => {
dataIndex: "field_type",
key: "field_type",
render: (_, record) => {
const baseFieldType = record.field_type.endsWith("[]")
? record.field_type.slice(0, -2)
: record.field_type;
const customTaxonomy = customTaxonomies?.find(
(taxonomy) => taxonomy.fides_key === record.field_type,
(taxonomy) => taxonomy.fides_key === baseFieldType,
);
const label = customTaxonomy?.name ?? getCustomFieldTypeLabel(record);
const label =
customTaxonomy?.name ??
getCustomFieldTypeLabel({ ...record, field_type: baseFieldType });
return <TagCell value={label} />;
Comment thread
gilluminate marked this conversation as resolved.
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ const TaxonomyCustomFieldsForm = ({
} = customFieldDefinition;

if (!allowListId) {
const isMultiSelectTaxonomy = fieldType.endsWith("[]");
const taxonomyKey = isMultiSelectTaxonomy
? fieldType.slice(0, -2)
: fieldType;

return (
<Form.Item
key={definitionId}
Expand All @@ -70,7 +75,8 @@ const TaxonomyCustomFieldsForm = ({
<Input />
) : (
<CustomTaxonomySelect
taxonomyKey={fieldType}
taxonomyKey={taxonomyKey}
mode={isMultiSelectTaxonomy ? "multiple" : undefined}
defaultValue={customFields.customFieldValues[id]}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clients/admin-ui/src/features/taxonomy/components/TaxonomyCustomFieldsForm.tsx:80

defaultValue is pre-existing here, but it's worth flagging with the new multi-select path: in a controlled Ant Design Form, defaultValue on an inner component is generally ignored in favour of the Form's field value (set via name={id} on the parent Form.Item). The value from customFields.customFieldValues should already be injected through Form initialValues on the wrapping <Form> at line 35. Passing it again as defaultValue is redundant and won't cause a bug here, but it can be confusing and mask issues if the form re-initialises. Consider removing defaultValue and relying solely on the Form's controlled state.

/>
)}
Expand Down
Loading