Skip to content

Commit

Permalink
Merge branch 'master' into box-ai-panel-preview-header
Browse files Browse the repository at this point in the history
  • Loading branch information
DanilaRubleuski authored Oct 16, 2024
2 parents c85ec99 + f4a562d commit 6a2d980
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 15 deletions.
4 changes: 2 additions & 2 deletions src/api/Metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -1111,10 +1111,10 @@ class Metadata extends File {
}

const url = this.getMetadataOptionsUrl(scope, templateKey, fieldKey);
const { marker, searchInput, signal } = options;
const { marker, searchInput: query_text, signal } = options;
const params = {
...(marker ? { marker } : {}),
...(searchInput ? { searchInput } : {}),
...(query_text ? { query_text } : {}),
...(level || level === 0 ? { level } : {}),
};

Expand Down
2 changes: 1 addition & 1 deletion src/api/__tests__/Metadata.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2929,7 +2929,7 @@ describe('api/Metadata', () => {
id: 'file_id',
params: {
marker: 'current_marker',
searchInput: 'search_term',
query_text: 'search_term',
level: 0,
},
});
Expand Down
4 changes: 3 additions & 1 deletion src/common/types/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
FIELD_TYPE_FLOAT,
FIELD_TYPE_MULTISELECT,
FIELD_TYPE_STRING,
FIELD_TYPE_TAXONOMY,
} from '../../features/metadata-instance-fields/constants';
import type { SkillCards } from './skills';

Expand All @@ -13,7 +14,8 @@ type MetadataFieldType =
| typeof FIELD_TYPE_ENUM
| typeof FIELD_TYPE_FLOAT
| typeof FIELD_TYPE_MULTISELECT
| typeof FIELD_TYPE_STRING;
| typeof FIELD_TYPE_STRING
| typeof FIELD_TYPE_TAXONOMY;

