Skip to content

Commit f904c9f

Browse files
authored
feat(metadata-sidebar): handle success events (#3789)
* feat(metadata-sidebar): handle success events * feat(metadata-sidebar): fix const * feat(metadata-sidebar): remove redundant comments * feat(metadata-sidebar): fix failing tests * feat(metadata-sidebar): fix failing tests * feat(metadata-sidebar): remove redundant onSuccess call * feat(metadata-sidebar): add waitFor * feat(metadata-sidebar): act instead of waitFor
1 parent dfe1392 commit f904c9f

File tree

9 files changed

+69
-23
lines changed

9 files changed

+69
-23
lines changed

src/common/types/api.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ type ErrorContextProps = {
6868

6969
type ElementsErrorCallback = (e: ElementsXhrError, code: string, contextInfo?: Object) => void;
7070

71+
type ElementsSuccess = {
72+
code: string,
73+
showNotification: boolean,
74+
};
75+
7176
type APIOptions = {
7277
apiHost?: string,
7378
cache?: APICache,
@@ -92,6 +97,7 @@ export type {
9297
ElementOrigin,
9398
ElementsError,
9499
ElementsErrorCallback,
100+
ElementsSuccess,
95101
ElementsXhrError,
96102
ErrorContextProps,
97103
ErrorResponseData,

src/constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ export const METADATA_SCOPE_GLOBAL = 'global';
9191
export const METADATA_SCOPE_ENTERPRISE = 'enterprise';
9292
export const METADATA_TEMPLATE_FETCH_LIMIT = API_PAGE_LIMIT;
9393
export const METADATA_SUGGESTIONS_CONFIDENCE_EXPERIMENTAL = 'experimental';
94+
export const SUCCESS_CODE_UPDATE_METADATA_TEMPLATE_INSTANCE = 'update_metadata_template_instance_success';
95+
export const SUCCESS_CODE_DELETE_METADATA_TEMPLATE_INSTANCE = 'delete_metadata_template_instance_success';
9496

9597
/* ----------------------- Fields --------------------------- */
9698
export const FIELD_ID = 'id';

src/elements/content-sidebar/MetadataSidebarRedesign.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,14 @@ export interface ErrorContextProps {
6060
onError: (error: Error, code: string, contextInfo?: Record<string, unknown>) => void;
6161
}
6262

63+
export interface SuccessContextProps {
64+
onSuccess: (code: string, showNotification: boolean) => void;
65+
}
66+
6367
export interface MetadataSidebarRedesignProps
6468
extends PropsWithoutContext,
6569
ErrorContextProps,
70+
SuccessContextProps,
6671
WithLoggerProps,
6772
RouteComponentProps {
6873
api: API;
@@ -75,6 +80,7 @@ function MetadataSidebarRedesign({
7580
filteredTemplateIds = [],
7681
history,
7782
onError,
83+
onSuccess,
7884
isFeatureEnabled,
7985
}: MetadataSidebarRedesignProps) {
8086
const {
@@ -87,7 +93,7 @@ function MetadataSidebarRedesign({
8793
errorMessage,
8894
status,
8995
templateInstances,
90-
} = useSidebarMetadataFetcher(api, fileId, onError, isFeatureEnabled);
96+
} = useSidebarMetadataFetcher(api, fileId, onError, onSuccess, isFeatureEnabled);
9197

9298
const { formatMessage } = useIntl();
9399
const isBoxAiSuggestionsEnabled: boolean = useFeatureEnabled('metadata.aiSuggestions.enabled');

src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => {
9797
filteredTemplateIds: emptyFilteredTemplateIds,
9898
isFeatureEnabled: true,
9999
onError: jest.fn(),
100+
onSuccess: jest.fn(),
100101
...routeComponentProps,
101102
} satisfies MetadataSidebarRedesignProps;
102103

src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
ERROR_CODE_EMPTY_METADATA_SUGGESTIONS,
66
ERROR_CODE_FETCH_METADATA_SUGGESTIONS,
77
FIELD_PERMISSIONS_CAN_UPLOAD,
8+
SUCCESS_CODE_DELETE_METADATA_TEMPLATE_INSTANCE,
9+
SUCCESS_CODE_UPDATE_METADATA_TEMPLATE_INSTANCE,
810
} from '../../../constants';
911
import useSidebarMetadataFetcher, { STATUS } from '../hooks/useSidebarMetadataFetcher';
1012

@@ -117,13 +119,15 @@ const api = {
117119

118120
describe('useSidebarMetadataFetcher', () => {
119121
const onErrorMock = jest.fn();
122+
const onSuccessMock = jest.fn();
120123
const isFeatureEnabledMock = true;
121124

122125
const setupHook = (fileId = '123') =>
123-
renderHook(() => useSidebarMetadataFetcher(api, fileId, onErrorMock, isFeatureEnabledMock));
126+
renderHook(() => useSidebarMetadataFetcher(api, fileId, onErrorMock, onSuccessMock, isFeatureEnabledMock));
124127

125128
beforeEach(() => {
126129
onErrorMock.mockClear();
130+
onSuccessMock.mockClear();
127131
mockAPI.getFile.mockClear();
128132
mockAPI.getMetadata.mockClear();
129133
mockAPI.deleteMetadata.mockClear();
@@ -152,6 +156,7 @@ describe('useSidebarMetadataFetcher', () => {
152156

153157
expect(result.current.file).toBeUndefined();
154158
expect(result.current.errorMessage).toBe(messages.sidebarMetadataEditingErrorContent);
159+
expect(onSuccessMock).not.toHaveBeenCalled();
155160
expect(onErrorMock).toHaveBeenCalledWith(
156161
mockError,
157162
'file_fetch_error',
@@ -175,6 +180,7 @@ describe('useSidebarMetadataFetcher', () => {
175180

176181
expect(result.current.templates).toBeNull();
177182
expect(result.current.errorMessage).toBe(messages.sidebarMetadataFetchingErrorContent);
183+
expect(onSuccessMock).not.toHaveBeenCalled();
178184
expect(onErrorMock).toHaveBeenCalledWith(
179185
mockError,
180186
'metadata_fetch_error',
@@ -201,6 +207,7 @@ describe('useSidebarMetadataFetcher', () => {
201207
expect(result.current.templates).toEqual(mockTemplates);
202208
expect(result.current.status).toEqual(STATUS.SUCCESS);
203209
expect(result.current.errorMessage).toBeNull();
210+
expect(onSuccessMock).toHaveBeenCalledWith(SUCCESS_CODE_DELETE_METADATA_TEMPLATE_INSTANCE, true);
204211
});
205212

206213
test('should handle metadata instance removal error', async () => {
@@ -217,6 +224,7 @@ describe('useSidebarMetadataFetcher', () => {
217224
await waitFor(() => result.current.handleDeleteMetadataInstance(mockTemplateInstances[0]));
218225

219226
expect(result.current.status).toEqual(STATUS.ERROR);
227+
expect(onSuccessMock).not.toHaveBeenCalled();
220228
expect(onErrorMock).toHaveBeenCalledWith(
221229
mockError,
222230
'metadata_remove_error',
@@ -243,6 +251,7 @@ describe('useSidebarMetadataFetcher', () => {
243251
await waitFor(() => result.current.handleCreateMetadataInstance(newTemplateInstance, successCallback));
244252

245253
expect(successCallback).toHaveBeenCalled();
254+
expect(onSuccessMock).not.toHaveBeenCalled();
246255
});
247256

248257
test('should handle metadata instance creation error', async () => {
@@ -259,6 +268,7 @@ describe('useSidebarMetadataFetcher', () => {
259268
await waitFor(() => result.current.handleCreateMetadataInstance(newTemplateInstance, jest.fn()));
260269

261270
expect(result.current.status).toBe(STATUS.ERROR);
271+
expect(onSuccessMock).not.toHaveBeenCalled();
262272
expect(onErrorMock).toHaveBeenCalledWith(
263273
mockError,
264274
'metadata_creation_error',
@@ -286,6 +296,7 @@ describe('useSidebarMetadataFetcher', () => {
286296
result.current.handleUpdateMetadataInstance(mockTemplateInstances[0], ops, successCallback),
287297
);
288298
expect(successCallback).toHaveBeenCalled();
299+
expect(onSuccessMock).toHaveBeenCalledWith(SUCCESS_CODE_UPDATE_METADATA_TEMPLATE_INSTANCE, true);
289300
});
290301

291302
test('should handle metadata update error', async () => {
@@ -305,6 +316,7 @@ describe('useSidebarMetadataFetcher', () => {
305316
);
306317

307318
expect(successCallback).not.toHaveBeenCalled();
319+
expect(onSuccessMock).not.toHaveBeenCalled();
308320

309321
expect(result.current.status).toEqual(STATUS.ERROR);
310322
expect(result.current.templates).toEqual(mockTemplates);
@@ -346,6 +358,7 @@ describe('useSidebarMetadataFetcher', () => {
346358
const suggestions = await result.current.extractSuggestions('templateKey', 'global');
347359

348360
expect(suggestions).toEqual([]);
361+
expect(onSuccessMock).not.toHaveBeenCalled();
349362
expect(onErrorMock).toHaveBeenCalledWith(
350363
mockError,
351364
ERROR_CODE_FETCH_METADATA_SUGGESTIONS,
@@ -362,6 +375,7 @@ describe('useSidebarMetadataFetcher', () => {
362375
const suggestions = await result.current.extractSuggestions('templateKey', 'global');
363376

364377
expect(suggestions).toEqual([]);
378+
expect(onSuccessMock).not.toHaveBeenCalled();
365379
expect(onErrorMock).toHaveBeenCalledWith(
366380
new Error('No suggestions found.'),
367381
ERROR_CODE_EMPTY_METADATA_SUGGESTIONS,

src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ import {
1818
FIELD_IS_EXTERNALLY_OWNED,
1919
FIELD_PERMISSIONS_CAN_UPLOAD,
2020
FIELD_PERMISSIONS,
21+
SUCCESS_CODE_UPDATE_METADATA_TEMPLATE_INSTANCE,
22+
SUCCESS_CODE_DELETE_METADATA_TEMPLATE_INSTANCE,
2123
} from '../../../constants';
2224

2325
import messages from '../../common/messages';
2426

2527
import { type BoxItem } from '../../../common/types/core';
26-
import { type ErrorContextProps, type ExternalProps } from '../MetadataSidebarRedesign';
28+
import { type ErrorContextProps, type ExternalProps, type SuccessContextProps } from '../MetadataSidebarRedesign';
2729

2830
export enum STATUS {
2931
IDLE = 'idle',
@@ -55,6 +57,7 @@ function useSidebarMetadataFetcher(
5557
api: API,
5658
fileId: string,
5759
onError: ErrorContextProps['onError'],
60+
onSuccess: SuccessContextProps['onSuccess'],
5861
isFeatureEnabled: ExternalProps['isFeatureEnabled'],
5962
): DataFetcher {
6063
const [status, setStatus] = React.useState<STATUS>(STATUS.IDLE);
@@ -149,14 +152,17 @@ function useSidebarMetadataFetcher(
149152
await api.getMetadataAPI(false).deleteMetadata(
150153
file,
151154
metadataInstance,
152-
() => setStatus(STATUS.SUCCESS),
155+
() => {
156+
setStatus(STATUS.SUCCESS);
157+
onSuccess(SUCCESS_CODE_DELETE_METADATA_TEMPLATE_INSTANCE, true);
158+
},
153159
(error: ElementsXhrError, code: string) => {
154160
onApiError(error, code, messages.sidebarMetadataEditingErrorContent);
155161
},
156162
true,
157163
);
158164
},
159-
[api, onApiError, file],
165+
[api, onApiError, onSuccess, file],
160166
);
161167

162168
const handleCreateMetadataInstance = React.useCallback(
@@ -180,19 +186,20 @@ function useSidebarMetadataFetcher(
180186
JSONPatch: JSONPatchOperations,
181187
successCallback: () => void,
182188
) => {
183-
await api
184-
.getMetadataAPI(false)
185-
.updateMetadataRedesign(
186-
file,
187-
metadataInstance,
188-
JSONPatch,
189-
successCallback,
190-
(error: ElementsXhrError, code: string) => {
191-
onApiError(error, code, messages.sidebarMetadataEditingErrorContent);
192-
},
193-
);
189+
await api.getMetadataAPI(false).updateMetadataRedesign(
190+
file,
191+
metadataInstance,
192+
JSONPatch,
193+
() => {
194+
successCallback();
195+
onSuccess(SUCCESS_CODE_UPDATE_METADATA_TEMPLATE_INSTANCE, true);
196+
},
197+
(error: ElementsXhrError, code: string) => {
198+
onApiError(error, code, messages.sidebarMetadataEditingErrorContent);
199+
},
200+
);
194201
},
195-
[api, file, onApiError],
202+
[api, file, onApiError, onSuccess],
196203
);
197204

198205
const [, setError] = React.useState();
@@ -227,6 +234,7 @@ function useSidebarMetadataFetcher(
227234

228235
const templateInstance = templates.find(template => template.templateKey === templateKey && template.scope);
229236
const fields = templateInstance?.fields || [];
237+
230238
return fields.map(field => {
231239
const value = answer[field.key];
232240
// TODO: @box/metadadata-editor does not support AI suggestions, enable once supported

src/elements/content-sidebar/stories/MetadataSidebarRedesign.stories.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ const mockLogger = {
2525
const defaultMetadataSidebarProps: ComponentProps<typeof MetadataSidebarRedesign> = {
2626
isBoxAiSuggestionsEnabled: true,
2727
isFeatureEnabled: true,
28-
onError: fn,
28+
onError: fn(),
29+
onSuccess: fn(),
2930
};
3031

3132
export default {

src/elements/content-sidebar/stories/__mocks__/MetadataSidebarRedesignedMocks.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const mockMetadataInstances = {
8484
$typeVersion: 1,
8585
$template: 'myTemplate',
8686
$scope: 'enterprise_173733877',
87+
$templateKey: 'myTemplate',
8788
myAttribute: 'My Value',
8889
$canEdit: true,
8990
},

src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ const token = global.TOKEN;
2424
const defaultMetadataArgs = {
2525
fileId: fileIdWithMetadata,
2626
isFeatureEnabled: true,
27-
onError: fn,
27+
onError: fn(),
28+
onSuccess: fn(),
2829
};
2930
const defaultMetadataSidebarProps: ComponentProps<typeof MetadataSidebarRedesign> = {
3031
isFeatureEnabled: true,
31-
onError: fn,
32+
onError: fn(),
33+
onSuccess: fn(),
3234
};
3335
const mockFeatures = {
3436
'metadata.redesign.enabled': true,
@@ -61,7 +63,8 @@ export const AddTemplateDropdownMenuOnEmpty = {
6163
metadataSidebarProps: {
6264
isBoxAiSuggestionsEnabled: true,
6365
isFeatureEnabled: true,
64-
onError: fn,
66+
onError: fn(),
67+
onSuccess: fn(),
6568
},
6669
},
6770
play: async ({ canvasElement }) => {
@@ -277,11 +280,15 @@ export const DeleteButtonIsDisabledWhenAddingNewMetadataTemplate: StoryObj<typeo
277280

278281
const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' });
279282
expect(addTemplateButton).toBeInTheDocument();
280-
await userEvent.click(addTemplateButton);
283+
await act(async () => {
284+
await userEvent.click(addTemplateButton);
285+
});
281286

282287
const customMetadataOption = canvas.getByRole('option', { name: 'Virus Scan' });
283288
expect(customMetadataOption).toBeInTheDocument();
284-
await userEvent.click(customMetadataOption);
289+
await act(async () => {
290+
await userEvent.click(customMetadataOption);
291+
});
285292

286293
const deleteButton = await canvas.findByRole('button', { name: 'Delete' });
287294
expect(deleteButton).toBeDisabled();

0 commit comments

Comments
 (0)