From 86f3c3833600971599bf6282789718e68d102f62 Mon Sep 17 00:00:00 2001 From: Greg Wong Date: Wed, 3 Sep 2025 15:30:11 -0400 Subject: [PATCH] fix(metadata-view): Enable sorting dropdown --- .../content-explorer/ContentExplorer.tsx | 7 +- .../MetadataViewContainer.tsx | 43 ++++++--- .../__tests__/ContentExplorer.test.tsx | 9 +- .../__tests__/MetadataViewContainer.test.tsx | 96 +++++++++++++++---- 4 files changed, 114 insertions(+), 41 deletions(-) diff --git a/src/elements/content-explorer/ContentExplorer.tsx b/src/elements/content-explorer/ContentExplorer.tsx index 6e0143bf36..424e400327 100644 --- a/src/elements/content-explorer/ContentExplorer.tsx +++ b/src/elements/content-explorer/ContentExplorer.tsx @@ -144,7 +144,7 @@ export interface ContentExplorerProps { metadataQuery?: MetadataQuery; metadataViewProps?: Omit< MetadataViewContainerProps, - 'hasError' | 'currentCollection' | 'metadataTemplate' | 'onMetadataFilter' + 'hasError' | 'currentCollection' | 'metadataTemplate' | 'selectedKeys' >; onCreate?: (item: BoxItem) => void; onDelete?: (item: BoxItem) => void; @@ -1612,7 +1612,10 @@ class ContentExplorer extends Component { return maxWidthColumns; }; - getMetadataViewProps = (): ContentExplorerProps['metadataViewProps'] => { + getMetadataViewProps = (): Omit< + MetadataViewContainerProps, + 'hasError' | 'currentCollection' | 'metadataTemplate' + > => { const { metadataViewProps } = this.props; const { onSelectionChange } = metadataViewProps ?? {}; const { selectedItemIds } = this.state; diff --git a/src/elements/content-explorer/MetadataViewContainer.tsx b/src/elements/content-explorer/MetadataViewContainer.tsx index 2c1e31e2e6..d059324cb1 100644 --- a/src/elements/content-explorer/MetadataViewContainer.tsx +++ b/src/elements/content-explorer/MetadataViewContainer.tsx @@ -155,6 +155,17 @@ const MetadataViewContainer = ({ const clonedTemplate = cloneDeep(metadataTemplate); let fields = clonedTemplate?.fields || []; + // Filter fields to only include those that have corresponding columns + const columnIds = newColumns.map(col => col.id); + fields = fields.filter((field: MetadataTemplateField) => { + // For metadata fields, check if the column ID matches the field key + // Column IDs for metadata fields are typically in format: metadata.template.fieldKey + return columnIds.some(columnId => { + const trimmedColumnId = trimMetadataFieldPrefix(columnId); + return trimmedColumnId === field.key; + }); + }); + // Check if item_name field already exists to avoid duplicates const hasItemNameField = fields.some((field: MetadataTemplateField) => field.key === ITEM_FILTER_NAME); @@ -185,7 +196,7 @@ const MetadataViewContainer = ({ }) || [], }, ]; - }, [formatMessage, metadataTemplate]); + }, [formatMessage, metadataTemplate, newColumns]); const initialFilterValues = React.useMemo( () => transformInitialFilterValuesToInternal(initialFilterValuesProp), @@ -203,20 +214,6 @@ const MetadataViewContainer = ({ [onFilterSubmit, onMetadataFilter], ); - const transformedActionBarProps = React.useMemo(() => { - return { - ...actionBarProps, - initialFilterValues, - onFilterSubmit: handleFilterSubmit, - filterGroups, - - predefinedFilterOptions: { - [PredefinedFilterName.KeywordSearchFilterGroup]: { isDisabled: true }, - [PredefinedFilterName.LocationFilterGroup]: { isDisabled: true }, - }, - }; - }, [actionBarProps, initialFilterValues, handleFilterSubmit, filterGroups]); - // Create a wrapper function that calls both. The wrapper function should follow the signature of onSortChange from RAC const handleSortChange = React.useCallback( ({ column, direction }: SortDescriptor) => { @@ -239,6 +236,22 @@ const MetadataViewContainer = ({ [onSortChangeInternal, tableProps], ); + const transformedActionBarProps = React.useMemo(() => { + return { + ...actionBarProps, + initialFilterValues, + onFilterSubmit: handleFilterSubmit, + filterGroups, + sortDropdownProps: { + onSortChange: handleSortChange, + }, + predefinedFilterOptions: { + [PredefinedFilterName.KeywordSearchFilterGroup]: { isDisabled: true }, + [PredefinedFilterName.LocationFilterGroup]: { isDisabled: true }, + }, + }; + }, [actionBarProps, initialFilterValues, handleFilterSubmit, handleSortChange, filterGroups]); + // Create new tableProps with our wrapper function const newTableProps = { ...tableProps, diff --git a/src/elements/content-explorer/__tests__/ContentExplorer.test.tsx b/src/elements/content-explorer/__tests__/ContentExplorer.test.tsx index a5cb9a7e6e..7973b7ab20 100644 --- a/src/elements/content-explorer/__tests__/ContentExplorer.test.tsx +++ b/src/elements/content-explorer/__tests__/ContentExplorer.test.tsx @@ -442,12 +442,7 @@ describe('elements/content-explorer/ContentExplorer', () => { 'name', ], }; - const fieldsToShow = [ - { key: `${metadataFieldNamePrefix}.name`, canEdit: false, displayName: 'Alias' }, - { key: `${metadataFieldNamePrefix}.industry`, canEdit: true }, - { key: `${metadataFieldNamePrefix}.last_contacted_at`, canEdit: true }, - { key: `${metadataFieldNamePrefix}.role`, canEdit: true }, - ]; + const columns = [ { // Always include the name column @@ -472,9 +467,9 @@ describe('elements/content-explorer/ContentExplorer', () => { metadataViewProps: { columns, isSelectionEnabled: true, + onMetadataFilter: jest.fn(), }, metadataQuery, - fieldsToShow, defaultView, features: { contentExplorer: { diff --git a/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx b/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx index bcea2d7aff..84009e3fc1 100644 --- a/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx +++ b/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx @@ -33,12 +33,6 @@ describe('elements/content-explorer/MetadataViewContainer', () => { ]; const mockMetadataTemplateFields: MetadataTemplateField[] = [ - { - id: 'field1', - key: 'item.name', - displayName: 'Name', - type: 'string', - }, { id: 'field2', key: 'industry', @@ -101,6 +95,30 @@ describe('elements/content-explorer/MetadataViewContainer', () => { minWidth: 250, maxWidth: 250, }, + { + textValue: 'Contact Role', + id: 'role', + type: 'string', + allowsSorting: true, + minWidth: 250, + maxWidth: 250, + }, + { + textValue: 'Status', + id: 'status', + type: 'string', + allowsSorting: true, + minWidth: 250, + maxWidth: 250, + }, + { + textValue: 'Price', + id: 'price', + type: 'string', + allowsSorting: true, + minWidth: 250, + maxWidth: 250, + }, ], metadataTemplate: mockMetadataTemplate, onMetadataFilter: jest.fn(), @@ -264,9 +282,10 @@ describe('elements/content-explorer/MetadataViewContainer', () => { actionBarProps: { initialFilterValues }, }); - expect(screen.getByRole('button', { name: 'All Filters 3' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'All Filters 2' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: /Industry/i })).toHaveTextContent(/\(1\)/); - expect(screen.getByRole('button', { name: /Category/i })).toHaveTextContent(/\(2\)/); + // Category filter should not be present since there's no corresponding column + expect(screen.queryByRole('button', { name: /Category/i })).not.toBeInTheDocument(); }); test('should handle empty metadata template fields', () => { @@ -334,12 +353,6 @@ describe('elements/content-explorer/MetadataViewContainer', () => { const templateWithoutOptions: MetadataTemplate = { ...mockMetadataTemplate, fields: [ - { - id: 'field1', - key: 'name', - displayName: 'File Name', - type: 'string', - }, { id: 'field2', key: 'industry', @@ -350,11 +363,11 @@ describe('elements/content-explorer/MetadataViewContainer', () => { ], }; - renderComponent({ metadataTemplate: templateWithoutOptions }); + renderComponent({ + metadataTemplate: templateWithoutOptions, + }); expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument(); - expect(screen.getAllByRole('button', { name: 'Name' })).toHaveLength(1); // Only the one added by component - expect(screen.getByRole('button', { name: 'File Name' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Industry' })).toBeInTheDocument(); }); @@ -546,4 +559,53 @@ describe('elements/content-explorer/MetadataViewContainer', () => { ).not.toBeInTheDocument(); }); }); + + test('should filter fields based on columns provided', () => { + const template: MetadataTemplate = { + ...mockMetadataTemplate, + fields: [ + { + id: 'field1', + key: 'status', + displayName: 'Status', + type: 'enum', + options: [ + { id: 's1', key: 'Active' }, + { id: 's2', key: 'Inactive' }, + ], + }, + { + id: 'field2', + key: 'price', + displayName: 'Price', + type: 'float', + }, + { + id: 'field3', + key: 'category', + displayName: 'Category', + type: 'multiSelect', + options: [ + { id: 'c1', key: 'tech' }, + { id: 'c2', key: 'finance' }, + ], + }, + ], + }; + + // Only provide columns for 'status' and 'price', not 'category' + renderComponent({ + metadataTemplate: template, + }); + + // Should show filters for fields that have corresponding columns + expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Status' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Price' })).toBeInTheDocument(); + + // Should NOT show filter for 'category' since there's no corresponding column + expect(screen.queryByRole('button', { name: 'Category' })).not.toBeInTheDocument(); + // Should NOT show filter for 'industry' since it's not in the provided columns + expect(screen.queryByRole('button', { name: 'Industry' })).not.toBeInTheDocument(); + }); });