Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(metadata-sidebar): Add handler for Autofill button #3700

Merged
merged 7 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -306,7 +306,7 @@
"@box/blueprint-web-assets": "^4.21.0",
"@box/box-ai-content-answers": "^0.50.5",
"@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",
Expand Down
1 change: 1 addition & 0 deletions src/api/Intelligence.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class Intelligence extends Base {
suggestionsResponse = await this.xhr.post({
url,
data: request,
id: `file_${request.items[0].id}`,
Copy link
Contributor

@tjuanitas tjuanitas Oct 10, 2024

Choose a reason for hiding this comment

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

i see we do this in the ask method as well but how important is this id field? if items has multiple files would this cause conflicts or unexpected behavior since we always look at the first index?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

id is used internally by post to get auth token, it can by an array, but for this endpoint we only use one file. That is whybI take first element.

});
} catch (e) {
const { status } = e;
Expand Down
4 changes: 4 additions & 0 deletions src/api/__tests__/Intelligence.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ describe('api/Intelligence', () => {

describe('extractStructured()', () => {
const request = {
items: [{ id: '123', type: 'file' }],
metadata_template: {
type: 'metadata_template',
scope: 'global',
Expand All @@ -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,
});
});
Expand All @@ -143,6 +145,7 @@ describe('api/Intelligence', () => {
expect(intelligence.xhr.post).toHaveBeenCalledWith({
url: `${intelligence.getBaseApiUrl()}/ai/extract_structured`,
data: request,
id: 'file_123',
});
});

Expand All @@ -161,6 +164,7 @@ describe('api/Intelligence', () => {
expect(intelligence.xhr.post).toHaveBeenCalledWith({
url: `${intelligence.getBaseApiUrl()}/ai/extract_structured`,
data: request,
id: 'file_123',
});
});
});
Expand Down
45 changes: 19 additions & 26 deletions src/elements/content-sidebar/MetadataInstanceEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import {
AutofillContextProvider,
AutofillContextProviderProps,
MetadataInstanceForm,
withApiWrapper,
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<BaseOptionType>);

export interface MetadataInstanceEditorProps {
areAiSuggestionsAvailable: boolean;
fetchSuggestions: AutofillContextProviderProps['fetchSuggestions'];
isBoxAiSuggestionsEnabled: boolean;
isDeleteButtonDisabled: boolean;
isUnsavedChangesModalOpen: boolean;
Expand All @@ -22,9 +24,8 @@ export interface MetadataInstanceEditorProps {
template: MetadataTemplateInstance;
}

const MetadataInstanceEditor: React.FC<MetadataInstanceEditorProps> = ({
export const MetadataInstanceEditor: React.FC<MetadataInstanceEditorProps> = ({
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: if we're exporting for unit tests, we usually add the export at bottom with something like:

export { MetadataInstanceEditor as MetadataInstanceEditorComponent };

example:

export { Sidebar as SidebarComponent };
export default flow([withCurrentUser, withFeatureConsumer, withRouter])(Sidebar);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We actually don't need the withApiWrapper so I will restore the old way we were exporting here.

areAiSuggestionsAvailable,
fetchSuggestions,
isBoxAiSuggestionsEnabled,
isDeleteButtonDisabled,
isUnsavedChangesModalOpen,
Expand All @@ -35,29 +36,21 @@ const MetadataInstanceEditor: React.FC<MetadataInstanceEditorProps> = ({
setIsUnsavedChangesModalOpen,
template,
}) => {
const handleCancel = () => {
onCancel();
};

return (
<AutofillContextProvider
fetchSuggestions={fetchSuggestions}
<MetadataInstanceForm
areAiSuggestionsAvailable={areAiSuggestionsAvailable}
isAiSuggestionsFeatureEnabled={isBoxAiSuggestionsEnabled}
>
<MetadataInstanceForm
areAiSuggestionsAvailable={areAiSuggestionsAvailable}
isAiSuggestionsFeatureEnabled={isBoxAiSuggestionsEnabled}
isDeleteButtonDisabled={isDeleteButtonDisabled}
isUnsavedChangesModalOpen={isUnsavedChangesModalOpen}
onCancel={handleCancel}
onDelete={onDelete}
onDiscardUnsavedChanges={onDiscardUnsavedChanges}
onSubmit={onSubmit}
selectedTemplateInstance={template}
setIsUnsavedChangesModalOpen={setIsUnsavedChangesModalOpen}
/>
</AutofillContextProvider>
isDeleteButtonDisabled={isDeleteButtonDisabled}
isUnsavedChangesModalOpen={isUnsavedChangesModalOpen}
onCancel={onCancel}
onDelete={onDelete}
onDiscardUnsavedChanges={onDiscardUnsavedChanges}
onSubmit={onSubmit}
selectedTemplateInstance={template}
setIsUnsavedChangesModalOpen={setIsUnsavedChangesModalOpen}
taxonomyOptionsFetcher={noopTaxonomyFetcher}
/>
);
};

export default MetadataInstanceEditor;
export default withApiWrapper(MetadataInstanceEditor);
81 changes: 36 additions & 45 deletions src/elements/content-sidebar/MetadataSidebarRedesign.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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<string, unknown>) => void;
}

export interface MetadataSidebarRedesignProps extends PropsWithoutContext, ErrorContextProps, WithLoggerProps {
Expand All @@ -67,6 +60,7 @@ export interface MetadataSidebarRedesignProps extends PropsWithoutContext, Error

function MetadataSidebarRedesign({ api, elementId, fileId, onError, isFeatureEnabled }: MetadataSidebarRedesignProps) {
const {
extractSuggestions,
file,
handleCreateMetadataInstance,
handleDeleteMetadataInstance,
Expand Down Expand Up @@ -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<MetadataInstanceEditorProps['fetchSuggestions']>(
async (templateKey, fields) => {
// should use getIntelligenceAPI().extractStructured
return fields;
},
[],
);

return (
<SidebarContent
Expand All @@ -196,32 +183,36 @@ function MetadataSidebarRedesign({ api, elementId, fileId, onError, isFeatureEna
{showEmptyState && (
<MetadataEmptyState level={'file'} isBoxAiSuggestionsFeatureEnabled={isBoxAiSuggestionsEnabled} />
)}
{editingTemplate && (
<MetadataInstanceEditor
areAiSuggestionsAvailable={areAiSuggestionsAvailable}
fetchSuggestions={fetchSuggestions}
isBoxAiSuggestionsEnabled={isBoxAiSuggestionsEnabled}
isDeleteButtonDisabled={isDeleteButtonDisabled}
isUnsavedChangesModalOpen={isUnsavedChangesModalOpen}
onCancel={handleCancel}
onDelete={handleDeleteInstance}
onDiscardUnsavedChanges={handleDiscardUnsavedChanges}
onSubmit={handleSubmit}
setIsUnsavedChangesModalOpen={setIsUnsavedChangesModalOpen}
template={editingTemplate}
/>
)}
{showList && (
<MetadataInstanceList
isAiSuggestionsFeatureEnabled={isBoxAiSuggestionsEnabled}
onEdit={templateInstance => {
setEditingTemplate(templateInstance);
setIsDeleteButtonDisabled(false);
}}
onEditWithAutofill={noop}
templateInstances={templateInstances}
/>
)}
<AutofillContextProvider
fetchSuggestions={extractSuggestions}
isAiSuggestionsFeatureEnabled={isBoxAiSuggestionsEnabled}
>
{editingTemplate && (
<MetadataInstanceEditor
areAiSuggestionsAvailable={areAiSuggestionsAvailable}
isBoxAiSuggestionsEnabled={isBoxAiSuggestionsEnabled}
isDeleteButtonDisabled={isDeleteButtonDisabled}
isUnsavedChangesModalOpen={isUnsavedChangesModalOpen}
onCancel={handleCancel}
onDelete={handleDeleteInstance}
onDiscardUnsavedChanges={handleDiscardUnsavedChanges}
onSubmit={handleSubmit}
setIsUnsavedChangesModalOpen={setIsUnsavedChangesModalOpen}
template={editingTemplate}
/>
)}
{showList && (
<MetadataInstanceList
areAiSuggestionsAvailable={areAiSuggestionsAvailable}
isAiSuggestionsFeatureEnabled={isBoxAiSuggestionsEnabled}
onEdit={templateInstance => {
setEditingTemplate(templateInstance);
setIsDeleteButtonDisabled(false);
}}
templateInstances={templateInstances}
/>
)}
</AutofillContextProvider>
</div>
</SidebarContent>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { type MetadataTemplateInstance } from '@box/metadata-editor';
import userEvent from '@testing-library/user-event';
import { screen, render } from '../../../test-utils/testing-library';
import MetadataInstanceEditor, { MetadataInstanceEditorProps } from '../MetadataInstanceEditor';
import { MetadataInstanceEditor, MetadataInstanceEditorProps } from '../MetadataInstanceEditor';

const mockOnCancel = jest.fn();
const mockOnDiscardUnsavedChanges = jest.fn();
Expand Down Expand Up @@ -50,7 +50,6 @@ describe('MetadataInstanceEditor', () => {

const defaultProps: MetadataInstanceEditorProps = {
areAiSuggestionsAvailable: true,
fetchSuggestions: jest.fn(),
isBoxAiSuggestionsEnabled: true,
isDeleteButtonDisabled: false,
isUnsavedChangesModalOpen: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
Expand Down Expand Up @@ -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: [],
Expand All @@ -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(),
Expand Down Expand Up @@ -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!'),
Expand All @@ -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,
Expand Down
Loading
Loading