box-ui-elements 24.0.0-beta.5 → 24.0.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/explorer.css +1 -1
- package/dist/explorer.js +1 -1
- package/dist/picker.js +1 -1
- package/es/elements/content-explorer/Content.js +3 -1
- package/es/elements/content-explorer/Content.js.map +1 -1
- package/es/elements/content-explorer/ContentExplorer.js +17 -6
- package/es/elements/content-explorer/ContentExplorer.js.map +1 -1
- package/es/elements/content-explorer/MetadataQueryAPIHelper.js +104 -7
- package/es/elements/content-explorer/MetadataQueryAPIHelper.js.map +1 -1
- package/es/elements/content-explorer/MetadataQueryBuilder.js +154 -0
- package/es/elements/content-explorer/MetadataQueryBuilder.js.map +1 -0
- package/es/elements/content-explorer/MetadataViewContainer.js +92 -46
- package/es/elements/content-explorer/MetadataViewContainer.js.map +1 -1
- package/es/elements/content-explorer/constants.js +4 -2
- package/es/elements/content-explorer/constants.js.map +1 -1
- package/es/elements/content-explorer/stories/MetadataView.stories.js +3 -25
- package/es/elements/content-explorer/stories/MetadataView.stories.js.map +1 -1
- package/es/elements/content-explorer/stories/tests/MetadataView-visual.stories.js +4 -16
- package/es/elements/content-explorer/stories/tests/MetadataView-visual.stories.js.map +1 -1
- package/es/elements/content-explorer/utils.js +12 -0
- package/es/elements/content-explorer/utils.js.map +1 -1
- package/es/src/elements/common/__mocks__/mockMetadata.d.ts +8 -24
- package/es/src/elements/content-explorer/Content.d.ts +4 -3
- package/es/src/elements/content-explorer/ContentExplorer.d.ts +8 -3
- package/es/src/elements/content-explorer/MetadataQueryAPIHelper.d.ts +11 -2
- package/es/src/elements/content-explorer/MetadataQueryBuilder.d.ts +27 -0
- package/es/src/elements/content-explorer/MetadataViewContainer.d.ts +8 -4
- package/es/src/elements/content-explorer/__tests__/MetadataQueryBuilder.test.d.ts +1 -0
- package/es/src/elements/content-explorer/constants.d.ts +4 -2
- package/es/src/elements/content-explorer/utils.d.ts +2 -0
- package/i18n/bn-IN.js +1 -1
- package/i18n/bn-IN.properties +8 -0
- package/i18n/da-DK.js +1 -1
- package/i18n/da-DK.properties +8 -0
- package/i18n/de-DE.js +1 -1
- package/i18n/de-DE.properties +8 -0
- package/i18n/en-AU.js +1 -1
- package/i18n/en-AU.properties +8 -0
- package/i18n/en-CA.js +1 -1
- package/i18n/en-CA.properties +8 -0
- package/i18n/en-GB.js +1 -1
- package/i18n/en-GB.properties +8 -0
- package/i18n/es-419.js +1 -1
- package/i18n/es-419.properties +8 -0
- package/i18n/es-ES.js +1 -1
- package/i18n/es-ES.properties +8 -0
- package/i18n/fi-FI.js +1 -1
- package/i18n/fi-FI.properties +8 -0
- package/i18n/fr-CA.js +1 -1
- package/i18n/fr-CA.properties +8 -0
- package/i18n/fr-FR.js +1 -1
- package/i18n/fr-FR.properties +8 -0
- package/i18n/hi-IN.js +1 -1
- package/i18n/hi-IN.properties +8 -0
- package/i18n/it-IT.js +1 -1
- package/i18n/it-IT.properties +8 -0
- package/i18n/ja-JP.js +1 -1
- package/i18n/ja-JP.properties +8 -0
- package/i18n/ko-KR.js +1 -1
- package/i18n/ko-KR.properties +8 -0
- package/i18n/nb-NO.js +1 -1
- package/i18n/nb-NO.properties +8 -0
- package/i18n/nl-NL.js +1 -1
- package/i18n/nl-NL.properties +8 -0
- package/i18n/pl-PL.js +1 -1
- package/i18n/pl-PL.properties +8 -0
- package/i18n/pt-BR.js +1 -1
- package/i18n/pt-BR.properties +8 -0
- package/i18n/ru-RU.js +1 -1
- package/i18n/ru-RU.properties +8 -0
- package/i18n/sv-SE.js +1 -1
- package/i18n/sv-SE.properties +8 -0
- package/i18n/tr-TR.js +1 -1
- package/i18n/tr-TR.properties +8 -0
- package/i18n/zh-CN.js +1 -1
- package/i18n/zh-CN.properties +8 -0
- package/i18n/zh-TW.js +1 -1
- package/i18n/zh-TW.properties +8 -0
- package/package.json +3 -3
- package/src/elements/common/__mocks__/mockMetadata.ts +7 -11
- package/src/elements/content-explorer/Content.tsx +8 -2
- package/src/elements/content-explorer/ContentExplorer.tsx +209 -194
- package/src/elements/content-explorer/MetadataQueryAPIHelper.ts +111 -5
- package/src/elements/content-explorer/MetadataQueryBuilder.ts +194 -0
- package/src/elements/content-explorer/MetadataViewContainer.tsx +112 -37
- package/src/elements/content-explorer/__tests__/Content.test.tsx +1 -0
- package/src/elements/content-explorer/__tests__/ContentExplorer.test.tsx +2 -5
- package/src/elements/content-explorer/__tests__/MetadataQueryAPIHelper.test.ts +427 -8
- package/src/elements/content-explorer/__tests__/MetadataQueryBuilder.test.ts +535 -0
- package/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx +413 -9
- package/src/elements/content-explorer/constants.ts +39 -2
- package/src/elements/content-explorer/stories/MetadataView.stories.tsx +3 -21
- package/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx +2 -13
- package/src/elements/content-explorer/utils.ts +17 -0
|
@@ -3,18 +3,34 @@ import * as React from 'react';
|
|
|
3
3
|
import type { Collection } from '../../../common/types/core';
|
|
4
4
|
import type { MetadataTemplate, MetadataTemplateField } from '../../../common/types/metadata';
|
|
5
5
|
import { render, screen, userEvent, waitFor, within } from '../../../test-utils/testing-library';
|
|
6
|
-
import MetadataViewContainer, {
|
|
6
|
+
import MetadataViewContainer, {
|
|
7
|
+
type MetadataViewContainerProps,
|
|
8
|
+
convertFilterValuesToExternal,
|
|
9
|
+
type ExternalFilterValues,
|
|
10
|
+
} from '../MetadataViewContainer';
|
|
7
11
|
|
|
8
12
|
describe('elements/content-explorer/MetadataViewContainer', () => {
|
|
9
13
|
const mockItems = [
|
|
10
|
-
{
|
|
11
|
-
|
|
14
|
+
{
|
|
15
|
+
id: '1',
|
|
16
|
+
name: 'File 1.txt',
|
|
17
|
+
type: 'file',
|
|
18
|
+
'item.name': 'File 1.txt',
|
|
19
|
+
industry: 'tech',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: '2',
|
|
23
|
+
name: 'File 2.pdf',
|
|
24
|
+
type: 'file',
|
|
25
|
+
'item.name': 'File 2.pdf',
|
|
26
|
+
industry: 'finance',
|
|
27
|
+
},
|
|
12
28
|
];
|
|
13
29
|
|
|
14
30
|
const mockMetadataTemplateFields: MetadataTemplateField[] = [
|
|
15
31
|
{
|
|
16
32
|
id: 'field1',
|
|
17
|
-
key: '
|
|
33
|
+
key: 'item.name',
|
|
18
34
|
displayName: 'Name',
|
|
19
35
|
type: 'string',
|
|
20
36
|
},
|
|
@@ -28,6 +44,22 @@ describe('elements/content-explorer/MetadataViewContainer', () => {
|
|
|
28
44
|
{ key: 'finance', id: 'finance1' },
|
|
29
45
|
],
|
|
30
46
|
},
|
|
47
|
+
{
|
|
48
|
+
id: 'field3',
|
|
49
|
+
key: 'price',
|
|
50
|
+
displayName: 'Price',
|
|
51
|
+
type: 'float',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'field4',
|
|
55
|
+
key: 'category',
|
|
56
|
+
displayName: 'Category',
|
|
57
|
+
type: 'multiSelect',
|
|
58
|
+
options: [
|
|
59
|
+
{ key: 'category1', id: 'cat1' },
|
|
60
|
+
{ key: 'category2', id: 'cat2' },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
31
63
|
];
|
|
32
64
|
|
|
33
65
|
const mockMetadataTemplate: MetadataTemplate = {
|
|
@@ -49,7 +81,7 @@ describe('elements/content-explorer/MetadataViewContainer', () => {
|
|
|
49
81
|
columns: [
|
|
50
82
|
{
|
|
51
83
|
textValue: 'Name',
|
|
52
|
-
id: 'name',
|
|
84
|
+
id: 'item.name',
|
|
53
85
|
type: 'string',
|
|
54
86
|
allowsSorting: true,
|
|
55
87
|
minWidth: 250,
|
|
@@ -66,17 +98,22 @@ describe('elements/content-explorer/MetadataViewContainer', () => {
|
|
|
66
98
|
},
|
|
67
99
|
],
|
|
68
100
|
metadataTemplate: mockMetadataTemplate,
|
|
101
|
+
onMetadataFilter: jest.fn(),
|
|
69
102
|
};
|
|
70
103
|
|
|
71
104
|
const renderComponent = (props: Partial<MetadataViewContainerProps> = {}) => {
|
|
72
105
|
return render(<MetadataViewContainer {...defaultProps} {...props} />);
|
|
73
106
|
};
|
|
74
107
|
|
|
108
|
+
beforeEach(() => {
|
|
109
|
+
jest.clearAllMocks();
|
|
110
|
+
});
|
|
111
|
+
|
|
75
112
|
test('should render MetadataView component', () => {
|
|
76
113
|
renderComponent();
|
|
77
114
|
|
|
78
115
|
expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument();
|
|
79
|
-
expect(screen.
|
|
116
|
+
expect(screen.getAllByRole('button', { name: 'Name' })).toHaveLength(2); // One in filter bar, one in table header
|
|
80
117
|
expect(screen.getByRole('button', { name: 'Industry' })).toBeInTheDocument();
|
|
81
118
|
expect(screen.getByText('File 1.txt')).toBeInTheDocument();
|
|
82
119
|
expect(screen.getByText('File 2.pdf')).toBeInTheDocument();
|
|
@@ -101,7 +138,11 @@ describe('elements/content-explorer/MetadataViewContainer', () => {
|
|
|
101
138
|
],
|
|
102
139
|
};
|
|
103
140
|
|
|
104
|
-
renderComponent({
|
|
141
|
+
renderComponent({
|
|
142
|
+
metadataTemplate: template,
|
|
143
|
+
actionBarProps: { onFilterSubmit },
|
|
144
|
+
onMetadataFilter: jest.fn(),
|
|
145
|
+
});
|
|
105
146
|
|
|
106
147
|
await userEvent().click(screen.getByRole('button', { name: /Contact Role/ }));
|
|
107
148
|
await userEvent().click(within(screen.getByRole('menu')).getByRole('menuitemcheckbox', { name: 'Developer' }));
|
|
@@ -112,7 +153,370 @@ describe('elements/content-explorer/MetadataViewContainer', () => {
|
|
|
112
153
|
await waitFor(() => expect(onFilterSubmit).toHaveBeenCalledTimes(2));
|
|
113
154
|
const firstCall = onFilterSubmit.mock.calls[0][0];
|
|
114
155
|
const secondCall = onFilterSubmit.mock.calls[1][0];
|
|
115
|
-
|
|
116
|
-
expect(
|
|
156
|
+
|
|
157
|
+
expect(firstCall.role.value).toEqual(['Developer']);
|
|
158
|
+
expect(secondCall.role.value).toEqual(['Developer', 'Marketing']);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('should call onMetadataFilter and onFilterSubmit when filter is submitted', async () => {
|
|
162
|
+
const onFilterSubmit = jest.fn();
|
|
163
|
+
const onMetadataFilter = jest.fn();
|
|
164
|
+
const template: MetadataTemplate = {
|
|
165
|
+
...mockMetadataTemplate,
|
|
166
|
+
fields: [
|
|
167
|
+
{
|
|
168
|
+
id: 'field1',
|
|
169
|
+
key: 'status',
|
|
170
|
+
displayName: 'Status',
|
|
171
|
+
type: 'enum',
|
|
172
|
+
options: [
|
|
173
|
+
{ id: 's1', key: 'Active' },
|
|
174
|
+
{ id: 's2', key: 'Inactive' },
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
renderComponent({
|
|
181
|
+
metadataTemplate: template,
|
|
182
|
+
actionBarProps: { onFilterSubmit },
|
|
183
|
+
onMetadataFilter,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await userEvent().click(screen.getByRole('button', { name: /Status/ }));
|
|
187
|
+
await userEvent().click(within(screen.getByRole('menu')).getByRole('menuitemcheckbox', { name: 'Active' }));
|
|
188
|
+
|
|
189
|
+
await waitFor(() => {
|
|
190
|
+
expect(onMetadataFilter).toHaveBeenCalledTimes(1);
|
|
191
|
+
expect(onFilterSubmit).toHaveBeenCalledTimes(1);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const filterCall = onMetadataFilter.mock.calls[0][0];
|
|
195
|
+
const submitCall = onFilterSubmit.mock.calls[0][0];
|
|
196
|
+
|
|
197
|
+
expect(filterCall.status.value).toEqual(['Active']);
|
|
198
|
+
expect(submitCall.status.value).toEqual(['Active']);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('should only call onMetadataFilter when onFilterSubmit is not provided', async () => {
|
|
202
|
+
const onMetadataFilter = jest.fn();
|
|
203
|
+
const template: MetadataTemplate = {
|
|
204
|
+
...mockMetadataTemplate,
|
|
205
|
+
fields: [
|
|
206
|
+
{
|
|
207
|
+
id: 'field1',
|
|
208
|
+
key: 'status',
|
|
209
|
+
displayName: 'Status',
|
|
210
|
+
type: 'enum',
|
|
211
|
+
options: [
|
|
212
|
+
{ id: 's1', key: 'Active' },
|
|
213
|
+
{ id: 's2', key: 'Inactive' },
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
renderComponent({
|
|
220
|
+
metadataTemplate: template,
|
|
221
|
+
onMetadataFilter,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
await userEvent().click(screen.getByRole('button', { name: /Status/ }));
|
|
225
|
+
await userEvent().click(within(screen.getByRole('menu')).getByRole('menuitemcheckbox', { name: 'Active' }));
|
|
226
|
+
|
|
227
|
+
await waitFor(() => {
|
|
228
|
+
expect(onMetadataFilter).toHaveBeenCalledTimes(1);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const filterCall = onMetadataFilter.mock.calls[0][0];
|
|
232
|
+
expect(filterCall.status.value).toEqual(['Active']);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('should handle initial filter values transformation', () => {
|
|
236
|
+
const initialFilterValues = {
|
|
237
|
+
industry: {
|
|
238
|
+
fieldType: 'enum' as const,
|
|
239
|
+
value: ['tech'],
|
|
240
|
+
},
|
|
241
|
+
price: {
|
|
242
|
+
fieldType: 'float' as const,
|
|
243
|
+
value: { range: { gt: 10, lt: 100 } },
|
|
244
|
+
},
|
|
245
|
+
name: {
|
|
246
|
+
fieldType: 'string' as const,
|
|
247
|
+
value: ['search term'],
|
|
248
|
+
},
|
|
249
|
+
category: {
|
|
250
|
+
fieldType: 'multiSelect' as const,
|
|
251
|
+
value: ['category1', 'category2'],
|
|
252
|
+
},
|
|
253
|
+
} as unknown as ExternalFilterValues;
|
|
254
|
+
|
|
255
|
+
renderComponent({
|
|
256
|
+
actionBarProps: { initialFilterValues },
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(screen.getByRole('button', { name: 'All Filters 3' })).toBeInTheDocument();
|
|
260
|
+
expect(screen.getByRole('button', { name: /Industry/i })).toHaveTextContent(/\(1\)/);
|
|
261
|
+
expect(screen.getByRole('button', { name: /Category/i })).toHaveTextContent(/\(2\)/);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('should handle empty metadata template fields', () => {
|
|
265
|
+
const emptyTemplate: MetadataTemplate = {
|
|
266
|
+
...mockMetadataTemplate,
|
|
267
|
+
fields: [],
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
renderComponent({ metadataTemplate: emptyTemplate });
|
|
271
|
+
|
|
272
|
+
expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument();
|
|
273
|
+
expect(screen.getByText('File 1.txt')).toBeInTheDocument();
|
|
274
|
+
expect(screen.getByText('File 2.pdf')).toBeInTheDocument();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('should handle undefined metadata template', () => {
|
|
278
|
+
renderComponent({ metadataTemplate: undefined as unknown as MetadataTemplate });
|
|
279
|
+
|
|
280
|
+
expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument();
|
|
281
|
+
expect(screen.getByText('File 1.txt')).toBeInTheDocument();
|
|
282
|
+
expect(screen.getByText('File 2.pdf')).toBeInTheDocument();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test('should handle empty collection items', () => {
|
|
286
|
+
const emptyCollection: Collection = {
|
|
287
|
+
id: '0',
|
|
288
|
+
items: [],
|
|
289
|
+
percentLoaded: 100,
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
renderComponent({ currentCollection: emptyCollection });
|
|
293
|
+
|
|
294
|
+
expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('should handle undefined collection items', () => {
|
|
298
|
+
const collectionWithoutItems: Collection = {
|
|
299
|
+
id: '0',
|
|
300
|
+
percentLoaded: 100,
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
renderComponent({ currentCollection: collectionWithoutItems });
|
|
304
|
+
|
|
305
|
+
expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('should memoize filterGroups when metadataTemplate changes', () => {
|
|
309
|
+
const { rerender } = renderComponent();
|
|
310
|
+
|
|
311
|
+
// Re-render with same template
|
|
312
|
+
rerender(<MetadataViewContainer {...defaultProps} />);
|
|
313
|
+
|
|
314
|
+
// Re-render with different template
|
|
315
|
+
const newTemplate: MetadataTemplate = {
|
|
316
|
+
...mockMetadataTemplate,
|
|
317
|
+
id: 'template2',
|
|
318
|
+
displayName: 'New Template',
|
|
319
|
+
};
|
|
320
|
+
rerender(<MetadataViewContainer {...defaultProps} metadataTemplate={newTemplate} />);
|
|
321
|
+
|
|
322
|
+
expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('should handle fields with no options', () => {
|
|
326
|
+
const templateWithoutOptions: MetadataTemplate = {
|
|
327
|
+
...mockMetadataTemplate,
|
|
328
|
+
fields: [
|
|
329
|
+
{
|
|
330
|
+
id: 'field1',
|
|
331
|
+
key: 'name',
|
|
332
|
+
displayName: 'File Name',
|
|
333
|
+
type: 'string',
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
id: 'field2',
|
|
337
|
+
key: 'industry',
|
|
338
|
+
displayName: 'Industry',
|
|
339
|
+
type: 'enum',
|
|
340
|
+
// No options defined
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
renderComponent({ metadataTemplate: templateWithoutOptions });
|
|
346
|
+
|
|
347
|
+
expect(screen.getByRole('button', { name: 'All Filters' })).toBeInTheDocument();
|
|
348
|
+
expect(screen.getAllByRole('button', { name: 'Name' })).toHaveLength(1); // Only the one added by component
|
|
349
|
+
expect(screen.getByRole('button', { name: 'File Name' })).toBeInTheDocument();
|
|
350
|
+
expect(screen.getByRole('button', { name: 'Industry' })).toBeInTheDocument();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('should handle multiple field types in filter submission', async () => {
|
|
354
|
+
const onFilterSubmit = jest.fn();
|
|
355
|
+
const onMetadataFilter = jest.fn();
|
|
356
|
+
const template: MetadataTemplate = {
|
|
357
|
+
...mockMetadataTemplate,
|
|
358
|
+
fields: [
|
|
359
|
+
{
|
|
360
|
+
id: 'field1',
|
|
361
|
+
key: 'status',
|
|
362
|
+
displayName: 'Status',
|
|
363
|
+
type: 'enum',
|
|
364
|
+
options: [
|
|
365
|
+
{ id: 's1', key: 'Active' },
|
|
366
|
+
{ id: 's2', key: 'Inactive' },
|
|
367
|
+
],
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
id: 'field2',
|
|
371
|
+
key: 'price',
|
|
372
|
+
displayName: 'Price',
|
|
373
|
+
type: 'float',
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
renderComponent({
|
|
379
|
+
metadataTemplate: template,
|
|
380
|
+
actionBarProps: { onFilterSubmit },
|
|
381
|
+
onMetadataFilter,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Test enum filter
|
|
385
|
+
await userEvent().click(screen.getByRole('button', { name: /Status/ }));
|
|
386
|
+
await userEvent().click(within(screen.getByRole('menu')).getByRole('menuitemcheckbox', { name: 'Active' }));
|
|
387
|
+
|
|
388
|
+
await waitFor(() => {
|
|
389
|
+
expect(onMetadataFilter).toHaveBeenCalledTimes(1);
|
|
390
|
+
expect(onFilterSubmit).toHaveBeenCalledTimes(1);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
const filterCall = onMetadataFilter.mock.calls[0][0];
|
|
394
|
+
expect(filterCall.status.value).toEqual(['Active']);
|
|
395
|
+
expect(filterCall.status.fieldType).toBe('enum');
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
describe('convertFilterValuesToExternal', () => {
|
|
399
|
+
test('should convert enum values to string arrays', () => {
|
|
400
|
+
const internalFilters = {
|
|
401
|
+
'status-filter': {
|
|
402
|
+
fieldType: 'enum' as const,
|
|
403
|
+
options: [
|
|
404
|
+
{ key: 'active', id: 'active1' },
|
|
405
|
+
{ key: 'inactive', id: 'inactive1' },
|
|
406
|
+
],
|
|
407
|
+
value: { enum: ['active', 'inactive'] },
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
const result = convertFilterValuesToExternal(internalFilters);
|
|
412
|
+
|
|
413
|
+
expect(result['status-filter'].value).toEqual(['active', 'inactive']);
|
|
414
|
+
expect(result['status-filter'].fieldType).toBe('enum');
|
|
415
|
+
expect(result['status-filter'].options).toEqual([
|
|
416
|
+
{ key: 'active', id: 'active1' },
|
|
417
|
+
{ key: 'inactive', id: 'inactive1' },
|
|
418
|
+
]);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test('should keep range values unchanged', () => {
|
|
422
|
+
const internalFilters = {
|
|
423
|
+
'price-filter': {
|
|
424
|
+
fieldType: 'float' as const,
|
|
425
|
+
value: { range: { gt: 10, lt: 100 }, advancedFilterOption: 'range' },
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const result = convertFilterValuesToExternal(internalFilters);
|
|
430
|
+
|
|
431
|
+
expect(result['price-filter'].value).toEqual({ range: { gt: 10, lt: 100 }, advancedFilterOption: 'range' });
|
|
432
|
+
expect(result['price-filter'].fieldType).toBe('float');
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test('should keep float values unchanged', () => {
|
|
436
|
+
const internalFilters = {
|
|
437
|
+
'rating-filter': {
|
|
438
|
+
fieldType: 'float' as const,
|
|
439
|
+
value: { range: { gt: 4.5, lt: 5.0 }, advancedFilterOption: 'range' },
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
const result = convertFilterValuesToExternal(internalFilters);
|
|
444
|
+
|
|
445
|
+
expect(result['rating-filter'].value).toEqual({
|
|
446
|
+
range: { gt: 4.5, lt: 5.0 },
|
|
447
|
+
advancedFilterOption: 'range',
|
|
448
|
+
});
|
|
449
|
+
expect(result['rating-filter'].fieldType).toBe('float');
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test('should handle mixed field types', () => {
|
|
453
|
+
const internalFilters = {
|
|
454
|
+
'status-filter': {
|
|
455
|
+
fieldType: 'enum' as const,
|
|
456
|
+
options: [
|
|
457
|
+
{ key: 'active', id: 'active1' },
|
|
458
|
+
{ key: 'inactive', id: 'inactive1' },
|
|
459
|
+
],
|
|
460
|
+
value: { enum: ['active'] },
|
|
461
|
+
},
|
|
462
|
+
'price-filter': {
|
|
463
|
+
fieldType: 'float' as const,
|
|
464
|
+
value: { range: { gt: 0, lt: 50 }, advancedFilterOption: 'range' },
|
|
465
|
+
},
|
|
466
|
+
'category-filter': {
|
|
467
|
+
fieldType: 'multiSelect' as const,
|
|
468
|
+
options: [
|
|
469
|
+
{ key: 'tech', id: 'tech1' },
|
|
470
|
+
{ key: 'finance', id: 'finance1' },
|
|
471
|
+
{ key: 'healthcare', id: 'healthcare1' },
|
|
472
|
+
],
|
|
473
|
+
value: { enum: ['tech', 'finance'] },
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const result = convertFilterValuesToExternal(internalFilters);
|
|
478
|
+
|
|
479
|
+
expect(result['status-filter'].value).toEqual(['active']);
|
|
480
|
+
expect(result['price-filter'].value).toEqual({ range: { gt: 0, lt: 50 }, advancedFilterOption: 'range' });
|
|
481
|
+
expect(result['category-filter'].value).toEqual(['tech', 'finance']);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
test('should handle empty filter object', () => {
|
|
485
|
+
const result = convertFilterValuesToExternal({});
|
|
486
|
+
expect(result).toEqual({});
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test('should handle enum values with empty array', () => {
|
|
490
|
+
const internalFilters = {
|
|
491
|
+
'status-filter': {
|
|
492
|
+
fieldType: 'enum' as const,
|
|
493
|
+
options: [{ key: 'active', id: 'active1' }],
|
|
494
|
+
value: { enum: [] },
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const result = convertFilterValuesToExternal(internalFilters);
|
|
499
|
+
|
|
500
|
+
expect(result['status-filter'].value).toEqual([]);
|
|
501
|
+
expect(result['status-filter'].fieldType).toBe('enum');
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test('should handle multiSelect values', () => {
|
|
505
|
+
const internalFilters = {
|
|
506
|
+
'category-filter': {
|
|
507
|
+
fieldType: 'multiSelect' as const,
|
|
508
|
+
options: [
|
|
509
|
+
{ key: 'tech', id: 'tech1' },
|
|
510
|
+
{ key: 'finance', id: 'finance1' },
|
|
511
|
+
],
|
|
512
|
+
value: { enum: ['tech', 'finance'] },
|
|
513
|
+
},
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
const result = convertFilterValuesToExternal(internalFilters);
|
|
517
|
+
|
|
518
|
+
expect(result['category-filter'].value).toEqual(['tech', 'finance']);
|
|
519
|
+
expect(result['category-filter'].fieldType).toBe('multiSelect');
|
|
520
|
+
});
|
|
117
521
|
});
|
|
118
522
|
});
|
|
@@ -2,7 +2,7 @@ import { FIELD_FILE_VERSION, FIELD_SHA1, FIELD_SHARED_LINK, FIELD_WATERMARK_INFO
|
|
|
2
2
|
import { FOLDER_FIELDS_TO_FETCH } from '../../utils/fields';
|
|
3
3
|
|
|
4
4
|
// Fields needed for Content Explorer folder requests
|
|
5
|
-
const CONTENT_EXPLORER_FOLDER_FIELDS_TO_FETCH = [
|
|
5
|
+
export const CONTENT_EXPLORER_FOLDER_FIELDS_TO_FETCH = [
|
|
6
6
|
...FOLDER_FIELDS_TO_FETCH,
|
|
7
7
|
FIELD_FILE_VERSION,
|
|
8
8
|
FIELD_SHA1,
|
|
@@ -10,4 +10,41 @@ const CONTENT_EXPLORER_FOLDER_FIELDS_TO_FETCH = [
|
|
|
10
10
|
FIELD_WATERMARK_INFO,
|
|
11
11
|
];
|
|
12
12
|
|
|
13
|
-
export
|
|
13
|
+
export const NON_FOLDER_FILE_TYPES_MAP = new Map([
|
|
14
|
+
['boxnoteType', ['boxnote']],
|
|
15
|
+
['boxcanvasType', ['boxcanvas']],
|
|
16
|
+
['pdfType', ['pdf']],
|
|
17
|
+
['documentType', ['doc', 'docx', 'gdoc', 'rtf', 'txt']],
|
|
18
|
+
['spreadsheetType', ['xls', 'xlsx', 'xlsm', 'csv', 'gsheet']],
|
|
19
|
+
['presentationType', ['ppt', 'pptx', 'odp']],
|
|
20
|
+
['imageType', ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tif', 'tiff']],
|
|
21
|
+
['audioType', ['mp3', 'm4a', 'm4p', 'wav', 'mid', 'wma']],
|
|
22
|
+
[
|
|
23
|
+
'videoType',
|
|
24
|
+
[
|
|
25
|
+
'mp4',
|
|
26
|
+
'mpeg',
|
|
27
|
+
'mpg',
|
|
28
|
+
'wmv',
|
|
29
|
+
'3g2',
|
|
30
|
+
'3gp',
|
|
31
|
+
'avi',
|
|
32
|
+
'm2v',
|
|
33
|
+
'm4v',
|
|
34
|
+
'mkv',
|
|
35
|
+
'mov',
|
|
36
|
+
'ogg',
|
|
37
|
+
'mts',
|
|
38
|
+
'qt',
|
|
39
|
+
'ts',
|
|
40
|
+
'flv',
|
|
41
|
+
'rm',
|
|
42
|
+
],
|
|
43
|
+
],
|
|
44
|
+
['drawingType', ['dwg', 'dxf']],
|
|
45
|
+
['threedType', ['obj', 'fbx', 'stl', 'amf', 'iges']],
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
export const FILE_FOLDER_TYPES_MAP = new Map(NON_FOLDER_FILE_TYPES_MAP).set('folderType', ['folder']);
|
|
49
|
+
|
|
50
|
+
export const NON_FOLDER_FILE_TYPES = Array.from(NON_FOLDER_FILE_TYPES_MAP.keys());
|
|
@@ -23,37 +23,19 @@ const metadataQuery = {
|
|
|
23
23
|
|
|
24
24
|
ancestor_folder_id: '0',
|
|
25
25
|
fields: [
|
|
26
|
-
`name`,
|
|
27
26
|
`${metadataSourceFieldName}.industry`,
|
|
28
27
|
`${metadataSourceFieldName}.last_contacted_at`,
|
|
29
28
|
`${metadataSourceFieldName}.role`,
|
|
29
|
+
`${metadataSourceFieldName}.number`,
|
|
30
30
|
],
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
const fieldsToShow = [
|
|
34
|
-
{ key: `name` },
|
|
35
|
-
{ key: `${metadataSourceFieldName}.industry`, canEdit: true },
|
|
36
|
-
{ key: `${metadataSourceFieldName}.last_contacted_at`, canEdit: true },
|
|
37
|
-
{ key: `${metadataSourceFieldName}.role`, canEdit: true },
|
|
38
|
-
];
|
|
39
|
-
|
|
40
33
|
const columns = mockSchema.fields.map(field => {
|
|
41
|
-
if (field.key === 'name') {
|
|
42
|
-
return {
|
|
43
|
-
textValue: field.displayName,
|
|
44
|
-
id: 'name',
|
|
45
|
-
type: 'string',
|
|
46
|
-
allowsSorting: true,
|
|
47
|
-
minWidth: 250,
|
|
48
|
-
maxWidth: 250,
|
|
49
|
-
isRowHeader: true,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
34
|
if (field.type === 'date') {
|
|
54
35
|
return {
|
|
55
36
|
textValue: field.displayName,
|
|
56
37
|
id: `${metadataSourceFieldName}.${field.key}`,
|
|
38
|
+
key: `${metadataSourceFieldName}.${field.key}`,
|
|
57
39
|
type: field.type,
|
|
58
40
|
allowsSorting: true,
|
|
59
41
|
minWidth: 200,
|
|
@@ -68,6 +50,7 @@ const columns = mockSchema.fields.map(field => {
|
|
|
68
50
|
return {
|
|
69
51
|
textValue: field.displayName,
|
|
70
52
|
id: `${metadataSourceFieldName}.${field.key}`,
|
|
53
|
+
key: `${metadataSourceFieldName}.${field.key}`,
|
|
71
54
|
type: field.type,
|
|
72
55
|
allowsSorting: true,
|
|
73
56
|
minWidth: 200,
|
|
@@ -87,7 +70,6 @@ export const metadataView: Story = {
|
|
|
87
70
|
},
|
|
88
71
|
},
|
|
89
72
|
metadataQuery,
|
|
90
|
-
fieldsToShow,
|
|
91
73
|
defaultView,
|
|
92
74
|
features: {
|
|
93
75
|
contentExplorer: {
|
|
@@ -32,13 +32,11 @@ const metadataQuery = {
|
|
|
32
32
|
fields: [
|
|
33
33
|
// Default to returning all fields in the metadata template schema, and name as a standalone (non-metadata) field
|
|
34
34
|
...mockSchema.fields.map(field => `${metadataFieldNamePrefix}.${field.key}`),
|
|
35
|
-
'name',
|
|
36
35
|
],
|
|
37
36
|
};
|
|
38
37
|
|
|
39
38
|
// Used for metadata view v1
|
|
40
39
|
const fieldsToShow = [
|
|
41
|
-
{ key: `${metadataFieldNamePrefix}.name`, canEdit: false, displayName: 'Alias' },
|
|
42
40
|
{ key: `${metadataFieldNamePrefix}.industry`, canEdit: true },
|
|
43
41
|
{ key: `${metadataFieldNamePrefix}.last_contacted_at`, canEdit: true },
|
|
44
42
|
{ key: `${metadataFieldNamePrefix}.role`, canEdit: true },
|
|
@@ -46,15 +44,6 @@ const fieldsToShow = [
|
|
|
46
44
|
|
|
47
45
|
// Used for metadata view v2
|
|
48
46
|
const columns = [
|
|
49
|
-
{
|
|
50
|
-
// Always include the name column
|
|
51
|
-
textValue: 'Name',
|
|
52
|
-
id: 'name',
|
|
53
|
-
type: 'string',
|
|
54
|
-
allowsSorting: true,
|
|
55
|
-
minWidth: 150,
|
|
56
|
-
maxWidth: 150,
|
|
57
|
-
},
|
|
58
47
|
...mockSchema.fields.map(field => ({
|
|
59
48
|
textValue: field.displayName,
|
|
60
49
|
id: `${metadataFieldNamePrefix}.${field.key}`,
|
|
@@ -167,9 +156,9 @@ export const metadataViewV2WithCustomActions: Story = {
|
|
|
167
156
|
|
|
168
157
|
const initialFilterActionBarProps = {
|
|
169
158
|
initialFilterValues: {
|
|
170
|
-
|
|
159
|
+
industry: { value: ['Legal'] },
|
|
171
160
|
'mimetype-filter': { value: ['boxnoteType', 'documentType', 'threedType'] },
|
|
172
|
-
|
|
161
|
+
role: { value: ['Developer', 'Business Owner', 'Marketing'] },
|
|
173
162
|
},
|
|
174
163
|
};
|
|
175
164
|
|
|
@@ -11,9 +11,11 @@ import {
|
|
|
11
11
|
} from '@box/metadata-editor';
|
|
12
12
|
import type { MetadataFieldType } from '@box/metadata-view';
|
|
13
13
|
import type { Selection } from 'react-aria-components';
|
|
14
|
+
import { BoxItemSelection } from '@box/box-item-type-selector';
|
|
14
15
|
import type { BoxItem, Collection } from '../../common/types/core';
|
|
15
16
|
|
|
16
17
|
import messages from '../common/messages';
|
|
18
|
+
import { FILE_FOLDER_TYPES_MAP, NON_FOLDER_FILE_TYPES } from './constants';
|
|
17
19
|
|
|
18
20
|
// Specific type for metadata field value in the item
|
|
19
21
|
// Note: Item doesn't have field value in metadata object if that field is not set, so the value will be undefined in this case
|
|
@@ -193,3 +195,18 @@ export function useTemplateInstance(metadataTemplate: MetadataTemplate, selected
|
|
|
193
195
|
type,
|
|
194
196
|
};
|
|
195
197
|
}
|
|
198
|
+
|
|
199
|
+
export const mapFileTypes = (selectedFileTypes: BoxItemSelection) => {
|
|
200
|
+
const selectedFileTypesSet = new Set(selectedFileTypes);
|
|
201
|
+
|
|
202
|
+
const areAllNonFolderFileTypesSelected = NON_FOLDER_FILE_TYPES.every(key => selectedFileTypesSet.has(key));
|
|
203
|
+
|
|
204
|
+
if (areAllNonFolderFileTypesSelected) {
|
|
205
|
+
if (selectedFileTypes.includes('folderType')) {
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
return ['file'];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return selectedFileTypes.map(fileType => FILE_FOLDER_TYPES_MAP.get(fileType as string) || []).flat();
|
|
212
|
+
};
|