type MetadataTemplateFieldOption = {
id?: string,
Expand Down
13 changes: 10 additions & 3 deletions src/elements/content-sidebar/MetadataInstanceEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import {
type MetadataTemplateInstance,
type FetcherResponse,
type BaseOptionType,
type PaginationQueryInput,
} from '@box/metadata-editor';
import React from 'react';

const noopTaxonomyFetcher = () => Promise.resolve({ options: [] } satisfies FetcherResponse<BaseOptionType>);

export interface MetadataInstanceEditorProps {
areAiSuggestionsAvailable: boolean;
isBoxAiSuggestionsEnabled: boolean;
Expand All @@ -20,6 +19,13 @@ export interface MetadataInstanceEditorProps {
onDiscardUnsavedChanges: () => void;
onSubmit: (values: FormValues, operations: JSONPatchOperations) => Promise<void>;
setIsUnsavedChangesModalOpen: (isUnsavedChangesModalOpen: boolean) => void;
taxonomyOptionsFetcher: (
scope: string,
templateKey: string,
fieldKey: string,
level: number,
options: PaginationQueryInput,
) => Promise<FetcherResponse<BaseOptionType>>;
template: MetadataTemplateInstance;
}

Expand All @@ -33,6 +39,7 @@ const MetadataInstanceEditor: React.FC<MetadataInstanceEditorProps> = ({
onDiscardUnsavedChanges,
onSubmit,
setIsUnsavedChangesModalOpen,
taxonomyOptionsFetcher,
template,
}) => {
return (
Expand All @@ -47,7 +54,7 @@ const MetadataInstanceEditor: React.FC<MetadataInstanceEditorProps> = ({
onSubmit={onSubmit}
selectedTemplateInstance={template}
setIsUnsavedChangesModalOpen={setIsUnsavedChangesModalOpen}
taxonomyOptionsFetcher={noopTaxonomyFetcher}
taxonomyOptionsFetcher={taxonomyOptionsFetcher}
/>
);
};
Expand Down
11 changes: 11 additions & 0 deletions src/elements/content-sidebar/MetadataSidebarRedesign.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
type JSONPatchOperations,
type MetadataTemplate,
type MetadataTemplateInstance,
type PaginationQueryInput,
} from '@box/metadata-editor';

import API from '../../api';
Expand All @@ -35,6 +36,7 @@ import './MetadataSidebarRedesign.scss';
import MetadataInstanceEditor from './MetadataInstanceEditor';
import { convertTemplateToTemplateInstance } from './utils/convertTemplateToTemplateInstance';
import { isExtensionSupportedForMetadataSuggestions } from './utils/isExtensionSupportedForMetadataSuggestions';
import { metadataTaxonomyFetcher } from './fetchers/metadataTaxonomyFetcher';

const MARK_NAME_JS_READY = `${ORIGIN_METADATA_SIDEBAR_REDESIGN}_${EVENT_JS_READY}`;

Expand Down Expand Up @@ -169,6 +171,14 @@ function MetadataSidebarRedesign({ api, elementId, fileId, onError, isFeatureEna
const showList = !showEditor && templateInstances.length > 0 && !editingTemplate;
const areAiSuggestionsAvailable = isExtensionSupportedForMetadataSuggestions(file?.extension ?? '');

const taxonomyOptionsFetcher = async (
scope: string,
templateKey: string,
fieldKey: string,
level: number,
options: PaginationQueryInput,
) => metadataTaxonomyFetcher(api, fileId, scope, templateKey, fieldKey, level, options);

return (
<SidebarContent
actions={metadataDropdown}
Expand Down Expand Up @@ -198,6 +208,7 @@ function MetadataSidebarRedesign({ api, elementId, fileId, onError, isFeatureEna
onDiscardUnsavedChanges={handleDiscardUnsavedChanges}
onSubmit={handleSubmit}
setIsUnsavedChangesModalOpen={setIsUnsavedChangesModalOpen}
taxonomyOptionsFetcher={taxonomyOptionsFetcher}
template={editingTemplate}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import React from 'react';
import { AutofillContextProvider, type MetadataTemplateInstance } from '@box/metadata-editor';
import {
AutofillContextProvider,
type MetadataTemplateField,
type MetadataTemplateInstance,
} from '@box/metadata-editor';
import userEvent from '@testing-library/user-event';
import { TooltipProvider } from '@box/blueprint-web';
import { IntlProvider } from 'react-intl';
Expand Down Expand Up @@ -75,6 +79,7 @@ describe('MetadataInstanceEditor', () => {
onDiscardUnsavedChanges: mockOnDiscardUnsavedChanges,
onSubmit: jest.fn(),
setIsUnsavedChangesModalOpen: mockSetIsUnsavedChangesModalOpen,
taxonomyOptionsFetcher: jest.fn(),
template: mockMetadataTemplate,
};

Expand Down Expand Up @@ -153,4 +158,44 @@ describe('MetadataInstanceEditor', () => {

expect(mockOnDiscardUnsavedChanges).toHaveBeenCalled();
});

test('should call taxonomyOptionsFetcher on metadata taxonomy field search', async () => {
const taxonomyField: MetadataTemplateField = {
type: 'taxonomy',
key: 'States',
displayName: 'States',
description: 'State locations',
hidden: false,
id: '2',
taxonomyKey: 'geography',
taxonomyId: '1',
optionsRules: {
multiSelect: true,
selectableLevels: [1],
},
};

const template: MetadataTemplateInstance = {
...mockCustomMetadataTemplateWithField,
fields: [...mockCustomMetadataTemplateWithField.fields, taxonomyField],
};

const props: MetadataInstanceEditorProps = {
...defaultProps,
template,
};

const { getByRole } = renderWithAutofill(<MetadataInstanceEditor {...props} />);
const combobox = getByRole('combobox', { name: 'States' });

await userEvent.type(combobox, 'A');

expect(props.taxonomyOptionsFetcher).toHaveBeenCalledWith(
template.scope,
template.templateKey,
taxonomyField.key,
taxonomyField.optionsRules.selectableLevels[0],
{ marker: null, searchInput: 'A', signal: expect.anything() },
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type { PaginationQueryInput } from '@box/metadata-editor';
import { metadataTaxonomyFetcher } from '../fetchers/metadataTaxonomyFetcher';
import type API from '../../../api';

describe('metadataTaxonomyFetcher', () => {
let apiMock: jest.Mocked<API>;
const fileId = '12345';
const scope = 'global';
const templateKey = 'template_123';
const fieldKey = 'field_abc';
const level = 1;
const options: PaginationQueryInput = { marker: 'marker_1' };

beforeEach(() => {
apiMock = {
getMetadataAPI: jest.fn().mockReturnValue({
getMetadataOptions: jest.fn(),
}),
};
});

test('should fetch metadata options and return formatted data', async () => {
const mockMetadataOptions = {
entries: [
{ id: 'opt1', display_name: 'Option 1' },
{ id: 'opt2', display_name: 'Option 2' },
],
};

apiMock.getMetadataAPI(false).getMetadataOptions.mockResolvedValue(mockMetadataOptions);

const result = await metadataTaxonomyFetcher(apiMock, fileId, scope, templateKey, fieldKey, level, options);

const expectedResult = {
options: [
{ value: 'opt1', displayValue: 'Option 1' },
{ value: 'opt2', displayValue: 'Option 2' },
],
marker: 'marker_1',
};

expect(apiMock.getMetadataAPI).toHaveBeenCalledWith(false);
expect(apiMock.getMetadataAPI(false).getMetadataOptions).toHaveBeenCalledWith(
fileId,
scope,
templateKey,
fieldKey,
level,
options,
);
expect(result).toEqual(expectedResult);
});

test('should handle empty entries array', async () => {
const mockMetadataOptions = {
entries: [],
};

apiMock.getMetadataAPI(false).getMetadataOptions.mockResolvedValue(mockMetadataOptions);

const result = await metadataTaxonomyFetcher(apiMock, fileId, scope, templateKey, fieldKey, level, options);

const expectedResult = {
options: [],
marker: 'marker_1',
};

expect(result).toEqual(expectedResult);
});

test('should set marker to null if not provided in options', async () => {
const mockMetadataOptions = {
entries: [{ id: 'opt1', display_name: 'Option 1' }],
};

apiMock.getMetadataAPI(false).getMetadataOptions.mockResolvedValue(mockMetadataOptions);

const result = await metadataTaxonomyFetcher(apiMock, fileId, scope, templateKey, fieldKey, level, {});

const expectedResult = {
options: [{ value: 'opt1', displayValue: 'Option 1' }],
marker: null,
};

expect(result).toEqual(expectedResult);
});

test('should throw an error if getMetadataOptions fails', async () => {
const error = new Error('API Error');
apiMock.getMetadataAPI(false).getMetadataOptions.mockRejectedValue(error);

await expect(
metadataTaxonomyFetcher(apiMock, fileId, scope, templateKey, fieldKey, level, options),
).rejects.toThrow('API Error');
});
});
26 changes: 26 additions & 0 deletions src/elements/content-sidebar/fetchers/metadataTaxonomyFetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { PaginationQueryInput } from '@box/metadata-editor';
import type API from '../../../api';
import type { MetadataOptionEntry } from '../../../common/types/metadata';

export const metadataTaxonomyFetcher = async (
api: API,
fileId: string,
scope: string,
templateKey: string,
fieldKey: string,
level: number,
options: PaginationQueryInput,
) => {
const metadataOptions = await api
.getMetadataAPI(false)
.getMetadataOptions(fileId, scope, templateKey, fieldKey, level, options);
const { marker = null } = options;

return {
options: metadataOptions.entries.map((metadataOption: MetadataOptionEntry) => ({
value: metadataOption.id,
displayValue: metadataOption.display_name,
})),
marker,
};
};
2 changes: 2 additions & 0 deletions src/elements/content-uploader/UploadState.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
text-align: center;

svg {
width: 130px;
height: 130px;
margin-bottom: 10px;
}

Expand Down
12 changes: 5 additions & 7 deletions src/elements/content-uploader/UploadState.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import * as React from 'react';
import classNames from 'classnames';
import { useIntl } from 'react-intl';
import { HatWand } from '@box/blueprint-web-assets/illustrations/Medium';
import { DiscoDanceParty, HatWand, UploadCloud } from '@box/blueprint-web-assets/illustrations/Medium';

import UploadEmptyState from '../../icons/states/UploadEmptyState';
import UploadSuccessState from '../../icons/states/UploadSuccessState';
import UploadStateContent from './UploadStateContent';
import type { View } from '../../common/types/core';

Expand Down Expand Up @@ -38,11 +36,11 @@ const UploadState = ({
let content;
switch (view) {
case VIEW_ERROR:
icon = <HatWand aria-label={formatMessage(messages.uploadErrorState)} height={126} width={130} />;
icon = <HatWand aria-label={formatMessage(messages.uploadErrorState)} />;
content = <UploadStateContent message={formatMessage(messages.uploadError)} />;
break;
case VIEW_UPLOAD_EMPTY:
icon = <UploadEmptyState title={formatMessage(messages.uploadEmptyState)} />;
icon = <UploadCloud aria-label={formatMessage(messages.uploadEmptyState)} />;
/* eslint-disable no-nested-ternary */
content =
canDrop && hasItems ? (
Expand All @@ -68,11 +66,11 @@ const UploadState = ({
/* eslint-enable no-nested-ternary */
break;
case VIEW_UPLOAD_IN_PROGRESS:
icon = <UploadEmptyState title={formatMessage(messages.uploadEmptyState)} />;
icon = <UploadCloud aria-label={formatMessage(messages.uploadEmptyState)} />;
content = <UploadStateContent message={formatMessage(messages.uploadInProgress)} />;
break;
case VIEW_UPLOAD_SUCCESS:
icon = <UploadSuccessState title={formatMessage(messages.uploadSuccessState)} />;
icon = <DiscoDanceParty aria-label={formatMessage(messages.uploadSuccessState)} />;
content = (
<UploadStateContent
fileInputLabel={formatMessage(messages.uploadSuccessFileInput)}
Expand Down
1 change: 1 addition & 0 deletions src/features/metadata-instance-fields/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export const FIELD_TYPE_INTEGER: 'integer' = 'integer';
export const FIELD_TYPE_FLOAT: 'float' = 'float';
export const FIELD_TYPE_MULTISELECT: 'multiSelect' = 'multiSelect';
export const FIELD_TYPE_STRING: 'string' = 'string';
export const FIELD_TYPE_TAXONOMY: 'taxonomy' = 'taxonomy';

0 comments on commit 6a2d980

Please sign in to comment.