From 95735e0291f233d173c17a19304119c83e51723d Mon Sep 17 00:00:00 2001 From: Dawid Jankowiak Date: Thu, 10 Oct 2024 23:47:25 +0200 Subject: [PATCH] feat(metadata-sidebar): Add handler for Autofill button (#3700) * feat(metadata-sidebar): Add handler for Autofill button * feat(metadata-sidebar): Handle Error by rethrowing them to host app * feat(metadata-sidebar): Add error handling And bump metadata-editor package Wrap MetadataInstanceList and MetadataInstanceForm in Autofill provider Co-authored-by: Wiola (wpiesiak) * feat(metadata-sidebar): Remove unnecessary wait function * feat(metadata-sidebar): Restore old export in MetadataInstanceEditor * feat(metadata-sidebar): Move AutofillContextProvider out of test-utils --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- package.json | 4 +- src/api/Intelligence.js | 1 + src/api/__tests__/Intelligence.test.js | 4 + .../MetadataInstanceEditor.tsx | 40 ++++----- .../MetadataSidebarRedesign.tsx | 81 +++++++++---------- .../__tests__/MetadataInstanceEditor.test.tsx | 46 +++++++---- .../MetadataSidebarRedesign.test.tsx | 15 +++- .../hooks/useSidebarMetadataFetcher.ts | 52 +++++++++++- ...MetadataSidebarRedesign-visual.stories.tsx | 2 +- src/test-utils/testing-library.tsx | 31 +++---- yarn.lock | 8 +- 11 files changed, 169 insertions(+), 115 deletions(-) diff --git a/package.json b/package.json index 1ad4ba16d8..f3e15db81c 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "@box/cldr-data": "^34.2.0", "@box/frontend": "^10.0.0", "@box/languages": "^1.0.0", - "@box/metadata-editor": "^0.61.1", + "@box/metadata-editor": "^0.65.0", "@box/react-virtualized": "9.22.3-rc-box.9", "@cfaester/enzyme-adapter-react-18": "^0.8.0", "@chromatic-com/storybook": "^1.6.1", @@ -306,7 +306,7 @@ "@box/blueprint-web-assets": "^4.21.0", "@box/box-ai-content-answers": "^0.57.1", "@box/cldr-data": ">=34.2.0", - "@box/metadata-editor": "^0.61.1", + "@box/metadata-editor": "^0.65.0", "@box/react-virtualized": "9.22.3-rc-box.9", "@hapi/address": "^2.1.4", "axios": "^0.25.0", diff --git a/src/api/Intelligence.js b/src/api/Intelligence.js index 06d8ef3c17..a609db21e8 100644 --- a/src/api/Intelligence.js +++ b/src/api/Intelligence.js @@ -75,6 +75,7 @@ class Intelligence extends Base { suggestionsResponse = await this.xhr.post({ url, data: request, + id: `file_${request.items[0].id}`, }); } catch (e) { const { status } = e; diff --git a/src/api/__tests__/Intelligence.test.js b/src/api/__tests__/Intelligence.test.js index 94a6ad37c9..452598f863 100644 --- a/src/api/__tests__/Intelligence.test.js +++ b/src/api/__tests__/Intelligence.test.js @@ -102,6 +102,7 @@ describe('api/Intelligence', () => { describe('extractStructured()', () => { const request = { + items: [{ id: '123', type: 'file' }], metadata_template: { type: 'metadata_template', scope: 'global', @@ -124,6 +125,7 @@ describe('api/Intelligence', () => { expect(suggestions).toEqual(suggestionsFromServer); expect(intelligence.xhr.post).toHaveBeenCalledWith({ url: `${intelligence.getBaseApiUrl()}/ai/extract_structured`, + id: 'file_123', data: request, }); }); @@ -143,6 +145,7 @@ describe('api/Intelligence', () => { expect(intelligence.xhr.post).toHaveBeenCalledWith({ url: `${intelligence.getBaseApiUrl()}/ai/extract_structured`, data: request, + id: 'file_123', }); }); @@ -161,6 +164,7 @@ describe('api/Intelligence', () => { expect(intelligence.xhr.post).toHaveBeenCalledWith({ url: `${intelligence.getBaseApiUrl()}/ai/extract_structured`, data: request, + id: 'file_123', }); }); }); diff --git a/src/elements/content-sidebar/MetadataInstanceEditor.tsx b/src/elements/content-sidebar/MetadataInstanceEditor.tsx index d945a4d33d..46d4e2947f 100644 --- a/src/elements/content-sidebar/MetadataInstanceEditor.tsx +++ b/src/elements/content-sidebar/MetadataInstanceEditor.tsx @@ -1,16 +1,17 @@ import { - AutofillContextProvider, - AutofillContextProviderProps, MetadataInstanceForm, type FormValues, type JSONPatchOperations, type MetadataTemplateInstance, + type FetcherResponse, + type BaseOptionType, } from '@box/metadata-editor'; import React from 'react'; +const noopTaxonomyFetcher = () => Promise.resolve({ options: [] } satisfies FetcherResponse); + export interface MetadataInstanceEditorProps { areAiSuggestionsAvailable: boolean; - fetchSuggestions: AutofillContextProviderProps['fetchSuggestions']; isBoxAiSuggestionsEnabled: boolean; isDeleteButtonDisabled: boolean; isUnsavedChangesModalOpen: boolean; @@ -24,7 +25,6 @@ export interface MetadataInstanceEditorProps { const MetadataInstanceEditor: React.FC = ({ areAiSuggestionsAvailable, - fetchSuggestions, isBoxAiSuggestionsEnabled, isDeleteButtonDisabled, isUnsavedChangesModalOpen, @@ -35,28 +35,20 @@ const MetadataInstanceEditor: React.FC = ({ setIsUnsavedChangesModalOpen, template, }) => { - const handleCancel = () => { - onCancel(); - }; - return ( - - - + isDeleteButtonDisabled={isDeleteButtonDisabled} + isUnsavedChangesModalOpen={isUnsavedChangesModalOpen} + onCancel={onCancel} + onDelete={onDelete} + onDiscardUnsavedChanges={onDiscardUnsavedChanges} + onSubmit={onSubmit} + selectedTemplateInstance={template} + setIsUnsavedChangesModalOpen={setIsUnsavedChangesModalOpen} + taxonomyOptionsFetcher={noopTaxonomyFetcher} + /> ); }; diff --git a/src/elements/content-sidebar/MetadataSidebarRedesign.tsx b/src/elements/content-sidebar/MetadataSidebarRedesign.tsx index c53c852569..d3097b832d 100644 --- a/src/elements/content-sidebar/MetadataSidebarRedesign.tsx +++ b/src/elements/content-sidebar/MetadataSidebarRedesign.tsx @@ -8,33 +8,31 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { InlineError, LoadingIndicator } from '@box/blueprint-web'; import { AddMetadataTemplateDropdown, + AutofillContextProvider, MetadataEmptyState, MetadataInstanceList, type FormValues, type JSONPatchOperations, - type MetadataTemplateInstance, type MetadataTemplate, + type MetadataTemplateInstance, } from '@box/metadata-editor'; -import noop from 'lodash/noop'; import API from '../../api'; import SidebarContent from './SidebarContent'; import { withAPIContext } from '../common/api-context'; import { withErrorBoundary } from '../common/error-boundary'; import { withLogger } from '../common/logger'; +import { useFeatureEnabled } from '../common/feature-checking'; import { ORIGIN_METADATA_SIDEBAR_REDESIGN, SIDEBAR_VIEW_METADATA } from '../../constants'; import { EVENT_JS_READY } from '../common/logger/constants'; -import { useFeatureEnabled } from '../common/feature-checking'; import { mark } from '../../utils/performance'; import useSidebarMetadataFetcher, { STATUS } from './hooks/useSidebarMetadataFetcher'; -import { type ElementsXhrError } from '../../common/types/api'; -import { type ElementOrigin } from '../common/flowTypes'; import { type WithLoggerProps } from '../../common/types/logging'; import messages from '../common/messages'; import './MetadataSidebarRedesign.scss'; -import MetadataInstanceEditor, { MetadataInstanceEditorProps } from './MetadataInstanceEditor'; +import MetadataInstanceEditor from './MetadataInstanceEditor'; import { convertTemplateToTemplateInstance } from './utils/convertTemplateToTemplateInstance'; import { isExtensionSupportedForMetadataSuggestions } from './utils/isExtensionSupportedForMetadataSuggestions'; @@ -52,13 +50,8 @@ interface PropsWithoutContext extends ExternalProps { hasSidebarInitialized?: boolean; } -interface ContextInfo { - isErrorDisplayed: boolean; - error: ElementsXhrError | Error; -} - export interface ErrorContextProps { - onError: (error: ElementsXhrError | Error, code: string, contextInfo?: ContextInfo, origin?: ElementOrigin) => void; + onError: (error: Error, code: string, contextInfo?: Record) => void; } export interface MetadataSidebarRedesignProps extends PropsWithoutContext, ErrorContextProps, WithLoggerProps { @@ -67,6 +60,7 @@ export interface MetadataSidebarRedesignProps extends PropsWithoutContext, Error function MetadataSidebarRedesign({ api, elementId, fileId, onError, isFeatureEnabled }: MetadataSidebarRedesignProps) { const { + extractSuggestions, file, handleCreateMetadataInstance, handleDeleteMetadataInstance, @@ -174,13 +168,6 @@ function MetadataSidebarRedesign({ api, elementId, fileId, onError, isFeatureEna const showEditor = !showEmptyState && editingTemplate; const showList = !showEditor && templateInstances.length > 0 && !editingTemplate; const areAiSuggestionsAvailable = isExtensionSupportedForMetadataSuggestions(file?.extension ?? ''); - const fetchSuggestions = React.useCallback( - async (templateKey, fields) => { - // should use getIntelligenceAPI().extractStructured - return fields; - }, - [], - ); return ( )} - {editingTemplate && ( - - )} - {showList && ( - { - setEditingTemplate(templateInstance); - setIsDeleteButtonDisabled(false); - }} - onEditWithAutofill={noop} - templateInstances={templateInstances} - /> - )} + + {editingTemplate && ( + + )} + {showList && ( + { + setEditingTemplate(templateInstance); + setIsDeleteButtonDisabled(false); + }} + templateInstances={templateInstances} + /> + )} + ); diff --git a/src/elements/content-sidebar/__tests__/MetadataInstanceEditor.test.tsx b/src/elements/content-sidebar/__tests__/MetadataInstanceEditor.test.tsx index 86c8d3af1a..f7aef558bc 100644 --- a/src/elements/content-sidebar/__tests__/MetadataInstanceEditor.test.tsx +++ b/src/elements/content-sidebar/__tests__/MetadataInstanceEditor.test.tsx @@ -1,13 +1,30 @@ import React from 'react'; -import { type MetadataTemplateInstance } from '@box/metadata-editor'; +import { AutofillContextProvider, type MetadataTemplateInstance } from '@box/metadata-editor'; import userEvent from '@testing-library/user-event'; +import { TooltipProvider } from '@box/blueprint-web'; +import { IntlProvider } from 'react-intl'; import { screen, render } from '../../../test-utils/testing-library'; import MetadataInstanceEditor, { MetadataInstanceEditorProps } from '../MetadataInstanceEditor'; +import { FeatureProvider } from '../../common/feature-checking'; const mockOnCancel = jest.fn(); const mockOnDiscardUnsavedChanges = jest.fn(); const mockSetIsUnsavedChangesModalOpen = jest.fn(); +jest.unmock('react-intl'); + +const wrapper = ({ children }) => ( + Promise.resolve([])} isAiSuggestionsFeatureEnabled> + + + {children} + + + +); + +const renderWithAutofill = element => render(element, { wrapper }); + describe('MetadataInstanceEditor', () => { const mockCustomMetadataTemplate: MetadataTemplateInstance = { id: 'template-id', @@ -50,7 +67,6 @@ describe('MetadataInstanceEditor', () => { const defaultProps: MetadataInstanceEditorProps = { areAiSuggestionsAvailable: true, - fetchSuggestions: jest.fn(), isBoxAiSuggestionsEnabled: true, isDeleteButtonDisabled: false, isUnsavedChangesModalOpen: false, @@ -63,7 +79,7 @@ describe('MetadataInstanceEditor', () => { }; test('should render MetadataInstanceForm with correct props', () => { - render(); + renderWithAutofill(); const templateHeader = screen.getByText(mockMetadataTemplateInstance.displayName); expect(templateHeader).toBeInTheDocument(); @@ -71,7 +87,7 @@ describe('MetadataInstanceEditor', () => { test('should render MetadataInstanceForm with Custom Template', () => { const props = { ...defaultProps, template: mockCustomMetadataTemplate }; - render(); + renderWithAutofill(); const templateHeader = screen.getByText('Custom Metadata'); expect(templateHeader).toBeInTheDocument(); @@ -79,7 +95,7 @@ describe('MetadataInstanceEditor', () => { test('should render UnsavedChangesModal if isUnsavedChangesModalOpen is true', async () => { const props = { ...defaultProps, isUnsavedChangesModalOpen: true }; - const { findByText } = render(); + const { findByText } = renderWithAutofill(); const unsavedChangesModal = await findByText('Unsaved Changes'); expect(unsavedChangesModal).toBeInTheDocument(); @@ -87,14 +103,14 @@ describe('MetadataInstanceEditor', () => { test('should render MetadataInstanceForm with Delete button disabled', () => { const props = { ...defaultProps, isDeleteButtonDisabled: true }; - render(); + renderWithAutofill(); const deleteButton = screen.getByRole('button', { name: 'Delete' }); expect(deleteButton).toBeDisabled(); }); test('should render MetadataInstanceForm with Delete button enabled', () => { - render(); + renderWithAutofill(); const deleteButton = screen.getByRole('button', { name: 'Delete' }); expect(deleteButton).toBeEnabled(); @@ -102,9 +118,9 @@ describe('MetadataInstanceEditor', () => { test('Should call onCancel when canceling editing', async () => { const props: MetadataInstanceEditorProps = { ...defaultProps, template: mockCustomMetadataTemplate }; - const { findByRole } = render(); - const cancelButton = await findByRole('button', { name: 'Cancel' }); + renderWithAutofill(); + const cancelButton = await screen.findByRole('button', { name: 'Cancel' }); await userEvent.click(cancelButton); expect(mockOnCancel).toHaveBeenCalled(); @@ -115,9 +131,10 @@ describe('MetadataInstanceEditor', () => { ...defaultProps, template: mockCustomMetadataTemplateWithField, }; - const { rerender, findByRole, findByText } = render(); - const input = await findByRole('textbox'); - const cancelButton = await findByRole('button', { name: 'Cancel' }); + const { rerender } = renderWithAutofill(); + + const input = await screen.findByRole('textbox'); + const cancelButton = await screen.findByRole('button', { name: 'Cancel' }); await userEvent.type(input, 'Lorem ipsum dolor.'); await userEvent.click(cancelButton); @@ -126,10 +143,11 @@ describe('MetadataInstanceEditor', () => { expect(mockSetIsUnsavedChangesModalOpen).toHaveBeenCalledWith(true); rerender(); - const unsavedChangesModal = await findByText('Unsaved Changes'); + + const unsavedChangesModal = await screen.findByText('Unsaved Changes'); expect(unsavedChangesModal).toBeInTheDocument(); - const unsavedChangesModalDiscardButton = await findByRole('button', { name: 'Discard Changes' }); + const unsavedChangesModalDiscardButton = await screen.findByRole('button', { name: 'Discard Changes' }); await userEvent.click(unsavedChangesModalDiscardButton); diff --git a/src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx b/src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx index 45e97b1aae..bebe2dd24d 100644 --- a/src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx +++ b/src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx @@ -68,8 +68,9 @@ describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => { beforeEach(() => { mockUseSidebarMetadataFetcher.mockReturnValue({ - handleDeleteMetadataInstance: jest.fn(), + extractSuggestions: jest.fn(), handleCreateMetadataInstance: jest.fn(), + handleDeleteMetadataInstance: jest.fn(), handleUpdateMetadataInstance: jest.fn(), templates: mockTemplates, templateInstances: [], @@ -113,8 +114,9 @@ describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => { test('should render metadata sidebar with error', async () => { mockUseSidebarMetadataFetcher.mockReturnValue({ - handleDeleteMetadataInstance: jest.fn(), + extractSuggestions: jest.fn(), handleCreateMetadataInstance: jest.fn(), + handleDeleteMetadataInstance: jest.fn(), handleUpdateMetadataInstance: jest.fn(), templateInstances: [], templates: [], @@ -135,6 +137,7 @@ describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => { test('should render metadata sidebar with loading indicator', async () => { mockUseSidebarMetadataFetcher.mockReturnValue({ + extractSuggestions: jest.fn(), handleCreateMetadataInstance: jest.fn(), handleDeleteMetadataInstance: jest.fn(), handleUpdateMetadataInstance: jest.fn(), @@ -163,7 +166,10 @@ describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => { }); test('should correctly render empty state when AI feature is disabled', () => { - renderComponent({ isBoxAiSuggestionsEnabled: false }); + renderComponent( + { isBoxAiSuggestionsEnabled: false }, + { wrapperProps: { features: { 'metadata.aiSuggestions.enabled': false } } }, + ); expect(screen.getByRole('heading', { level: 2, name: 'Add Metadata Templates' })).toBeInTheDocument(); expect( screen.getByText('Add Metadata to your file to support business operations, workflows, and more!'), @@ -172,8 +178,9 @@ describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => { test('should render metadata instance list when templates are present', () => { mockUseSidebarMetadataFetcher.mockReturnValue({ - handleDeleteMetadataInstance: jest.fn(), + extractSuggestions: jest.fn(), handleCreateMetadataInstance: jest.fn(), + handleDeleteMetadataInstance: jest.fn(), handleUpdateMetadataInstance: jest.fn(), templateInstances: [mockCustomTemplateInstance], templates: mockTemplates, diff --git a/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts b/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts index 67b0f1961f..c50665ec75 100644 --- a/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts +++ b/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts @@ -1,11 +1,23 @@ import * as React from 'react'; import getProp from 'lodash/get'; import { type MessageDescriptor } from 'react-intl'; -import { type JSONPatchOperations, type MetadataTemplate, type MetadataTemplateInstance } from '@box/metadata-editor'; +import { + type JSONPatchOperations, + type MetadataTemplate, + type MetadataTemplateInstance, + type MetadataTemplateField, +} from '@box/metadata-editor'; +import isEmpty from 'lodash/isEmpty'; import API from '../../../api'; import { type ElementsXhrError } from '../../../common/types/api'; import { isUserCorrectableError } from '../../../utils/error'; -import { FIELD_IS_EXTERNALLY_OWNED, FIELD_PERMISSIONS, FIELD_PERMISSIONS_CAN_UPLOAD } from '../../../constants'; +import { + ERROR_CODE_EMPTY_METADATA_SUGGESTIONS, + ERROR_CODE_FETCH_METADATA_SUGGESTIONS, + FIELD_IS_EXTERNALLY_OWNED, + FIELD_PERMISSIONS_CAN_UPLOAD, + FIELD_PERMISSIONS, +} from '../../../constants'; import messages from '../../common/messages'; @@ -18,8 +30,10 @@ export enum STATUS { ERROR = 'error', SUCCESS = 'success', } + interface DataFetcher { errorMessage: MessageDescriptor | null; + extractSuggestions: (templateKey: string, fields: MetadataTemplateField[]) => Promise; file: BoxItem | null; handleCreateMetadataInstance: ( templateInstance: MetadataTemplateInstance, @@ -174,6 +188,39 @@ function useSidebarMetadataFetcher( [api, file, onApiError], ); + const extractSuggestions = React.useCallback( + async (templateKey: string, fields: MetadataTemplateField[]) => { + const aiAPI = api.getIntelligenceAPI(); + + let answer = {}; + try { + answer = await aiAPI.extractStructured({ + items: [{ id: file.id, type: file.type }], + fields, + }); + } catch (error) { + onError(error, ERROR_CODE_FETCH_METADATA_SUGGESTIONS, { showNotification: true }); + } + + if (isEmpty(answer)) { + const error = new Error('No suggestions found.'); + onError(error, ERROR_CODE_EMPTY_METADATA_SUGGESTIONS, { showNotification: true }); + } + + return fields.map(field => { + const value = answer[field.key]; + if (!value) { + return field; + } + return { + ...field, + aiSuggestion: value, + }; + }); + }, + [api, file, onError], + ); + React.useEffect(() => { if (status === STATUS.IDLE) { setStatus(STATUS.LOADING); @@ -185,6 +232,7 @@ function useSidebarMetadataFetcher( }, [api, fetchFileErrorCallback, fetchFileSuccessCallback, fileId, status]); return { + extractSuggestions, handleCreateMetadataInstance, handleDeleteMetadataInstance, handleUpdateMetadataInstance, diff --git a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx index d99de05ff8..d97bedba9b 100644 --- a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx +++ b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx @@ -206,7 +206,7 @@ export const MetadataInstanceEditorCancelChanges: StoryObj heading.textContent)).toEqual(expect.arrayContaining(['Custom Metadata'])); + expect(headlines.map(heading => heading.textContent)).toEqual(expect.arrayContaining(['My Template'])); // cancel editing - back to list view const cancelButton = await canvas.findByRole('button', { name: 'Cancel' }); diff --git a/src/test-utils/testing-library.tsx b/src/test-utils/testing-library.tsx index 172c0fb36e..bc5557617a 100644 --- a/src/test-utils/testing-library.tsx +++ b/src/test-utils/testing-library.tsx @@ -4,35 +4,28 @@ import { render, type RenderOptions } from '@testing-library/react'; // Data Providers import { TooltipProvider } from '@box/blueprint-web'; import { IntlProvider } from 'react-intl'; -import { AutofillContextProvider } from '@box/metadata-editor'; + import { FeatureProvider } from '../elements/common/feature-checking'; jest.unmock('react-intl'); -const Wrapper = ({ - children, - features = {}, - isAiSuggestionsFeatureEnabled = false, - fetchSuggestions = () => Promise.resolve([]), -}) => ( - - - - {children} - - - +const Wrapper = ({ children, features = {} }) => ( + + + {children} + + ); -type RenderConnectedOptions = Omit & { +type RenderConnectedOptions = RenderOptions & { wrapperProps?: Record; }; const renderConnected = (element, options: RenderConnectedOptions = {}) => - render(element, { wrapper: props => , ...options }); + render(element, { + wrapper: options.wrapper ? options.wrapper : props => , + ...options, + }); export * from '@testing-library/react'; export { renderConnected as render }; diff --git a/yarn.lock b/yarn.lock index 6ef7b5fc91..2f771e1051 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1569,10 +1569,10 @@ resolved "https://registry.yarnpkg.com/@box/languages/-/languages-1.1.2.tgz#cd4266b3da62da18560d881e10b429653186be29" integrity sha512-d64TGosx+KRmrLZj4CIyLp42LUiEbgBJ8n8cviMQwTJmfU0g+UwZqLjmQZR1j+Q9D64yV4xHzY9K1t5nInWWeQ== -"@box/metadata-editor@^0.61.1": - version "0.61.2" - resolved "https://registry.yarnpkg.com/@box/metadata-editor/-/metadata-editor-0.61.2.tgz#b8e66f3e822531ae57463cfdd8be89dd9cabdb21" - integrity sha512-Ot+cGU+benO0ChRrKWahvJmHMgxKTp4nuNr+VypnQtTt63hGCn1F8TbZ2aQNRtPikhoGCAiOwYspG8fRKJ/Fmw== +"@box/metadata-editor@^0.65.0": + version "0.65.0" + resolved "https://registry.yarnpkg.com/@box/metadata-editor/-/metadata-editor-0.65.0.tgz#ebacdf249d1106a250431d2b55cd670b9cf6adbd" + integrity sha512-MjYmCWUdqpjkpYZvOAmIKJ0gAtJ0DZ3U5yLS2IYCm1vtWoTtfji77PQ+4UeHHK/dmalDWn61FC3daicQx6Buxw== "@box/react-virtualized@9.22.3-rc-box.9": version "9.22.3-rc-box.9"