From 1b51b6d15695b5b75c6ada77c7b4ecb787096929 Mon Sep 17 00:00:00 2001 From: jfox-box Date: Wed, 13 Aug 2025 10:00:01 -0700 Subject: [PATCH 1/4] feat(content-explorer): Add initial filter values --- package.json | 8 +- .../MetadataViewContainer.tsx | 80 +++++- .../__tests__/MetadataViewContainer.test.tsx | 40 ++- .../tests/MetadataView-visual.stories.tsx | 62 ++-- yarn.lock | 264 ++++++++---------- 5 files changed, 272 insertions(+), 182 deletions(-) diff --git a/package.json b/package.json index 12b694ef42..701f10c735 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "@box/languages": "^1.0.0", "@box/metadata-editor": "^0.122.12", "@box/metadata-filter": "^1.16.12", - "@box/metadata-view": "^0.29.4", + "@box/metadata-view": "^0.39.4", "@box/react-virtualized": "^9.22.3-rc-box.10", "@box/types": "^0.2.1", "@cfaester/enzyme-adapter-react-18": "^0.8.0", @@ -177,7 +177,7 @@ "@typescript-eslint/eslint-plugin": "^7.3.1", "@typescript-eslint/parser": "^7.3.1", "autoprefixer": "^10.4.19", - "axios": "^0.30.0", + "axios": "^0.30.1", "babel-core": "^7.0.0-bridge.0", "babel-jest": "^29.7.0", "babel-loader": "^9.1.3", @@ -286,7 +286,7 @@ "ts-loader": "^6.2.1", "typescript": "5.2.2", "uuid": "^8.3.2", - "wait-on": "^3.3.0", + "wait-on": "^8.0.4", "webpack": "^5.92.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^5.1.4", @@ -303,7 +303,7 @@ "@box/item-icon": "^0.17.0", "@box/metadata-editor": "^0.122.12", "@box/metadata-filter": "^1.16.12", - "@box/metadata-view": "^0.29.4", + "@box/metadata-view": "^0.39.4", "@box/react-virtualized": "^9.22.3-rc-box.10", "@box/types": "^0.2.1", "@hapi/address": "^2.1.4", diff --git a/src/elements/content-explorer/MetadataViewContainer.tsx b/src/elements/content-explorer/MetadataViewContainer.tsx index f142563f54..6b5f632018 100644 --- a/src/elements/content-explorer/MetadataViewContainer.tsx +++ b/src/elements/content-explorer/MetadataViewContainer.tsx @@ -1,9 +1,30 @@ import * as React from 'react'; import { MetadataView, type MetadataViewProps } from '@box/metadata-view'; +import { FloatType, MetadataFormFieldValue, RangeType } from '@box/metadata-filter'; + import type { MetadataTemplate } from '../../common/types/metadata'; import type { Collection } from '../../common/types/core'; -export interface MetadataViewContainerProps extends Omit { +// Public-friendly metadata value shape (array value for enum type, range/float objects stay the same) +export type MetadataFormFieldValuePublic = string[] | RangeType | FloatType; + +type FilterValuesPublic = Record< + string, + { + value: MetadataFormFieldValuePublic; + } +>; + +export type ActionBarProps = Omit< + MetadataViewProps['actionBarProps'], + 'initialFilterValues' | 'onFilterSubmit' | 'filterGroups' +> & { + initialFilterValues?: FilterValuesPublic; + onFilterSubmit?: (filterValues: FilterValuesPublic) => void; +}; + +export interface MetadataViewContainerProps extends Omit { + actionBarProps?: ActionBarProps; currentCollection: Collection; metadataTemplate: MetadataTemplate; } @@ -36,17 +57,54 @@ const MetadataViewContainer = ({ [metadataTemplate], ); - return ( - + // Transform initial filter values to internal field format + const initialFilterValues = React.useMemo(() => { + const filterValues = actionBarProps?.initialFilterValues; + if (!filterValues) return undefined; + + const transformed: Record = {}; + Object.entries(filterValues).forEach(([key, filterValue]) => { + const { value } = filterValue as { value: unknown }; + if (Array.isArray(value)) { + // Convert customer-friendly array to internal enum shape + transformed[key] = { value: { enum: value } as unknown as MetadataFormFieldValue }; + } else { + // Keep range/float as-is + transformed[key] = filterValue as unknown as { value: MetadataFormFieldValue }; + } + }); + return transformed; + }, [actionBarProps?.initialFilterValues]); + + // Transform field values to public-friendly format + const onFilterSubmit = React.useCallback( + (fields: Record) => { + if (!actionBarProps?.onFilterSubmit) return; + + const transformed: Record = {}; + Object.entries(fields).forEach(([key, filterValue]) => { + const { value } = filterValue as { value: MetadataFormFieldValuePublic }; + if (value && 'enum' in value && Array.isArray(value.enum)) { + transformed[key] = { value: value.enum }; + } else { + transformed[key] = { value }; + } + }); + actionBarProps.onFilterSubmit(transformed); + }, + [actionBarProps], ); + + const transformedActionBarProps = React.useMemo(() => { + return { + ...actionBarProps, + initialFilterValues, + onFilterSubmit, + filterGroups, + }; + }, [actionBarProps, initialFilterValues, onFilterSubmit, filterGroups]); + + return ; }; export default MetadataViewContainer; diff --git a/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx b/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx index 16df4b3a0e..6e0f93dc6b 100644 --- a/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx +++ b/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; -import { render, screen } from '../../../test-utils/testing-library'; +import userEvent from '@testing-library/user-event'; +import { render, screen, waitFor, within } from '../../../test-utils/testing-library'; import MetadataViewContainer, { MetadataViewContainerProps } from '../MetadataViewContainer'; import type { Collection } from '../../../common/types/core'; import type { MetadataTemplate, MetadataTemplateField } from '../../../common/types/metadata'; @@ -80,4 +81,41 @@ describe('elements/content-explorer/MetadataViewContainer', () => { expect(screen.getByText('File 1.txt')).toBeInTheDocument(); expect(screen.getByText('File 2.pdf')).toBeInTheDocument(); }); + + test('should pass values as string[] on submit', async () => { + const onFilterSubmit = jest.fn(); + const template: MetadataTemplate = { + ...mockMetadataTemplate, + fields: [ + { + id: 'ms1', + key: 'role', + displayName: 'Contact Role', + type: 'multiSelect', + options: [ + { id: 'r1', key: 'Developer' }, + { id: 'r2', key: 'Marketing' }, + { id: 'r3', key: 'Sales' }, + ], + }, + ], + }; + + renderComponent({ metadataTemplate: template, actionBarProps: { onFilterSubmit } }); + + const roleChip = screen.getByRole('button', { name: /Contact Role/ }); + await userEvent.click(roleChip); + let menu = screen.getByRole('menu'); + await userEvent.click(within(menu).getByRole('menuitemcheckbox', { name: 'Developer' })); + // Re-open the chip to select a second value (menu closes after submit) + await userEvent.click(roleChip); + menu = screen.getByRole('menu'); + await userEvent.click(within(menu).getByRole('menuitemcheckbox', { name: 'Marketing' })); + + await waitFor(() => expect(onFilterSubmit).toHaveBeenCalledTimes(2)); + const firstCall = onFilterSubmit.mock.calls[0][0]; + const secondCall = onFilterSubmit.mock.calls[1][0]; + expect(firstCall['role-filter'].value).toEqual(['Developer']); + expect(secondCall['role-filter'].value).toEqual(['Developer', 'Marketing']); + }); }); diff --git a/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx b/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx index 87b7bbd072..a0cf00670b 100644 --- a/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx +++ b/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx @@ -1,6 +1,7 @@ -import React from 'react'; -import { http, HttpResponse } from 'msw'; import type { Meta, StoryObj } from '@storybook/react'; +import { http, HttpResponse } from 'msw'; +import { expect, waitFor } from 'storybook/test'; + import ContentExplorer from '../../ContentExplorer'; import { DEFAULT_HOSTNAME_API } from '../../../../constants'; import { mockMetadata, mockSchema } from '../../../common/__mocks__/mockMetadata'; @@ -71,26 +72,55 @@ export const metadataView: Story = { }, }; +const metadataViewV2ElementProps = { + metadataViewProps: { + columns, + }, + metadataQuery, + fieldsToShow, + defaultView, + features: { + contentExplorer: { + metadataViewV2: true, + }, + }, +}; + export const metadataViewV2: Story = { + args: metadataViewV2ElementProps, +}; + +const initialFilterActionBarProps = { + initialFilterValues: { + 'industry-filter': { value: ['Legal'] }, + 'mimetype-filter': { value: ['boxnoteType', 'documentType', 'threedType'] }, + 'role-filter': { value: ['Developer', 'Business Owner', 'Marketing'] }, + }, +}; + +export const metadataViewV2WithInitialFilterValues: Story = { args: { + ...metadataViewV2ElementProps, metadataViewProps: { columns, - }, - metadataQuery, - fieldsToShow, - defaultView, - features: { - contentExplorer: { - metadataViewV2: true, - }, + actionBarProps: initialFilterActionBarProps, }, }, - render: args => { - return ( -
- -
- ); + play: async ({ canvas }) => { + // Wait for content to render + await waitFor(() => { + expect(canvas.getByRole('row', { name: /Child 2/i })).toBeInTheDocument(); + }); + + // Chips should reflect initial counts + const industryChip = canvas.getByRole('button', { name: /Industry/i }); + await waitFor(() => expect(industryChip).toHaveTextContent(/\(1\)/)); + + const contactRoleChip = canvas.getByRole('button', { name: /Contact Role/i }); + await waitFor(() => expect(contactRoleChip).toHaveTextContent(/\(3\)/)); + + const fileTypeChip = canvas.getByRole('button', { name: /Box Note/i }); + await waitFor(() => expect(fileTypeChip).toHaveTextContent(/\+2/)); }, }; diff --git a/yarn.lock b/yarn.lock index 1c94941c9d..51bdd9834b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1522,10 +1522,10 @@ resolved "https://registry.yarnpkg.com/@box/metadata-filter/-/metadata-filter-1.18.0.tgz#c6e2a69f1ce9919063243fb451a97b4fc78ed019" integrity sha512-us2njX6ade6iYeJekaerUv67d+yAob+o14ouYkfHzRFjLyfxDxB6ycTy/0jHwXLQYREXSZfIu6L+INEgbT2VAA== -"@box/metadata-view@^0.29.4": - version "0.29.4" - resolved "https://registry.yarnpkg.com/@box/metadata-view/-/metadata-view-0.29.4.tgz#03c42c0e32e9fc9895a5b56bc4a97daafeec8ca9" - integrity sha512-0CPQ7PE6uiW4hO3EOfpyLu1nD0u4UXwCMYsdHjQTHSeqgpmT8sx7ZlZ/7or+bwlekMqijdJ2CqbJhz3WsIlIfQ== +"@box/metadata-view@^0.39.4": + version "0.39.4" + resolved "https://registry.yarnpkg.com/@box/metadata-view/-/metadata-view-0.39.4.tgz#2b3594763e17d65a898d8edc8ac2c23fc917c59d" + integrity sha512-BDVFt4/Q1dpBCVx1OjjQJ5RkAhsZHZ6pI9A3/b9SUDk0rH+EO40WBofkyCQ96HIPfpVUH1tJZ8ase7JnVdLE8A== "@box/react-virtualized@^9.22.3-rc-box.10": version "9.22.3-rc-box.10" @@ -2191,37 +2191,22 @@ tslib "^2.4.0" typescript "5" -"@hapi/address@2.x.x", "@hapi/address@^2.1.4": +"@hapi/address@^2.1.4": version "2.1.4" resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ== -"@hapi/bourne@1.x.x": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-1.3.2.tgz#0a7095adea067243ce3283e1b56b8a8f453b242a" - integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA== - -"@hapi/hoek@8.x.x", "@hapi/hoek@^8.3.0": - version "8.5.1" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06" - integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== - -"@hapi/joi@^15.0.3": - version "15.1.1" - resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7" - integrity sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ== - dependencies: - "@hapi/address" "2.x.x" - "@hapi/bourne" "1.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/topo" "3.x.x" +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== -"@hapi/topo@3.x.x": - version "3.1.6" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-3.1.6.tgz#68d935fa3eae7fdd5ab0d7f953f3205d8b2bfc29" - integrity sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ== +"@hapi/topo@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== dependencies: - "@hapi/hoek" "^8.3.0" + "@hapi/hoek" "^9.0.0" "@humanwhocodes/config-array@^0.11.14": version "0.11.14" @@ -5020,6 +5005,23 @@ lodash-es "^4.17.21" read-package-up "^11.0.0" +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "@sigstore/bundle@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@sigstore/bundle/-/bundle-3.1.0.tgz#74f8f3787148400ddd364be8a9a9212174c66646" @@ -6405,7 +6407,7 @@ ajv-keywords@^5.1.0: dependencies: fast-deep-equal "^3.1.3" -ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.9.1: +ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.9.1: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -6855,13 +6857,22 @@ axe-core@^4.9.1: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.9.1.tgz#fcd0f4496dad09e0c899b44f6c4bb7848da912ae" integrity sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw== -axios@^0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.30.0.tgz#026ae2c0ae6ac35d564056690683fb77c991d0d3" - integrity sha512-Z4F3LjCgfjZz8BMYalWdMgAQUnEtKDmpwNHjh/C8pQZWde32TF64cqnSeyL3xD/aTIASRU30RHTNzRiV/NpGMg== +axios@^0.30.1: + version "0.30.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.30.1.tgz#7ab803b877eca707ea5f4de55ea0358fe26898e0" + integrity sha512-2XabsR1u0/B6OoKy57/xJmPkQiUvdoV93oW4ww+Xjee7C2er/O5U77lvqycDkT2VQDtfjYcjw8ZV8GDaoqwjHQ== dependencies: follow-redirects "^1.15.4" - form-data "^4.0.0" + form-data "^4.0.4" + proxy-from-env "^1.1.0" + +axios@^1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.11.0.tgz#c2ec219e35e414c025b2095e8b8280278478fdb6" + integrity sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.4" proxy-from-env "^1.1.0" axobject-query@~3.1.1: @@ -7956,7 +7967,7 @@ colors@1.4.0: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: +combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -8194,11 +8205,6 @@ core-js-compat@^3.36.1, core-js-compat@^3.37.1, core-js-compat@^3.6.2: dependencies: browserslist "^4.23.0" -core-js@^2.6.5: - version "2.6.12" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" - integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== - core-js@^3.1.4, core-js@^3.6.4: version "3.37.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.37.1.tgz#d21751ddb756518ac5a00e4d66499df981a62db9" @@ -10732,6 +10738,11 @@ follow-redirects@^1.0.0, follow-redirects@^1.15.4: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== +follow-redirects@^1.15.6: + version "1.15.11" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" + integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== + for-each@^0.3.3, for-each@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" @@ -10770,10 +10781,10 @@ fork-ts-checker-webpack-plugin@^8.0.0: semver "^7.3.5" tapable "^2.2.1" -form-data@^4.0.0, form-data@~4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.3.tgz#608b1b3f3e28be0fccf5901fc85fb3641e5cf0ae" - integrity sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA== +form-data@^4.0.0, form-data@^4.0.4, form-data@~4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4" + integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" @@ -10781,15 +10792,6 @@ form-data@^4.0.0, form-data@~4.0.0: hasown "^2.0.2" mime-types "^2.1.12" -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - form-serialize@^0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/form-serialize/-/form-serialize-0.7.2.tgz#b0a2ff0c22026fb6d3d15c9d33f6de6a432e4732" @@ -11285,19 +11287,6 @@ handlebars@^4.7.7: optionalDependencies: uglify-js "^3.1.4" -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== - -har-validator@~5.1.3: - version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -11637,15 +11626,6 @@ http-proxy@^1.18.1: follow-redirects "^1.0.0" requires-port "^1.0.0" -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - http-signature@~1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.4.0.tgz#dee5a9ba2bf49416abc544abd6d967f6a94c8c3f" @@ -12979,6 +12959,17 @@ jiti@^2.4.1: resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560" integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== +joi@^17.13.3: + version "17.13.3" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.3.tgz#0f5cc1169c999b30d344366d384b12d92558bcec" + integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + js-sha1@0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/js-sha1/-/js-sha1-0.6.0.tgz#adbee10f0e8e18aa07cdea807cf08e9183dbc7f9" @@ -13154,16 +13145,6 @@ jsonparse@^1.2.0, jsonparse@^1.3.1: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -jsprim@^1.2.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" - integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.4.0" - verror "1.10.0" - jsprim@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" @@ -14767,11 +14748,6 @@ nwsapi@^2.2.2: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.12.tgz#fb6af5c0ec35b27b4581eb3bbad34ec9e5c696f8" integrity sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w== -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -16295,7 +16271,7 @@ prr@~1.0.1: resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== -psl@^1.1.28, psl@^1.1.33: +psl@^1.1.33: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== @@ -16359,11 +16335,6 @@ qs@6.14.0, qs@^6.12.3: dependencies: side-channel "^1.1.0" -qs@~6.5.2: - version "6.5.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" - integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== - query-string@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" @@ -17189,32 +17160,6 @@ request-progress@^3.0.0: dependencies: throttleit "^1.0.0" -request@^2.88.0: - version "2.88.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -17413,11 +17358,6 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rx@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" - integrity sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug== - rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.2: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" @@ -17432,6 +17372,13 @@ rxjs@^7.5.1: dependencies: tslib "^2.1.0" +rxjs@^7.8.2: + version "7.8.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" + integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== + dependencies: + tslib "^2.1.0" + safe-array-concat@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" @@ -18134,7 +18081,7 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -sshpk@^1.18.0, sshpk@^1.7.0: +sshpk@^1.18.0: version "1.18.0" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== @@ -18278,7 +18225,7 @@ string-replace-loader@^3.1.0: loader-utils "^2.0.0" schema-utils "^3.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -18313,6 +18260,15 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -18419,7 +18375,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -18447,6 +18403,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -19048,14 +19011,6 @@ tough-cookie@^5.0.0: dependencies: tldts "^6.1.32" -tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - tr46@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" @@ -19570,7 +19525,7 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@^3.1.0, uuid@^3.3.2: +uuid@^3.1.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -19650,16 +19605,16 @@ w3c-xmlserializer@^4.0.0: dependencies: xml-name-validator "^4.0.0" -wait-on@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-3.3.0.tgz#9940981d047a72a9544a97b8b5fca45b2170a082" - integrity sha512-97dEuUapx4+Y12aknWZn7D25kkjMk16PbWoYzpSdA8bYpVfS6hpl2a2pOWZ3c+Tyt3/i4/pglyZctG3J4V1hWQ== +wait-on@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-8.0.4.tgz#0e9dbc298fa0a6947d0993d41905fe994672bcee" + integrity sha512-8f9LugAGo4PSc0aLbpKVCVtzayd36sSCp4WLpVngkYq6PK87H79zt77/tlCU6eKCLqR46iFvcl0PU5f+DmtkwA== dependencies: - "@hapi/joi" "^15.0.3" - core-js "^2.6.5" - minimist "^1.2.0" - request "^2.88.0" - rx "^4.1.0" + axios "^1.11.0" + joi "^17.13.3" + lodash "^4.17.21" + minimist "^1.2.8" + rxjs "^7.8.2" walk-up-path@^3.0.1: version "3.0.1" @@ -20007,7 +19962,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -20042,6 +19997,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 0adee8003b2e4697bc9a2ab891f875b4d9f59b8b Mon Sep 17 00:00:00 2001 From: jfox-box Date: Wed, 13 Aug 2025 10:36:31 -0700 Subject: [PATCH 2/4] feat(content-explorer): Add initial filter values --- .../content-explorer/MetadataViewContainer.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/elements/content-explorer/MetadataViewContainer.tsx b/src/elements/content-explorer/MetadataViewContainer.tsx index 6b5f632018..b63896fd1b 100644 --- a/src/elements/content-explorer/MetadataViewContainer.tsx +++ b/src/elements/content-explorer/MetadataViewContainer.tsx @@ -8,7 +8,7 @@ import type { Collection } from '../../common/types/core'; // Public-friendly metadata value shape (array value for enum type, range/float objects stay the same) export type MetadataFormFieldValuePublic = string[] | RangeType | FloatType; -type FilterValuesPublic = Record< +export type FilterValuesPublic = Record< string, { value: MetadataFormFieldValuePublic; @@ -64,13 +64,13 @@ const MetadataViewContainer = ({ const transformed: Record = {}; Object.entries(filterValues).forEach(([key, filterValue]) => { - const { value } = filterValue as { value: unknown }; + const { value } = filterValue; if (Array.isArray(value)) { // Convert customer-friendly array to internal enum shape - transformed[key] = { value: { enum: value } as unknown as MetadataFormFieldValue }; + transformed[key] = { value: { enum: value } }; } else { // Keep range/float as-is - transformed[key] = filterValue as unknown as { value: MetadataFormFieldValue }; + transformed[key] = { value }; } }); return transformed; @@ -83,11 +83,11 @@ const MetadataViewContainer = ({ const transformed: Record = {}; Object.entries(fields).forEach(([key, filterValue]) => { - const { value } = filterValue as { value: MetadataFormFieldValuePublic }; - if (value && 'enum' in value && Array.isArray(value.enum)) { + const { value } = filterValue; + if (value && typeof value === 'object' && 'enum' in value && Array.isArray(value.enum)) { transformed[key] = { value: value.enum }; } else { - transformed[key] = { value }; + transformed[key] = { value: value as RangeType | FloatType }; } }); actionBarProps.onFilterSubmit(transformed); From 0bb39ff00a68b5f860ba6c34f89126778ccddc58 Mon Sep 17 00:00:00 2001 From: jfox-box Date: Wed, 13 Aug 2025 14:47:49 -0700 Subject: [PATCH 3/4] feat(content-explorer): Add initial filter values to Metadata View --- .../MetadataViewContainer.tsx | 85 ++++++++++--------- .../__tests__/MetadataViewContainer.test.tsx | 19 ++--- .../tests/MetadataView-visual.stories.tsx | 13 ++- 3 files changed, 59 insertions(+), 58 deletions(-) diff --git a/src/elements/content-explorer/MetadataViewContainer.tsx b/src/elements/content-explorer/MetadataViewContainer.tsx index b63896fd1b..8149835b49 100644 --- a/src/elements/content-explorer/MetadataViewContainer.tsx +++ b/src/elements/content-explorer/MetadataViewContainer.tsx @@ -1,28 +1,56 @@ import * as React from 'react'; +import type { EnumType, FloatType, MetadataFormFieldValue, RangeType } from '@box/metadata-filter'; import { MetadataView, type MetadataViewProps } from '@box/metadata-view'; -import { FloatType, MetadataFormFieldValue, RangeType } from '@box/metadata-filter'; -import type { MetadataTemplate } from '../../common/types/metadata'; import type { Collection } from '../../common/types/core'; +import type { MetadataTemplate } from '../../common/types/metadata'; -// Public-friendly metadata value shape (array value for enum type, range/float objects stay the same) -export type MetadataFormFieldValuePublic = string[] | RangeType | FloatType; +// Public-friendly version of MetadataFormFieldValue from @box/metadata-filter +// (string[] for enum type, range/float objects stay the same) +type EnumToStringArray = T extends EnumType ? string[] : T; +type ExternalMetadataFormFieldValue = EnumToStringArray; -export type FilterValuesPublic = Record< +type ExternalFilterValues = Record< string, { - value: MetadataFormFieldValuePublic; + value: ExternalMetadataFormFieldValue; } >; -export type ActionBarProps = Omit< +type ActionBarProps = Omit< MetadataViewProps['actionBarProps'], 'initialFilterValues' | 'onFilterSubmit' | 'filterGroups' > & { - initialFilterValues?: FilterValuesPublic; - onFilterSubmit?: (filterValues: FilterValuesPublic) => void; + initialFilterValues?: ExternalFilterValues; + onFilterSubmit?: (filterValues: ExternalFilterValues) => void; }; +function transformInitialFilterValuesToInternal( + publicValues?: ExternalFilterValues, +): Record | undefined { + if (!publicValues) return undefined; + + return Object.entries(publicValues).reduce>( + (acc, [key, { value }]) => { + acc[key] = Array.isArray(value) ? { value: { enum: value } } : { value }; + return acc; + }, + {}, + ); +} + +function transformInternalFieldsToPublic( + fields: Record, +): ExternalFilterValues { + return Object.entries(fields).reduce((acc, [key, { value }]) => { + acc[key] = + 'enum' in value && Array.isArray(value.enum) + ? { value: value.enum } + : { value: value as RangeType | FloatType }; + return acc; + }, {}); +} + export interface MetadataViewContainerProps extends Omit { actionBarProps?: ActionBarProps; currentCollection: Collection; @@ -37,6 +65,7 @@ const MetadataViewContainer = ({ ...rest }: MetadataViewContainerProps) => { const { items = [] } = currentCollection; + const { initialFilterValues: initialFilterValuesProp, onFilterSubmit: onFilterSubmitProp } = actionBarProps ?? {}; const filterGroups = React.useMemo( () => [ @@ -58,41 +87,19 @@ const MetadataViewContainer = ({ ); // Transform initial filter values to internal field format - const initialFilterValues = React.useMemo(() => { - const filterValues = actionBarProps?.initialFilterValues; - if (!filterValues) return undefined; - - const transformed: Record = {}; - Object.entries(filterValues).forEach(([key, filterValue]) => { - const { value } = filterValue; - if (Array.isArray(value)) { - // Convert customer-friendly array to internal enum shape - transformed[key] = { value: { enum: value } }; - } else { - // Keep range/float as-is - transformed[key] = { value }; - } - }); - return transformed; - }, [actionBarProps?.initialFilterValues]); + const initialFilterValues = React.useMemo( + () => transformInitialFilterValuesToInternal(initialFilterValuesProp), + [initialFilterValuesProp], + ); // Transform field values to public-friendly format const onFilterSubmit = React.useCallback( (fields: Record) => { - if (!actionBarProps?.onFilterSubmit) return; - - const transformed: Record = {}; - Object.entries(fields).forEach(([key, filterValue]) => { - const { value } = filterValue; - if (value && typeof value === 'object' && 'enum' in value && Array.isArray(value.enum)) { - transformed[key] = { value: value.enum }; - } else { - transformed[key] = { value: value as RangeType | FloatType }; - } - }); - actionBarProps.onFilterSubmit(transformed); + if (!onFilterSubmitProp) return; + const transformed = transformInternalFieldsToPublic(fields); + onFilterSubmitProp(transformed); }, - [actionBarProps], + [onFilterSubmitProp], ); const transformedActionBarProps = React.useMemo(() => { diff --git a/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx b/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx index 6e0f93dc6b..a480490180 100644 --- a/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx +++ b/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; -import userEvent from '@testing-library/user-event'; -import { render, screen, waitFor, within } from '../../../test-utils/testing-library'; -import MetadataViewContainer, { MetadataViewContainerProps } from '../MetadataViewContainer'; + import type { Collection } from '../../../common/types/core'; import type { MetadataTemplate, MetadataTemplateField } from '../../../common/types/metadata'; +import { render, screen, userEvent, waitFor, within } from '../../../test-utils/testing-library'; +import MetadataViewContainer, { MetadataViewContainerProps } from '../MetadataViewContainer'; describe('elements/content-explorer/MetadataViewContainer', () => { const mockItems = [ @@ -19,7 +19,7 @@ describe('elements/content-explorer/MetadataViewContainer', () => { type: 'string', }, { - id: 'field1', + id: 'field2', key: 'industry', displayName: 'Industry', type: 'enum', @@ -103,14 +103,11 @@ describe('elements/content-explorer/MetadataViewContainer', () => { renderComponent({ metadataTemplate: template, actionBarProps: { onFilterSubmit } }); - const roleChip = screen.getByRole('button', { name: /Contact Role/ }); - await userEvent.click(roleChip); - let menu = screen.getByRole('menu'); - await userEvent.click(within(menu).getByRole('menuitemcheckbox', { name: 'Developer' })); + await userEvent().click(screen.getByRole('button', { name: /Contact Role/ })); + await userEvent().click(within(screen.getByRole('menu')).getByRole('menuitemcheckbox', { name: 'Developer' })); // Re-open the chip to select a second value (menu closes after submit) - await userEvent.click(roleChip); - menu = screen.getByRole('menu'); - await userEvent.click(within(menu).getByRole('menuitemcheckbox', { name: 'Marketing' })); + await userEvent().click(screen.getByRole('button', { name: /Contact Role/ })); + await userEvent().click(within(screen.getByRole('menu')).getByRole('menuitemcheckbox', { name: 'Marketing' })); await waitFor(() => expect(onFilterSubmit).toHaveBeenCalledTimes(2)); const firstCall = onFilterSubmit.mock.calls[0][0]; diff --git a/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx b/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx index 7a9d2968fd..93cecdca8a 100644 --- a/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx +++ b/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx @@ -150,19 +150,16 @@ export const metadataViewV2WithInitialFilterValues: Story = { }, }, play: async ({ canvas }) => { - // Wait for content to render + // Wait for chips to update with initial values await waitFor(() => { - expect(canvas.getByRole('row', { name: /Child 2/i })).toBeInTheDocument(); + expect(canvas.getByRole('button', { name: /Industry/i })).toHaveTextContent(/\(1\)/); }); - // Chips should reflect initial counts - const industryChip = canvas.getByRole('button', { name: /Industry/i }); - await waitFor(() => expect(industryChip).toHaveTextContent(/\(1\)/)); - + // Other chips should reflect initialized values const contactRoleChip = canvas.getByRole('button', { name: /Contact Role/i }); - await waitFor(() => expect(contactRoleChip).toHaveTextContent(/\(3\)/)); + expect(contactRoleChip).toHaveTextContent(/\(3\)/); const fileTypeChip = canvas.getByRole('button', { name: /Box Note/i }); - await waitFor(() => expect(fileTypeChip).toHaveTextContent(/\+2/)); + expect(fileTypeChip).toHaveTextContent(/\+2/); }, }; From 2966b714a51f99d8de601b30f5470ba75a4229fc Mon Sep 17 00:00:00 2001 From: jfox-box Date: Thu, 14 Aug 2025 13:26:49 -0700 Subject: [PATCH 4/4] feat(content-explorer): Add initial filter values to Metadata View --- package.json | 16 +++++----- .../__tests__/MetadataViewContainer.test.tsx | 2 +- yarn.lock | 32 +++++++++---------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 701f10c735..4220a269dc 100644 --- a/package.json +++ b/package.json @@ -128,15 +128,15 @@ "@box/blueprint-web-assets": "4.61.5", "@box/box-ai-agent-selector": "^0.52.0", "@box/box-ai-content-answers": "^0.124.1", - "@box/box-item-type-selector": "^0.61.12", + "@box/box-item-type-selector": "^0.63.12", "@box/cldr-data": "^34.2.0", "@box/combobox-with-api": "^0.34.9", "@box/frontend": "^11.0.1", - "@box/item-icon": "^0.17.0", + "@box/item-icon": "^0.17.15", "@box/languages": "^1.0.0", "@box/metadata-editor": "^0.122.12", - "@box/metadata-filter": "^1.16.12", - "@box/metadata-view": "^0.39.4", + "@box/metadata-filter": "^1.19.2", + "@box/metadata-view": "^0.41.2", "@box/react-virtualized": "^9.22.3-rc-box.10", "@box/types": "^0.2.1", "@cfaester/enzyme-adapter-react-18": "^0.8.0", @@ -297,13 +297,13 @@ "@box/blueprint-web-assets": "4.61.5", "@box/box-ai-agent-selector": "^0.52.0", "@box/box-ai-content-answers": "^0.124.1", - "@box/box-item-type-selector": "^0.61.12", + "@box/box-item-type-selector": "^0.63.12", "@box/cldr-data": ">=34.2.0", "@box/combobox-with-api": "^0.34.9", - "@box/item-icon": "^0.17.0", + "@box/item-icon": "^0.17.15", "@box/metadata-editor": "^0.122.12", - "@box/metadata-filter": "^1.16.12", - "@box/metadata-view": "^0.39.4", + "@box/metadata-filter": "^1.19.2", + "@box/metadata-view": "^0.41.2", "@box/react-virtualized": "^9.22.3-rc-box.10", "@box/types": "^0.2.1", "@hapi/address": "^2.1.4", diff --git a/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx b/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx index a480490180..0fcaceb8e6 100644 --- a/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx +++ b/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import type { Collection } from '../../../common/types/core'; import type { MetadataTemplate, MetadataTemplateField } from '../../../common/types/metadata'; import { render, screen, userEvent, waitFor, within } from '../../../test-utils/testing-library'; -import MetadataViewContainer, { MetadataViewContainerProps } from '../MetadataViewContainer'; +import MetadataViewContainer, { type MetadataViewContainerProps } from '../MetadataViewContainer'; describe('elements/content-explorer/MetadataViewContainer', () => { const mockItems = [ diff --git a/yarn.lock b/yarn.lock index 51bdd9834b..6c340595b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1475,10 +1475,10 @@ resolved "https://registry.yarnpkg.com/@box/box-ai-content-answers/-/box-ai-content-answers-0.124.5.tgz#664a34d8338569be6801c1433295f858d8f054ad" integrity sha512-0p1j2JW9ig0rhM4tiOyEFN6dbiHa7wuX8PmKTjPbgU5MQDibGSbNeZS7IxZlJLYs2BmJ+IncUrPN+zlnsfaRgw== -"@box/box-item-type-selector@^0.61.12": - version "0.61.16" - resolved "https://registry.yarnpkg.com/@box/box-item-type-selector/-/box-item-type-selector-0.61.16.tgz#476ba5d58e177f9967a064077273d0d690b887f0" - integrity sha512-0wiiqX4/x6K7lX7QfbQYxW3ELTV+9rYdRxdV2IZqE+3fzrGiSgucYtRLkyfeqsBRUFDQSfYRXHA+sE4ZdTb/fA== +"@box/box-item-type-selector@^0.63.12": + version "0.63.13" + resolved "https://registry.yarnpkg.com/@box/box-item-type-selector/-/box-item-type-selector-0.63.13.tgz#08999f0149f9222f8d7f00db20f7951c59b6da97" + integrity sha512-c3N2zTPfZqYhLgQOHEo5kE/TTrXi9BDEaPQrqb2ctsySdRCzkwLk6DfVhGFyXLQeIl0zZLhMXomSMKSxryG1cA== "@box/cldr-data@^34.2.0": version "34.8.0" @@ -1502,10 +1502,10 @@ rimraf "^3.0.0" semver "^7.4.0" -"@box/item-icon@^0.17.0": - version "0.17.0" - resolved "https://registry.yarnpkg.com/@box/item-icon/-/item-icon-0.17.0.tgz#2e92c62046ce0d35c6d510ae1f2d2a36b31d72ad" - integrity sha512-Pj4XUYMnMfjZ72ZPMxmbt//juidqArdbfyxxbXEI+yKH8hsO4jr6Z2RGAUmSepweWkxYHLm5gDzTEJwBC0csHQ== +"@box/item-icon@^0.17.15": + version "0.17.16" + resolved "https://registry.yarnpkg.com/@box/item-icon/-/item-icon-0.17.16.tgz#bd883e33a5f75e8153a9236edbf30674a1dc69c2" + integrity sha512-8uNEvBuXHdoRonmQUe5lCyyIkzFIY7Fhcb2WD9yhfgRU4wHS/rqWZ6JHicGUkpT7GPNssEwuWU44Fytqp8lHvw== "@box/languages@^1.0.0": version "1.1.2" @@ -1517,15 +1517,15 @@ resolved "https://registry.yarnpkg.com/@box/metadata-editor/-/metadata-editor-0.122.16.tgz#d120602dcc39a6b4cd6f441ddd5fdc4b2f2862a4" integrity sha512-fy43Z9fr6+t0hO+tlVDX4A890K0mos78GqHeZp9fGDH+lQsjHBHz3DDvcbyfmkCrf3Dbg15wrvBO0Pa4FaCSvw== -"@box/metadata-filter@^1.16.12": - version "1.18.0" - resolved "https://registry.yarnpkg.com/@box/metadata-filter/-/metadata-filter-1.18.0.tgz#c6e2a69f1ce9919063243fb451a97b4fc78ed019" - integrity sha512-us2njX6ade6iYeJekaerUv67d+yAob+o14ouYkfHzRFjLyfxDxB6ycTy/0jHwXLQYREXSZfIu6L+INEgbT2VAA== +"@box/metadata-filter@^1.19.2": + version "1.19.3" + resolved "https://registry.yarnpkg.com/@box/metadata-filter/-/metadata-filter-1.19.3.tgz#87364bea4cbb1417866e65639f3b1e137a6d9b6a" + integrity sha512-5cSY8yLW7S1zsiqBHAuKkHjcyHFBuBUBHGTnYigV0eKyLH4Dm9ozjon23P3Z9HXVB5IMHwTM3I9TRDFAZuP7vw== -"@box/metadata-view@^0.39.4": - version "0.39.4" - resolved "https://registry.yarnpkg.com/@box/metadata-view/-/metadata-view-0.39.4.tgz#2b3594763e17d65a898d8edc8ac2c23fc917c59d" - integrity sha512-BDVFt4/Q1dpBCVx1OjjQJ5RkAhsZHZ6pI9A3/b9SUDk0rH+EO40WBofkyCQ96HIPfpVUH1tJZ8ase7JnVdLE8A== +"@box/metadata-view@^0.41.2": + version "0.41.3" + resolved "https://registry.yarnpkg.com/@box/metadata-view/-/metadata-view-0.41.3.tgz#95a4d8322d02c13172fb0be681e74e17f8fe90dc" + integrity sha512-7ZqUrx4YmfmwXeDoPhSpLnL8xxVBkZ3Hlw4gpfpCw8IPHdT/nYTFml1GW7DO5d43jICf3foD08wwksW9IeB7/A== "@box/react-virtualized@^9.22.3-rc-box.10": version "9.22.3-rc-box.10"