From 8a528e7ccd2ce9095002007b10a7e62bcbf6f385 Mon Sep 17 00:00:00 2001 From: Damian Lasecki Date: Fri, 26 Jun 2026 14:11:06 +0200 Subject: [PATCH 1/4] feat(metadata-editor): hide Add metadata button for non-editors --- package.json | 10 +-- .../MetadataSidebarRedesign.tsx | 4 +- .../MetadataSidebarRedesign.test.tsx | 64 ++++++++++++++++++- .../useSidebarMetadataFetcher.test.tsx | 25 +++++++- .../hooks/useSidebarMetadataFetcher.ts | 7 +- yarn.lock | 21 +++--- 6 files changed, 109 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 0202297dff..18fa9167c9 100644 --- a/package.json +++ b/package.json @@ -138,9 +138,10 @@ "@box/frontend": "^11.0.1", "@box/item-icon": "^3.2.0", "@box/languages": "^1.0.0", - "@box/metadata-editor": "^1.73.4", + "@box/metadata-editor": "^2.2.11", "@box/metadata-filter": "^1.80.23", - "@box/metadata-template-browser": "^1.24.11", + "@box/metadata-taxonomy-picker": "^3.1.8", + "@box/metadata-template-browser": "^2.1.7", "@box/metadata-view": "^1.53.26", "@box/react-virtualized": "^9.22.3-rc-box.10", "@box/readable-time": "^2.1.4", @@ -306,9 +307,10 @@ "@box/content-field": "^1.40.23", "@box/copy-input": "^1.42.16", "@box/item-icon": "^3.2.0", - "@box/metadata-editor": "^1.73.4", + "@box/metadata-editor": "^2.2.11", "@box/metadata-filter": "^1.80.23", - "@box/metadata-template-browser": "^1.24.11", + "@box/metadata-taxonomy-picker": "^3.1.8", + "@box/metadata-template-browser": "^2.1.7", "@box/metadata-view": "^1.53.26", "@box/react-virtualized": "^9.22.3-rc-box.10", "@box/readable-time": "^2.1.4", diff --git a/src/elements/content-sidebar/MetadataSidebarRedesign.tsx b/src/elements/content-sidebar/MetadataSidebarRedesign.tsx index e52a04bced..67e980a3c6 100644 --- a/src/elements/content-sidebar/MetadataSidebarRedesign.tsx +++ b/src/elements/content-sidebar/MetadataSidebarRedesign.tsx @@ -290,7 +290,9 @@ function MetadataSidebarRedesign({ const areAiSuggestionsAvailable = isExtensionSupportedForMetadataSuggestions(file?.extension ?? ''); - const metadataDropdown = isSuccess && templates && ( + const canEdit = !!file?.permissions?.can_upload; + + const metadataDropdown = canEdit && isSuccess && templates && ( { const mockFile = { id: '123', - permissions: { [FIELD_PERMISSIONS_CAN_UPLOAD]: true }, + permissions: { can_upload: true }, }; const renderComponent = (props = {}, features = {}) => { @@ -196,6 +196,66 @@ describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => { expect(screen.getByRole('button', { name: 'Add template' })).toBeInTheDocument(); }); + test('should render "Add template" button when user has can_upload permission', () => { + mockUseSidebarMetadataFetcher.mockReturnValue({ + clearExtractError: jest.fn(), + extractSuggestions: jest.fn(), + handleCreateMetadataInstance: jest.fn(), + handleDeleteMetadataInstance: jest.fn(), + handleUpdateMetadataInstance: jest.fn(), + templateInstances: [], + templates: mockTemplates, + errorMessage: null, + status: STATUS.SUCCESS, + file: { id: '123', permissions: { can_upload: true } }, + extractErrorCode: null, + }); + + renderComponent(); + + expect(screen.getByRole('button', { name: 'Add template' })).toBeInTheDocument(); + }); + + test('should not render "Add template" button when user lacks can_upload permission', () => { + mockUseSidebarMetadataFetcher.mockReturnValue({ + clearExtractError: jest.fn(), + extractSuggestions: jest.fn(), + handleCreateMetadataInstance: jest.fn(), + handleDeleteMetadataInstance: jest.fn(), + handleUpdateMetadataInstance: jest.fn(), + templateInstances: [], + templates: mockTemplates, + errorMessage: null, + status: STATUS.SUCCESS, + file: { id: '123', permissions: { can_upload: false } }, + extractErrorCode: null, + }); + + renderComponent(); + + expect(screen.queryByRole('button', { name: 'Add template' })).not.toBeInTheDocument(); + }); + + test('should not render "Add template" button when file has no permissions', () => { + mockUseSidebarMetadataFetcher.mockReturnValue({ + clearExtractError: jest.fn(), + extractSuggestions: jest.fn(), + handleCreateMetadataInstance: jest.fn(), + handleDeleteMetadataInstance: jest.fn(), + handleUpdateMetadataInstance: jest.fn(), + templateInstances: [], + templates: mockTemplates, + errorMessage: null, + status: STATUS.SUCCESS, + file: { id: '123' }, + extractErrorCode: null, + }); + + renderComponent(); + + expect(screen.queryByRole('button', { name: 'Add template' })).not.toBeInTheDocument(); + }); + test('should have selectable "Custom Metadata" template in dropdown', async () => { renderComponent(); diff --git a/src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx b/src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx index 24ab89d7aa..b856debca4 100644 --- a/src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx +++ b/src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx @@ -621,7 +621,7 @@ describe('useSidebarMetadataFetcher', () => { ); }); - test('should include include_confidence_score and include_reference when isConfidenceScoreEnabled is true', async () => { + test('should include only include_confidence_score when only isConfidenceScoreEnabled is true', async () => { mockAPI.extractStructured.mockResolvedValue({ answer: { field1: 'value1' }, created_at: '2026-03-27T08:10:14.106-07:00', @@ -632,6 +632,29 @@ describe('useSidebarMetadataFetcher', () => { await result.current.extractSuggestions('templateKey', 'global'); + expect(mockAPI.extractStructured).toHaveBeenCalledWith({ + items: [{ id: mockFile.id, type: mockFile.type }], + metadata_template: { template_key: 'templateKey', scope: 'global', type: 'metadata_template' }, + include_confidence_score: true, + }); + expect(mockAPI.extractStructured).toHaveBeenCalledWith( + expect.not.objectContaining({ + include_reference: expect.anything(), + }), + ); + }); + + test('should include include_confidence_score and include_reference when both flags are true', async () => { + mockAPI.extractStructured.mockResolvedValue({ + answer: { field1: 'value1' }, + created_at: '2026-03-27T08:10:14.106-07:00', + completion_reason: 'done', + }); + + const { result } = setupHook('123', true, true); + + await result.current.extractSuggestions('templateKey', 'global'); + expect(mockAPI.extractStructured).toHaveBeenCalledWith({ items: [{ id: mockFile.id, type: mockFile.type }], metadata_template: { template_key: 'templateKey', scope: 'global', type: 'metadata_template' }, diff --git a/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts b/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts index 0b06603e3c..3f9b0e4df9 100644 --- a/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts +++ b/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts @@ -236,12 +236,7 @@ function useSidebarMetadataFetcher( const customAiAgent = agentId ? { ai_agent: { type: 'ai_agent_id', id: agentId } } : {}; const confidenceScoreParams = isConfidenceScoreEnabled ? { include_confidence_score: true } : {}; - - // Additive gating during the metadata_bounding_box rollout: fetch bounding boxes from AI extract API when - // when either the confidence-score coupling or the new bounding-box flag is on. - // TODO: drop the isBoundingBoxOrConfidenceScoreReviewEnabled fallback so - // fetching bounding boxes depends solely on `isBoundingBoxEnabled`. - const boundingBoxParams = isBoundingBoxOrConfidenceScoreReviewEnabled ? { include_reference: true } : {}; + const boundingBoxParams = isBoundingBoxEnabled ? { include_reference: true } : {}; const requestBody: AiExtractStructured = { items: [{ id: file.id, type: file.type }], diff --git a/yarn.lock b/yarn.lock index f7ae082d05..19ae8b0aaf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1179,20 +1179,25 @@ resolved "https://registry.yarnpkg.com/@box/languages/-/languages-1.1.2.tgz#cd4266b3da62da18560d881e10b429653186be29" integrity sha512-d64TGosx+KRmrLZj4CIyLp42LUiEbgBJ8n8cviMQwTJmfU0g+UwZqLjmQZR1j+Q9D64yV4xHzY9K1t5nInWWeQ== -"@box/metadata-editor@^1.73.4": - version "1.73.11" - resolved "https://registry.yarnpkg.com/@box/metadata-editor/-/metadata-editor-1.73.11.tgz#ab80b67d3b6257bf7019a5711d803eb277993796" - integrity sha512-eiP+im6cDvDsYqQClUpt8Xc25yweJ0P6779YC7U/i4fsl21w6+FqddjtoXBtinfVqwXgo2Jelbl4wqgK/r8CYA== +"@box/metadata-editor@^2.2.11": + version "2.2.11" + resolved "https://registry.yarnpkg.com/@box/metadata-editor/-/metadata-editor-2.2.11.tgz#6a4875e0c1f3ba3593887ad6694e2b74162853d8" + integrity sha512-ouF4cPC2kzqgVJ9hnMOqtOy+SDd7MMec/igg0EqzGXXGpo8vuIsl3M2zk4qXHS9kTXXDbK+Xq0wb7WMfCj+ccA== "@box/metadata-filter@^1.80.23": version "1.80.23" resolved "https://registry.yarnpkg.com/@box/metadata-filter/-/metadata-filter-1.80.23.tgz#4eefe51151f71cc96081d5e2d036be18f1f42008" integrity sha512-6G5vgumSbUGk0r9SgCMol9HUW/9CkiHwDL5F8wH4EeRlF75xnxLGw7Q83BMMQfpJv+kkWhjJ0C/O9u0a07nBuQ== -"@box/metadata-template-browser@^1.24.11": - version "1.24.13" - resolved "https://registry.yarnpkg.com/@box/metadata-template-browser/-/metadata-template-browser-1.24.13.tgz#95f5490bf746a3c0e1629e8ba253c60295378764" - integrity sha512-P7Njbhom5dt2tLL1LCZ3jpCK76oG0mXVn3tECCpfcw2cAthZjprzPl1tgKcyWEA1mAwuiNGKl1AcQMiIXwLJ1A== +"@box/metadata-taxonomy-picker@^3.1.8": + version "3.1.8" + resolved "https://registry.yarnpkg.com/@box/metadata-taxonomy-picker/-/metadata-taxonomy-picker-3.1.8.tgz#d72f6df53c69b75f9e3fddbdaaca39858d43aade" + integrity sha512-ahlkefldWk1OlMtbD3i8b7yrPgHDkP2IBXvcFC3R+ckSsBjxa2amo63C0lViOHUc9vjZWrutY7EqeKUtTJ7OGw== + +"@box/metadata-template-browser@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@box/metadata-template-browser/-/metadata-template-browser-2.1.7.tgz#f31de745e40004805a230cd624912e1c5cf9547f" + integrity sha512-MzuFyzVInAR0ZqT4g3wro47DmirT39YaYIsxRborZtL1TpaQpgxZoqCWQ+/pgtuGoGHyoP+kpueO8APeC+XCGg== "@box/metadata-view@^1.53.26": version "1.53.26" From 7c9cd24d2ff5f29e7630bdd15e3c9848cf8e8a77 Mon Sep 17 00:00:00 2001 From: Damian Lasecki Date: Fri, 26 Jun 2026 14:35:54 +0200 Subject: [PATCH 2/4] feat(metadata-editor): updated jest.config --- scripts/jest/jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/jest/jest.config.js b/scripts/jest/jest.config.js index a8841a2c3a..05dff5bc40 100644 --- a/scripts/jest/jest.config.js +++ b/scripts/jest/jest.config.js @@ -28,6 +28,6 @@ module.exports = { testMatch: ['**/__tests__/**/*.test.+(js|jsx|ts|tsx)'], testPathIgnorePatterns: ['stories.test.js$', 'stories.test.tsx$', 'stories.test.d.ts'], transformIgnorePatterns: [ - 'node_modules/(?!(@box/activity-feed|@box/collaboration-popover|@box/react-virtualized/dist/es|@box/cldr-data|@box/blueprint-web|@box/blueprint-web-assets|@box/metadata-editor|@box/metadata-template-browser|@box/box-ai-content-answers|@box/box-ai-agent-selector|@box/item-icon|@box/combobox-with-api|@box/tree|@box/metadata-filter|@box/metadata-view|@box/content-field|@box/types|@box/box-item-type-selector|@box/unified-share-modal|@box/user-selector|@box/copy-input|@box/readable-time|@box/threaded-annotations|@box/uploads-manager)/)', + 'node_modules/(?!(@box/activity-feed|@box/collaboration-popover|@box/react-virtualized/dist/es|@box/cldr-data|@box/blueprint-web|@box/blueprint-web-assets|@box/metadata-editor|@box/metadata-template-browser|@box/metadata-taxonomy-picker|@box/box-ai-content-answers|@box/box-ai-agent-selector|@box/item-icon|@box/combobox-with-api|@box/tree|@box/metadata-filter|@box/metadata-view|@box/content-field|@box/types|@box/box-item-type-selector|@box/unified-share-modal|@box/user-selector|@box/copy-input|@box/readable-time|@box/threaded-annotations|@box/uploads-manager)/)', ], }; From 7c1f6f42df607406527b94c716c61a1b61aec560 Mon Sep 17 00:00:00 2001 From: Damian Lasecki Date: Fri, 26 Jun 2026 14:44:29 +0200 Subject: [PATCH 3/4] feat(metadata-editor): fix lint --- src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts b/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts index 3f9b0e4df9..770d85959d 100644 --- a/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts +++ b/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts @@ -315,7 +315,7 @@ function useSidebarMetadataFetcher( return result; }); }, - [api, file, isConfidenceScoreEnabled, isBoundingBoxOrConfidenceScoreReviewEnabled, onError, templates], + [api, file, isConfidenceScoreEnabled, isBoundingBoxEnabled, onError, templates], ); React.useEffect(() => { From f91ab586ddc5060ed6210b0042509764a4812e12 Mon Sep 17 00:00:00 2001 From: Damian Lasecki Date: Fri, 26 Jun 2026 15:07:34 +0200 Subject: [PATCH 4/4] feat(metadata-editor): fix storybook build --- .../stories/__mocks__/MetadataSidebarRedesignedMocks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elements/content-sidebar/stories/__mocks__/MetadataSidebarRedesignedMocks.ts b/src/elements/content-sidebar/stories/__mocks__/MetadataSidebarRedesignedMocks.ts index 739428e667..202b205c29 100644 --- a/src/elements/content-sidebar/stories/__mocks__/MetadataSidebarRedesignedMocks.ts +++ b/src/elements/content-sidebar/stories/__mocks__/MetadataSidebarRedesignedMocks.ts @@ -42,7 +42,7 @@ export const mockFileRequestWithoutMetadata = { permissions: { can_download: true, can_preview: true, - can_upload: false, + can_upload: true, can_comment: true, can_rename: false, can_delete: false,