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.
Files changed (94) hide show
  1. package/dist/explorer.css +1 -1
  2. package/dist/explorer.js +1 -1
  3. package/dist/picker.js +1 -1
  4. package/es/elements/content-explorer/Content.js +3 -1
  5. package/es/elements/content-explorer/Content.js.map +1 -1
  6. package/es/elements/content-explorer/ContentExplorer.js +17 -6
  7. package/es/elements/content-explorer/ContentExplorer.js.map +1 -1
  8. package/es/elements/content-explorer/MetadataQueryAPIHelper.js +104 -7
  9. package/es/elements/content-explorer/MetadataQueryAPIHelper.js.map +1 -1
  10. package/es/elements/content-explorer/MetadataQueryBuilder.js +154 -0
  11. package/es/elements/content-explorer/MetadataQueryBuilder.js.map +1 -0
  12. package/es/elements/content-explorer/MetadataViewContainer.js +92 -46
  13. package/es/elements/content-explorer/MetadataViewContainer.js.map +1 -1
  14. package/es/elements/content-explorer/constants.js +4 -2
  15. package/es/elements/content-explorer/constants.js.map +1 -1
  16. package/es/elements/content-explorer/stories/MetadataView.stories.js +3 -25
  17. package/es/elements/content-explorer/stories/MetadataView.stories.js.map +1 -1
  18. package/es/elements/content-explorer/stories/tests/MetadataView-visual.stories.js +4 -16
  19. package/es/elements/content-explorer/stories/tests/MetadataView-visual.stories.js.map +1 -1
  20. package/es/elements/content-explorer/utils.js +12 -0
  21. package/es/elements/content-explorer/utils.js.map +1 -1
  22. package/es/src/elements/common/__mocks__/mockMetadata.d.ts +8 -24
  23. package/es/src/elements/content-explorer/Content.d.ts +4 -3
  24. package/es/src/elements/content-explorer/ContentExplorer.d.ts +8 -3
  25. package/es/src/elements/content-explorer/MetadataQueryAPIHelper.d.ts +11 -2
  26. package/es/src/elements/content-explorer/MetadataQueryBuilder.d.ts +27 -0
  27. package/es/src/elements/content-explorer/MetadataViewContainer.d.ts +8 -4
  28. package/es/src/elements/content-explorer/__tests__/MetadataQueryBuilder.test.d.ts +1 -0
  29. package/es/src/elements/content-explorer/constants.d.ts +4 -2
  30. package/es/src/elements/content-explorer/utils.d.ts +2 -0
  31. package/i18n/bn-IN.js +1 -1
  32. package/i18n/bn-IN.properties +8 -0
  33. package/i18n/da-DK.js +1 -1
  34. package/i18n/da-DK.properties +8 -0
  35. package/i18n/de-DE.js +1 -1
  36. package/i18n/de-DE.properties +8 -0
  37. package/i18n/en-AU.js +1 -1
  38. package/i18n/en-AU.properties +8 -0
  39. package/i18n/en-CA.js +1 -1
  40. package/i18n/en-CA.properties +8 -0
  41. package/i18n/en-GB.js +1 -1
  42. package/i18n/en-GB.properties +8 -0
  43. package/i18n/es-419.js +1 -1
  44. package/i18n/es-419.properties +8 -0
  45. package/i18n/es-ES.js +1 -1
  46. package/i18n/es-ES.properties +8 -0
  47. package/i18n/fi-FI.js +1 -1
  48. package/i18n/fi-FI.properties +8 -0
  49. package/i18n/fr-CA.js +1 -1
  50. package/i18n/fr-CA.properties +8 -0
  51. package/i18n/fr-FR.js +1 -1
  52. package/i18n/fr-FR.properties +8 -0
  53. package/i18n/hi-IN.js +1 -1
  54. package/i18n/hi-IN.properties +8 -0
  55. package/i18n/it-IT.js +1 -1
  56. package/i18n/it-IT.properties +8 -0
  57. package/i18n/ja-JP.js +1 -1
  58. package/i18n/ja-JP.properties +8 -0
  59. package/i18n/ko-KR.js +1 -1
  60. package/i18n/ko-KR.properties +8 -0
  61. package/i18n/nb-NO.js +1 -1
  62. package/i18n/nb-NO.properties +8 -0
  63. package/i18n/nl-NL.js +1 -1
  64. package/i18n/nl-NL.properties +8 -0
  65. package/i18n/pl-PL.js +1 -1
  66. package/i18n/pl-PL.properties +8 -0
  67. package/i18n/pt-BR.js +1 -1
  68. package/i18n/pt-BR.properties +8 -0
  69. package/i18n/ru-RU.js +1 -1
  70. package/i18n/ru-RU.properties +8 -0
  71. package/i18n/sv-SE.js +1 -1
  72. package/i18n/sv-SE.properties +8 -0
  73. package/i18n/tr-TR.js +1 -1
  74. package/i18n/tr-TR.properties +8 -0
  75. package/i18n/zh-CN.js +1 -1
  76. package/i18n/zh-CN.properties +8 -0
  77. package/i18n/zh-TW.js +1 -1
  78. package/i18n/zh-TW.properties +8 -0
  79. package/package.json +3 -3
  80. package/src/elements/common/__mocks__/mockMetadata.ts +7 -11
  81. package/src/elements/content-explorer/Content.tsx +8 -2
  82. package/src/elements/content-explorer/ContentExplorer.tsx +209 -194
  83. package/src/elements/content-explorer/MetadataQueryAPIHelper.ts +111 -5
  84. package/src/elements/content-explorer/MetadataQueryBuilder.ts +194 -0
  85. package/src/elements/content-explorer/MetadataViewContainer.tsx +112 -37
  86. package/src/elements/content-explorer/__tests__/Content.test.tsx +1 -0
  87. package/src/elements/content-explorer/__tests__/ContentExplorer.test.tsx +2 -5
  88. package/src/elements/content-explorer/__tests__/MetadataQueryAPIHelper.test.ts +427 -8
  89. package/src/elements/content-explorer/__tests__/MetadataQueryBuilder.test.ts +535 -0
  90. package/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx +413 -9
  91. package/src/elements/content-explorer/constants.ts +39 -2
  92. package/src/elements/content-explorer/stories/MetadataView.stories.tsx +3 -21
  93. package/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx +2 -13
  94. 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, { type MetadataViewContainerProps } from '../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
- { id: '1', name: 'File 1.txt', type: 'file' },
11
- { id: '2', name: 'File 2.pdf', type: 'file' },
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: ' name',
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.getByRole('button', { name: 'Name' })).toBeInTheDocument();
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({ metadataTemplate: template, actionBarProps: { onFilterSubmit } });
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
- expect(firstCall['role-filter'].value).toEqual(['Developer']);
116
- expect(secondCall['role-filter'].value).toEqual(['Developer', 'Marketing']);
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 default CONTENT_EXPLORER_FOLDER_FIELDS_TO_FETCH;
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
- 'industry-filter': { value: ['Legal'] },
159
+ industry: { value: ['Legal'] },
171
160
  'mimetype-filter': { value: ['boxnoteType', 'documentType', 'threedType'] },
172
- 'role-filter': { value: ['Developer', 'Business Owner', 'Marketing'] },
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
+ };