box-ui-elements 24.0.0-beta.4 → 24.0.0-beta.6
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/openwith.js +1 -1
- package/dist/picker.js +1 -1
- package/dist/preview.js +1 -1
- package/dist/sharing.js +1 -1
- package/dist/sidebar.js +1 -1
- package/dist/uploader.js +1 -1
- package/es/api/Metadata.js +98 -13
- package/es/api/Metadata.js.flow +110 -12
- package/es/api/Metadata.js.map +1 -1
- package/es/elements/common/messages.js +16 -0
- package/es/elements/common/messages.js.flow +25 -0
- package/es/elements/common/messages.js.map +1 -1
- package/es/elements/content-explorer/Content.js +5 -2
- package/es/elements/content-explorer/Content.js.map +1 -1
- package/es/elements/content-explorer/ContentExplorer.js +31 -6
- package/es/elements/content-explorer/ContentExplorer.js.map +1 -1
- package/es/elements/content-explorer/MetadataQueryAPIHelper.js +164 -10
- package/es/elements/content-explorer/MetadataQueryAPIHelper.js.map +1 -1
- package/es/elements/content-explorer/MetadataQueryBuilder.js +115 -0
- package/es/elements/content-explorer/MetadataQueryBuilder.js.map +1 -0
- package/es/elements/content-explorer/MetadataSidePanel.js +40 -14
- package/es/elements/content-explorer/MetadataSidePanel.js.map +1 -1
- package/es/elements/content-explorer/MetadataViewContainer.js +133 -36
- package/es/elements/content-explorer/MetadataViewContainer.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 +65 -29
- package/es/elements/content-explorer/stories/tests/MetadataView-visual.stories.js.map +1 -1
- package/es/elements/content-explorer/utils.js +140 -12
- 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 +19 -6
- package/es/src/elements/content-explorer/MetadataQueryAPIHelper.d.ts +22 -3
- package/es/src/elements/content-explorer/MetadataQueryBuilder.d.ts +27 -0
- package/es/src/elements/content-explorer/MetadataSidePanel.d.ts +6 -3
- package/es/src/elements/content-explorer/MetadataViewContainer.d.ts +10 -4
- package/es/src/elements/content-explorer/__tests__/MetadataQueryBuilder.test.d.ts +1 -0
- package/es/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.d.ts +1 -0
- package/es/src/elements/content-explorer/utils.d.ts +9 -3
- package/i18n/bn-IN.js +4 -0
- package/i18n/bn-IN.properties +12 -0
- package/i18n/da-DK.js +4 -0
- package/i18n/da-DK.properties +12 -0
- package/i18n/de-DE.js +5 -1
- package/i18n/de-DE.properties +12 -0
- package/i18n/en-AU.js +4 -0
- package/i18n/en-AU.properties +12 -0
- package/i18n/en-CA.js +4 -0
- package/i18n/en-CA.properties +12 -0
- package/i18n/en-GB.js +4 -0
- package/i18n/en-GB.properties +12 -0
- package/i18n/en-US.js +4 -0
- package/i18n/en-US.properties +8 -0
- package/i18n/en-x-pseudo.js +4 -0
- package/i18n/es-419.js +5 -1
- package/i18n/es-419.properties +12 -0
- package/i18n/es-ES.js +5 -1
- package/i18n/es-ES.properties +12 -0
- package/i18n/fi-FI.js +4 -0
- package/i18n/fi-FI.properties +12 -0
- package/i18n/fr-CA.js +4 -0
- package/i18n/fr-CA.properties +12 -0
- package/i18n/fr-FR.js +4 -0
- package/i18n/fr-FR.properties +12 -0
- package/i18n/hi-IN.js +4 -0
- package/i18n/hi-IN.properties +12 -0
- package/i18n/it-IT.js +4 -0
- package/i18n/it-IT.properties +12 -0
- package/i18n/ja-JP.js +6 -2
- package/i18n/ja-JP.properties +14 -2
- package/i18n/ko-KR.js +4 -0
- package/i18n/ko-KR.properties +12 -0
- package/i18n/nb-NO.js +4 -0
- package/i18n/nb-NO.properties +12 -0
- package/i18n/nl-NL.js +4 -0
- package/i18n/nl-NL.properties +12 -0
- package/i18n/pl-PL.js +4 -0
- package/i18n/pl-PL.properties +12 -0
- package/i18n/pt-BR.js +4 -0
- package/i18n/pt-BR.properties +12 -0
- package/i18n/ru-RU.js +5 -1
- package/i18n/ru-RU.properties +12 -0
- package/i18n/sv-SE.js +4 -0
- package/i18n/sv-SE.properties +12 -0
- package/i18n/tr-TR.js +5 -1
- package/i18n/tr-TR.properties +12 -0
- package/i18n/zh-CN.js +4 -0
- package/i18n/zh-CN.properties +12 -0
- package/i18n/zh-TW.js +4 -0
- package/i18n/zh-TW.properties +12 -0
- package/package.json +3 -3
- package/src/api/Metadata.js +110 -12
- package/src/api/__tests__/Metadata.test.js +120 -0
- package/src/elements/common/__mocks__/mockMetadata.ts +7 -11
- package/src/elements/common/messages.js +25 -0
- package/src/elements/content-explorer/Content.tsx +9 -2
- package/src/elements/content-explorer/ContentExplorer.tsx +71 -17
- package/src/elements/content-explorer/MetadataQueryAPIHelper.ts +199 -8
- package/src/elements/content-explorer/MetadataQueryBuilder.ts +159 -0
- package/src/elements/content-explorer/MetadataSidePanel.tsx +55 -14
- package/src/elements/content-explorer/MetadataViewContainer.tsx +164 -29
- package/src/elements/content-explorer/__tests__/Content.test.tsx +1 -0
- package/src/elements/content-explorer/__tests__/ContentExplorer.test.tsx +38 -7
- package/src/elements/content-explorer/__tests__/MetadataQueryAPIHelper.test.ts +428 -12
- package/src/elements/content-explorer/__tests__/MetadataQueryBuilder.test.ts +419 -0
- package/src/elements/content-explorer/__tests__/MetadataSidePanel.test.tsx +145 -3
- package/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx +413 -9
- package/src/elements/content-explorer/stories/MetadataView.stories.tsx +3 -21
- package/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx +56 -21
- package/src/elements/content-explorer/utils.ts +150 -13
|
@@ -9,7 +9,6 @@ const mockMetadata = {
|
|
|
9
9
|
role: ['Business Owner', 'Marketing'],
|
|
10
10
|
$template: 'templateName',
|
|
11
11
|
$parent: 'file_1188899160835',
|
|
12
|
-
name: 'something',
|
|
13
12
|
industry: 'Technology',
|
|
14
13
|
last_contacted_at: '2023-11-16T00:00:00.000Z',
|
|
15
14
|
$version: 9,
|
|
@@ -31,7 +30,6 @@ const mockMetadata = {
|
|
|
31
30
|
role: ['Developer'],
|
|
32
31
|
$template: 'templateName',
|
|
33
32
|
$parent: 'file_1318276254035',
|
|
34
|
-
name: '1',
|
|
35
33
|
industry: 'Technology',
|
|
36
34
|
last_contacted_at: '2023-11-01T00:00:00.000Z',
|
|
37
35
|
$version: 3,
|
|
@@ -94,7 +92,6 @@ const mockMetadata = {
|
|
|
94
92
|
$scope: 'enterprise_0',
|
|
95
93
|
$template: 'templateName',
|
|
96
94
|
$parent: 'file_1812508470016',
|
|
97
|
-
name: 'in folder 3 that doesnt have metadata',
|
|
98
95
|
$version: 0,
|
|
99
96
|
},
|
|
100
97
|
},
|
|
@@ -154,14 +151,6 @@ const mockSchema = {
|
|
|
154
151
|
hidden: false,
|
|
155
152
|
copyInstanceOnItemCopy: false,
|
|
156
153
|
fields: [
|
|
157
|
-
{
|
|
158
|
-
id: '56b6f00e-5db3-4875-a31d-14b20f63c0ea',
|
|
159
|
-
type: 'string',
|
|
160
|
-
key: 'name',
|
|
161
|
-
displayName: 'Name',
|
|
162
|
-
hidden: false,
|
|
163
|
-
description: 'The customer name',
|
|
164
|
-
},
|
|
165
154
|
{
|
|
166
155
|
id: '07d3c06c-5db4-4f3f-821e-19219ba70ed3',
|
|
167
156
|
type: 'date',
|
|
@@ -220,6 +209,13 @@ const mockSchema = {
|
|
|
220
209
|
},
|
|
221
210
|
],
|
|
222
211
|
},
|
|
212
|
+
{
|
|
213
|
+
id: 'c3f87bb0-44df-4689-aafe-b9ed4aecbb01',
|
|
214
|
+
type: 'float',
|
|
215
|
+
key: 'number',
|
|
216
|
+
displayName: 'Merit Count',
|
|
217
|
+
hidden: false,
|
|
218
|
+
},
|
|
223
219
|
],
|
|
224
220
|
};
|
|
225
221
|
|
|
@@ -27,6 +27,11 @@ const messages = defineMessages({
|
|
|
27
27
|
description: 'Generic error label.',
|
|
28
28
|
defaultMessage: 'Error',
|
|
29
29
|
},
|
|
30
|
+
success: {
|
|
31
|
+
id: 'be.success',
|
|
32
|
+
description: 'Generic success label.',
|
|
33
|
+
defaultMessage: 'Success',
|
|
34
|
+
},
|
|
30
35
|
preview: {
|
|
31
36
|
id: 'be.preview',
|
|
32
37
|
description: 'Label for preview action.',
|
|
@@ -1105,6 +1110,26 @@ const messages = defineMessages({
|
|
|
1105
1110
|
}
|
|
1106
1111
|
`,
|
|
1107
1112
|
},
|
|
1113
|
+
multipleValues: {
|
|
1114
|
+
id: 'be.multipleValues',
|
|
1115
|
+
description: 'Display text for field when there are multiple items selected and have different value',
|
|
1116
|
+
defaultMessage: 'Multiple Values',
|
|
1117
|
+
},
|
|
1118
|
+
metadataUpdateErrorNotification: {
|
|
1119
|
+
id: 'be.metadataUpdateErrorNotification',
|
|
1120
|
+
description: 'Text shown in error notification banner',
|
|
1121
|
+
defaultMessage: 'Unable to save changes. Please try again.',
|
|
1122
|
+
},
|
|
1123
|
+
metadataUpdateSuccessNotification: {
|
|
1124
|
+
id: 'be.metadataUpdateSuccessNotification',
|
|
1125
|
+
description: 'Text shown in success notification banner',
|
|
1126
|
+
defaultMessage: `
|
|
1127
|
+
{numSelected, plural,
|
|
1128
|
+
=1 {1 document updated}
|
|
1129
|
+
other {# documents updated}
|
|
1130
|
+
}
|
|
1131
|
+
`,
|
|
1132
|
+
},
|
|
1108
1133
|
});
|
|
1109
1134
|
|
|
1110
1135
|
export default messages;
|
|
@@ -4,7 +4,7 @@ import ItemGrid from '../common/item-grid';
|
|
|
4
4
|
import ItemList from '../common/item-list';
|
|
5
5
|
import ProgressBar from '../common/progress-bar';
|
|
6
6
|
import MetadataBasedItemList from '../../features/metadata-based-view';
|
|
7
|
-
import MetadataViewContainer, { MetadataViewContainerProps } from './MetadataViewContainer';
|
|
7
|
+
import MetadataViewContainer, { ExternalFilterValues, MetadataViewContainerProps } from './MetadataViewContainer';
|
|
8
8
|
import { isFeatureEnabled, type FeatureConfig } from '../common/feature-checking';
|
|
9
9
|
import { VIEW_ERROR, VIEW_METADATA, VIEW_MODE_LIST, VIEW_MODE_GRID, VIEW_SELECTED } from '../../constants';
|
|
10
10
|
import type { ViewMode } from '../common/flowTypes';
|
|
@@ -37,7 +37,11 @@ export interface ContentProps extends Required<ItemEventHandlers>, Required<Item
|
|
|
37
37
|
isTouch: boolean;
|
|
38
38
|
itemActions?: ItemAction[];
|
|
39
39
|
metadataTemplate?: MetadataTemplate;
|
|
40
|
-
metadataViewProps?: Omit<
|
|
40
|
+
metadataViewProps?: Omit<
|
|
41
|
+
MetadataViewContainerProps,
|
|
42
|
+
'hasError' | 'currentCollection' | 'metadataTemplate' | 'onMetadataFilter'
|
|
43
|
+
>;
|
|
44
|
+
onMetadataFilter?: (fields: ExternalFilterValues) => void;
|
|
41
45
|
onMetadataUpdate: (
|
|
42
46
|
item: BoxItem,
|
|
43
47
|
field: string,
|
|
@@ -57,6 +61,7 @@ const Content = ({
|
|
|
57
61
|
gridColumnCount,
|
|
58
62
|
metadataTemplate,
|
|
59
63
|
metadataViewProps,
|
|
64
|
+
onMetadataFilter,
|
|
60
65
|
onMetadataUpdate,
|
|
61
66
|
onSortChange,
|
|
62
67
|
view,
|
|
@@ -89,6 +94,8 @@ const Content = ({
|
|
|
89
94
|
isLoading={percentLoaded !== 100}
|
|
90
95
|
hasError={view === VIEW_ERROR}
|
|
91
96
|
metadataTemplate={metadataTemplate}
|
|
97
|
+
onMetadataFilter={onMetadataFilter}
|
|
98
|
+
onSortChange={onSortChange}
|
|
92
99
|
{...metadataViewProps}
|
|
93
100
|
/>
|
|
94
101
|
)}
|
|
@@ -8,9 +8,9 @@ import getProp from 'lodash/get';
|
|
|
8
8
|
import noop from 'lodash/noop';
|
|
9
9
|
import throttle from 'lodash/throttle';
|
|
10
10
|
import uniqueid from 'lodash/uniqueId';
|
|
11
|
-
import { TooltipProvider } from '@box/blueprint-web';
|
|
12
11
|
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
13
|
-
import type { Selection } from 'react-aria-components';
|
|
12
|
+
import type { Key, Selection } from 'react-aria-components';
|
|
13
|
+
import type { MetadataTemplateField } from '@box/metadata-editor';
|
|
14
14
|
|
|
15
15
|
import CreateFolderDialog from '../common/create-folder-dialog';
|
|
16
16
|
import UploadDialog from '../common/upload-dialog';
|
|
@@ -77,6 +77,7 @@ import {
|
|
|
77
77
|
import type { ViewMode } from '../common/flowTypes';
|
|
78
78
|
import type { ItemAction } from '../common/item';
|
|
79
79
|
import type { Theme } from '../common/theming';
|
|
80
|
+
import type { JSONPatchOperations } from '../../common/types/api';
|
|
80
81
|
import type { MetadataQuery, FieldsToShow } from '../../common/types/metadataQueries';
|
|
81
82
|
import type { MetadataFieldValue, MetadataTemplate } from '../../common/types/metadata';
|
|
82
83
|
import type {
|
|
@@ -94,13 +95,14 @@ import type {
|
|
|
94
95
|
import type { BulkItemAction } from '../common/sub-header/BulkItemActionMenu';
|
|
95
96
|
import type { ContentPreviewProps } from '../content-preview';
|
|
96
97
|
import type { ContentUploaderProps } from '../content-uploader';
|
|
97
|
-
import type { MetadataViewContainerProps } from './MetadataViewContainer';
|
|
98
|
+
import type { ExternalFilterValues, MetadataViewContainerProps } from './MetadataViewContainer';
|
|
98
99
|
|
|
99
100
|
import '../common/fonts.scss';
|
|
100
101
|
import '../common/base.scss';
|
|
101
102
|
import '../common/modal.scss';
|
|
102
103
|
import './ContentExplorer.scss';
|
|
103
104
|
import { withBlueprintModernization } from '../common/withBlueprintModernization';
|
|
105
|
+
import Providers from '../common/Providers';
|
|
104
106
|
|
|
105
107
|
const GRID_VIEW_MAX_COLUMNS = 7;
|
|
106
108
|
const GRID_VIEW_MIN_COLUMNS = 1;
|
|
@@ -125,6 +127,7 @@ export interface ContentExplorerProps {
|
|
|
125
127
|
defaultView?: DefaultView;
|
|
126
128
|
features?: FeatureConfig;
|
|
127
129
|
fieldsToShow?: FieldsToShow;
|
|
130
|
+
hasProviders?: boolean;
|
|
128
131
|
initialPage?: number;
|
|
129
132
|
initialPageSize?: number;
|
|
130
133
|
isLarge?: boolean;
|
|
@@ -138,7 +141,10 @@ export interface ContentExplorerProps {
|
|
|
138
141
|
measureRef?: (ref: Element | null) => void;
|
|
139
142
|
messages?: StringMap;
|
|
140
143
|
metadataQuery?: MetadataQuery;
|
|
141
|
-
metadataViewProps?: Omit<
|
|
144
|
+
metadataViewProps?: Omit<
|
|
145
|
+
MetadataViewContainerProps,
|
|
146
|
+
'hasError' | 'currentCollection' | 'metadataTemplate' | 'onMetadataFilter'
|
|
147
|
+
>;
|
|
142
148
|
onCreate?: (item: BoxItem) => void;
|
|
143
149
|
onDelete?: (item: BoxItem) => void;
|
|
144
150
|
onDownload?: (item: BoxItem) => void;
|
|
@@ -153,7 +159,7 @@ export interface ContentExplorerProps {
|
|
|
153
159
|
rootFolderId?: string;
|
|
154
160
|
sharedLink?: string;
|
|
155
161
|
sharedLinkPassword?: string;
|
|
156
|
-
sortBy?: SortBy;
|
|
162
|
+
sortBy?: SortBy | Key;
|
|
157
163
|
sortDirection?: SortDirection;
|
|
158
164
|
staticHost?: string;
|
|
159
165
|
staticPath?: string;
|
|
@@ -181,6 +187,7 @@ type State = {
|
|
|
181
187
|
isUploadModalOpen: boolean;
|
|
182
188
|
markers: Array<string | null | undefined>;
|
|
183
189
|
metadataTemplate: MetadataTemplate;
|
|
190
|
+
metadataFilters: ExternalFilterValues;
|
|
184
191
|
rootName: string;
|
|
185
192
|
searchQuery: string;
|
|
186
193
|
selected?: BoxItem;
|
|
@@ -211,7 +218,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
211
218
|
|
|
212
219
|
store: LocalStore = new LocalStore();
|
|
213
220
|
|
|
214
|
-
metadataQueryAPIHelper: MetadataQueryAPIHelper;
|
|
221
|
+
metadataQueryAPIHelper: MetadataQueryAPIHelper | MetadataQueryAPIHelperV2;
|
|
215
222
|
|
|
216
223
|
static defaultProps = {
|
|
217
224
|
rootFolderId: DEFAULT_ROOT,
|
|
@@ -306,6 +313,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
306
313
|
isShareModalOpen: false,
|
|
307
314
|
isUploadModalOpen: false,
|
|
308
315
|
markers: [],
|
|
316
|
+
metadataFilters: {},
|
|
309
317
|
metadataTemplate: {},
|
|
310
318
|
rootName: '',
|
|
311
319
|
selectedItemIds: new Set(),
|
|
@@ -389,6 +397,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
389
397
|
*
|
|
390
398
|
* @private
|
|
391
399
|
* @param {Object} metadataQueryCollection - Metadata query response collection
|
|
400
|
+
* @param {Object} metadataTemplate - Metadata template object
|
|
392
401
|
* @return {void}
|
|
393
402
|
*/
|
|
394
403
|
showMetadataQueryResultsSuccessCallback = (
|
|
@@ -440,7 +449,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
440
449
|
*/
|
|
441
450
|
showMetadataQueryResults() {
|
|
442
451
|
const { features, metadataQuery = {} }: ContentExplorerProps = this.props;
|
|
443
|
-
const { currentPageNumber, markers, sortBy, sortDirection }: State = this.state;
|
|
452
|
+
const { currentPageNumber, markers, metadataFilters, sortBy, sortDirection }: State = this.state;
|
|
444
453
|
const metadataQueryClone = cloneDeep(metadataQuery);
|
|
445
454
|
|
|
446
455
|
if (currentPageNumber === 0) {
|
|
@@ -475,6 +484,12 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
475
484
|
];
|
|
476
485
|
|
|
477
486
|
this.metadataQueryAPIHelper = new MetadataQueryAPIHelperV2(this.api);
|
|
487
|
+
this.metadataQueryAPIHelper.fetchMetadataQueryResults(
|
|
488
|
+
metadataQueryClone,
|
|
489
|
+
this.showMetadataQueryResultsSuccessCallback,
|
|
490
|
+
this.errorCallback,
|
|
491
|
+
metadataFilters,
|
|
492
|
+
);
|
|
478
493
|
} else {
|
|
479
494
|
metadataQueryClone.order_by = [
|
|
480
495
|
{
|
|
@@ -483,15 +498,46 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
483
498
|
},
|
|
484
499
|
];
|
|
485
500
|
this.metadataQueryAPIHelper = new MetadataQueryAPIHelper(this.api);
|
|
501
|
+
this.metadataQueryAPIHelper.fetchMetadataQueryResults(
|
|
502
|
+
metadataQueryClone,
|
|
503
|
+
this.showMetadataQueryResultsSuccessCallback,
|
|
504
|
+
this.errorCallback,
|
|
505
|
+
);
|
|
486
506
|
}
|
|
487
|
-
|
|
488
|
-
this.metadataQueryAPIHelper.fetchMetadataQueryResults(
|
|
489
|
-
metadataQueryClone,
|
|
490
|
-
this.showMetadataQueryResultsSuccessCallback,
|
|
491
|
-
this.errorCallback,
|
|
492
|
-
);
|
|
493
507
|
}
|
|
494
508
|
|
|
509
|
+
/**
|
|
510
|
+
* Update selected items' metadata instances based on original and new field values in the metadata instance form
|
|
511
|
+
*
|
|
512
|
+
* @private
|
|
513
|
+
* @return {void}
|
|
514
|
+
*/
|
|
515
|
+
updateMetadataV2 = async (
|
|
516
|
+
items: BoxItem[],
|
|
517
|
+
operations: JSONPatchOperations,
|
|
518
|
+
templateOldFields: MetadataTemplateField[],
|
|
519
|
+
templateNewFields: MetadataTemplateField[],
|
|
520
|
+
successCallback: () => void,
|
|
521
|
+
errorCallback: ErrorCallback,
|
|
522
|
+
) => {
|
|
523
|
+
if (items.length === 1) {
|
|
524
|
+
await this.metadataQueryAPIHelper.updateMetadataWithOperations(
|
|
525
|
+
items[0],
|
|
526
|
+
operations,
|
|
527
|
+
successCallback,
|
|
528
|
+
errorCallback,
|
|
529
|
+
);
|
|
530
|
+
} else {
|
|
531
|
+
await this.metadataQueryAPIHelper.bulkUpdateMetadata(
|
|
532
|
+
items,
|
|
533
|
+
templateOldFields,
|
|
534
|
+
templateNewFields,
|
|
535
|
+
successCallback,
|
|
536
|
+
errorCallback,
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
|
|
495
541
|
/**
|
|
496
542
|
* Resets the collection so that the loading bar starts showing
|
|
497
543
|
*
|
|
@@ -896,7 +942,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
896
942
|
* @param {string} sortDirection - sort direction
|
|
897
943
|
* @return {void}
|
|
898
944
|
*/
|
|
899
|
-
sort = (sortBy: SortBy, sortDirection: SortDirection) => {
|
|
945
|
+
sort = (sortBy: SortBy | Key, sortDirection: SortDirection) => {
|
|
900
946
|
const {
|
|
901
947
|
currentCollection: { id },
|
|
902
948
|
view,
|
|
@@ -1691,6 +1737,10 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
1691
1737
|
this.setState({ isMetadataSidePanelOpen: false });
|
|
1692
1738
|
};
|
|
1693
1739
|
|
|
1740
|
+
filterMetadata = (fields: ExternalFilterValues) => {
|
|
1741
|
+
this.setState({ metadataFilters: fields }, this.refreshCollection);
|
|
1742
|
+
};
|
|
1743
|
+
|
|
1694
1744
|
/**
|
|
1695
1745
|
* Renders the file picker
|
|
1696
1746
|
*
|
|
@@ -1716,6 +1766,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
1716
1766
|
contentUploaderProps,
|
|
1717
1767
|
defaultView,
|
|
1718
1768
|
features,
|
|
1769
|
+
hasProviders,
|
|
1719
1770
|
isMedium,
|
|
1720
1771
|
isSmall,
|
|
1721
1772
|
isTouch,
|
|
@@ -1785,7 +1836,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
1785
1836
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
|
|
1786
1837
|
return (
|
|
1787
1838
|
<Internationalize language={language} messages={messages}>
|
|
1788
|
-
<
|
|
1839
|
+
<Providers hasProviders={hasProviders}>
|
|
1789
1840
|
<div id={this.id} className={styleClassName} ref={measureRef} data-testid="content-explorer">
|
|
1790
1841
|
<ThemingStyles selector={`#${this.id}`} theme={theme} />
|
|
1791
1842
|
<div className="be-app-element" onKeyDown={this.onKeyDown} tabIndex={0}>
|
|
@@ -1844,6 +1895,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
1844
1895
|
onItemRename={this.rename}
|
|
1845
1896
|
onItemSelect={this.select}
|
|
1846
1897
|
onItemShare={this.share}
|
|
1898
|
+
onMetadataFilter={this.filterMetadata}
|
|
1847
1899
|
onMetadataUpdate={this.updateMetadata}
|
|
1848
1900
|
onSortChange={this.sort}
|
|
1849
1901
|
portalElement={this.rootElement}
|
|
@@ -1869,8 +1921,10 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
1869
1921
|
{isDefaultViewMetadata && isMetadataViewV2Feature && isMetadataSidePanelOpen && (
|
|
1870
1922
|
<MetadataSidePanel
|
|
1871
1923
|
currentCollection={currentCollection}
|
|
1872
|
-
onClose={this.closeMetadataSidePanel}
|
|
1873
1924
|
metadataTemplate={metadataTemplate}
|
|
1925
|
+
onClose={this.closeMetadataSidePanel}
|
|
1926
|
+
onUpdate={this.updateMetadataV2}
|
|
1927
|
+
refreshCollection={this.refreshCollection}
|
|
1874
1928
|
selectedItemIds={selectedItemIds}
|
|
1875
1929
|
/>
|
|
1876
1930
|
)}
|
|
@@ -1966,7 +2020,7 @@ class ContentExplorer extends Component<ContentExplorerProps, State> {
|
|
|
1966
2020
|
/>
|
|
1967
2021
|
) : null}
|
|
1968
2022
|
</div>
|
|
1969
|
-
</
|
|
2023
|
+
</Providers>
|
|
1970
2024
|
</Internationalize>
|
|
1971
2025
|
);
|
|
1972
2026
|
/* eslint-enable jsx-a11y/no-static-element-interactions */
|
|
@@ -3,8 +3,11 @@ import find from 'lodash/find';
|
|
|
3
3
|
import getProp from 'lodash/get';
|
|
4
4
|
import includes from 'lodash/includes';
|
|
5
5
|
import isArray from 'lodash/isArray';
|
|
6
|
-
import
|
|
6
|
+
import type { MetadataTemplateField } from '@box/metadata-editor';
|
|
7
|
+
import type { MetadataFieldType } from '@box/metadata-view';
|
|
8
|
+
|
|
7
9
|
import API from '../../api';
|
|
10
|
+
import { areFieldValuesEqual, isEmptyValue, isMultiValuesField } from './utils';
|
|
8
11
|
|
|
9
12
|
import {
|
|
10
13
|
JSON_PATCH_OP_ADD,
|
|
@@ -14,7 +17,7 @@ import {
|
|
|
14
17
|
METADATA_FIELD_TYPE_ENUM,
|
|
15
18
|
METADATA_FIELD_TYPE_MULTISELECT,
|
|
16
19
|
} from '../../common/constants';
|
|
17
|
-
import {
|
|
20
|
+
import { FIELD_ITEM_NAME, FIELD_METADATA, FIELD_EXTENSION, FIELD_PERMISSIONS } from '../../constants';
|
|
18
21
|
|
|
19
22
|
import type { MetadataQuery as MetadataQueryType, MetadataQueryResponseData } from '../../common/types/metadataQueries';
|
|
20
23
|
import type {
|
|
@@ -26,6 +29,15 @@ import type {
|
|
|
26
29
|
} from '../../common/types/metadata';
|
|
27
30
|
import type { ElementsXhrError, JSONPatchOperations } from '../../common/types/api';
|
|
28
31
|
import type { Collection, BoxItem } from '../../common/types/core';
|
|
32
|
+
import {
|
|
33
|
+
getMimeTypeFilter,
|
|
34
|
+
getRangeFilter,
|
|
35
|
+
getSelectFilter,
|
|
36
|
+
getStringFilter,
|
|
37
|
+
mergeQueries,
|
|
38
|
+
mergeQueryParams,
|
|
39
|
+
} from './MetadataQueryBuilder';
|
|
40
|
+
import type { ExternalFilterValues } from './MetadataViewContainer';
|
|
29
41
|
|
|
30
42
|
type SuccessCallback = (metadataQueryCollection: Collection, metadataTemplate: MetadataTemplate) => void;
|
|
31
43
|
type ErrorCallback = (e: ElementsXhrError) => void;
|
|
@@ -57,13 +69,18 @@ export default class MetadataQueryAPIHelper {
|
|
|
57
69
|
oldValue: MetadataFieldValue | null,
|
|
58
70
|
newValue: MetadataFieldValue | null,
|
|
59
71
|
): JSONPatchOperations => {
|
|
72
|
+
// check if two values are the same, return empty operations if so
|
|
73
|
+
if (areFieldValuesEqual(oldValue, newValue)) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
60
77
|
let operation = JSON_PATCH_OP_REPLACE;
|
|
61
78
|
|
|
62
|
-
if (
|
|
79
|
+
if (isEmptyValue(oldValue) && !isEmptyValue(newValue)) {
|
|
63
80
|
operation = JSON_PATCH_OP_ADD;
|
|
64
81
|
}
|
|
65
82
|
|
|
66
|
-
if (oldValue &&
|
|
83
|
+
if (!isEmptyValue(oldValue) && isEmptyValue(newValue)) {
|
|
67
84
|
operation = JSON_PATCH_OP_REMOVE;
|
|
68
85
|
}
|
|
69
86
|
|
|
@@ -170,6 +187,51 @@ export default class MetadataQueryAPIHelper {
|
|
|
170
187
|
return this.api.getMetadataAPI(true).getSchemaByTemplateKey(this.templateKey);
|
|
171
188
|
};
|
|
172
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Generate operations for all fields update in the metadata sidepanel
|
|
192
|
+
*
|
|
193
|
+
* @private
|
|
194
|
+
* @return {JSONPatchOperations}
|
|
195
|
+
*/
|
|
196
|
+
generateOperations = (
|
|
197
|
+
item: BoxItem,
|
|
198
|
+
templateOldFields: MetadataTemplateField[],
|
|
199
|
+
templateNewFields: MetadataTemplateField[],
|
|
200
|
+
): JSONPatchOperations => {
|
|
201
|
+
const { scope, templateKey } = this.metadataTemplate;
|
|
202
|
+
const itemFields = item.metadata[scope][templateKey];
|
|
203
|
+
const operations = templateNewFields.flatMap(newField => {
|
|
204
|
+
let newFieldValue = newField.value;
|
|
205
|
+
const { key, type } = newField;
|
|
206
|
+
// when retrieve value from float type field, it gives a string instead
|
|
207
|
+
if (type === 'float' && newFieldValue !== '') {
|
|
208
|
+
newFieldValue = Number(newFieldValue);
|
|
209
|
+
}
|
|
210
|
+
const oldField = templateOldFields.find(f => f.key === key);
|
|
211
|
+
const oldFieldValue = oldField.value;
|
|
212
|
+
|
|
213
|
+
/*
|
|
214
|
+
Generate operations array based on all the fields' orignal value and the incoming updated value.
|
|
215
|
+
|
|
216
|
+
Edge Case:
|
|
217
|
+
If there are multiple items shared different value for enum or multi-select field, the form will
|
|
218
|
+
return 'Multiple values' as the value. In this case, it needs to generate operation based on the
|
|
219
|
+
actual item's field value.
|
|
220
|
+
*/
|
|
221
|
+
const shouldUseItemFieldValue =
|
|
222
|
+
isMultiValuesField(type as MetadataFieldType, oldFieldValue) &&
|
|
223
|
+
!isMultiValuesField(type as MetadataFieldType, newFieldValue);
|
|
224
|
+
|
|
225
|
+
return this.createJSONPatchOperations(
|
|
226
|
+
key,
|
|
227
|
+
shouldUseItemFieldValue ? itemFields[key] : oldFieldValue,
|
|
228
|
+
newFieldValue,
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
return operations;
|
|
233
|
+
};
|
|
234
|
+
|
|
173
235
|
queryMetadata = (): Promise<MetadataQueryResponseData> => {
|
|
174
236
|
return new Promise((resolve, reject) => {
|
|
175
237
|
this.api.getMetadataQueryAPI().queryMetadata(this.metadataQuery, resolve, reject, { forceFetch: true });
|
|
@@ -180,8 +242,10 @@ export default class MetadataQueryAPIHelper {
|
|
|
180
242
|
metadataQuery: MetadataQueryType,
|
|
181
243
|
successCallback: SuccessCallback,
|
|
182
244
|
errorCallback: ErrorCallback,
|
|
245
|
+
fields?: ExternalFilterValues,
|
|
183
246
|
): Promise<void> => {
|
|
184
|
-
this.metadataQuery = this.verifyQueryFields(metadataQuery);
|
|
247
|
+
this.metadataQuery = this.verifyQueryFields(metadataQuery, fields);
|
|
248
|
+
|
|
185
249
|
return this.queryMetadata()
|
|
186
250
|
.then(this.getTemplateSchemaInfo)
|
|
187
251
|
.then(this.getDataWithTypes)
|
|
@@ -205,26 +269,153 @@ export default class MetadataQueryAPIHelper {
|
|
|
205
269
|
.updateMetadata(file, this.metadataTemplate, operations, successCallback, errorCallback);
|
|
206
270
|
};
|
|
207
271
|
|
|
272
|
+
updateMetadataWithOperations = (
|
|
273
|
+
item: BoxItem,
|
|
274
|
+
operations: JSONPatchOperations,
|
|
275
|
+
successCallback: () => void,
|
|
276
|
+
errorCallback: ErrorCallback,
|
|
277
|
+
): Promise<void> => {
|
|
278
|
+
return this.api
|
|
279
|
+
.getMetadataAPI(true)
|
|
280
|
+
.updateMetadata(item, this.metadataTemplate, operations, successCallback, errorCallback);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
bulkUpdateMetadata = (
|
|
284
|
+
items: BoxItem[],
|
|
285
|
+
templateOldFields: MetadataTemplateField[],
|
|
286
|
+
templateNewFields: MetadataTemplateField[],
|
|
287
|
+
successCallback: () => void,
|
|
288
|
+
errorCallback: ErrorCallback,
|
|
289
|
+
): Promise<void> => {
|
|
290
|
+
const operations: JSONPatchOperations = [];
|
|
291
|
+
items.forEach(item => {
|
|
292
|
+
const operation = this.generateOperations(item, templateOldFields, templateNewFields);
|
|
293
|
+
operations.push(operation);
|
|
294
|
+
});
|
|
295
|
+
return this.api
|
|
296
|
+
.getMetadataAPI(true)
|
|
297
|
+
.bulkUpdateMetadata(items, this.metadataTemplate, operations, successCallback, errorCallback);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
buildMetadataQueryParams = (filters: ExternalFilterValues) => {
|
|
301
|
+
let argIndex = 0;
|
|
302
|
+
let queries: string[] = [];
|
|
303
|
+
let queryParams: { [key: string]: number | Date | string } = {};
|
|
304
|
+
|
|
305
|
+
if (filters) {
|
|
306
|
+
Object.keys(filters).forEach(key => {
|
|
307
|
+
const filter = filters[key];
|
|
308
|
+
if (!filter) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const { fieldType, value } = filter;
|
|
313
|
+
|
|
314
|
+
switch (fieldType) {
|
|
315
|
+
case 'date':
|
|
316
|
+
case 'float': {
|
|
317
|
+
if (typeof value === 'object' && value !== null && 'range' in value) {
|
|
318
|
+
const result = getRangeFilter(value, key, argIndex);
|
|
319
|
+
queryParams = mergeQueryParams(queryParams, result.queryParams);
|
|
320
|
+
queries = mergeQueries(queries, result.queries);
|
|
321
|
+
argIndex += result.keysGenerated;
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
case 'enum':
|
|
327
|
+
case 'multiSelect': {
|
|
328
|
+
const arrayValue = Array.isArray(value) ? value.map(v => String(v)) : [String(value)];
|
|
329
|
+
let result;
|
|
330
|
+
if (key === 'mimetype-filter') {
|
|
331
|
+
result = getMimeTypeFilter(arrayValue, key, argIndex);
|
|
332
|
+
} else {
|
|
333
|
+
result = getSelectFilter(arrayValue, key, argIndex);
|
|
334
|
+
}
|
|
335
|
+
queryParams = mergeQueryParams(queryParams, result.queryParams);
|
|
336
|
+
queries = mergeQueries(queries, result.queries);
|
|
337
|
+
argIndex += result.keysGenerated;
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
case 'string': {
|
|
342
|
+
if (value && value[0]) {
|
|
343
|
+
const result = getStringFilter(value[0], key, argIndex);
|
|
344
|
+
queryParams = mergeQueryParams(queryParams, result.queryParams);
|
|
345
|
+
queries = mergeQueries(queries, result.queries);
|
|
346
|
+
argIndex += result.keysGenerated;
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
default:
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const query = queries.reduce((acc, curr, index) => {
|
|
358
|
+
if (index > 0) {
|
|
359
|
+
acc += ` AND ${curr}`;
|
|
360
|
+
} else {
|
|
361
|
+
acc = curr;
|
|
362
|
+
}
|
|
363
|
+
return acc;
|
|
364
|
+
}, '');
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
queryParams,
|
|
368
|
+
query,
|
|
369
|
+
};
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
mergeQuery = (customQuery: string, filterQuery: string): string => {
|
|
373
|
+
if (!customQuery) {
|
|
374
|
+
return filterQuery;
|
|
375
|
+
}
|
|
376
|
+
if (!filterQuery) {
|
|
377
|
+
return customQuery;
|
|
378
|
+
}
|
|
379
|
+
// Merge queries with AND operator
|
|
380
|
+
return `${customQuery} AND ${filterQuery}`;
|
|
381
|
+
};
|
|
382
|
+
|
|
208
383
|
/**
|
|
209
384
|
* Verify that the metadata query has required fields and update it if necessary
|
|
210
385
|
* For a file item, default fields included in the response are "type", "id", "etag"
|
|
211
386
|
*
|
|
212
387
|
* @param {MetadataQueryType} metadataQuery metadata query object
|
|
388
|
+
* @param {ExternalFilterValues} [fields] optional filter values to apply to the metadata query
|
|
213
389
|
* @return {MetadataQueryType} updated metadata query object with required fields
|
|
214
390
|
*/
|
|
215
|
-
verifyQueryFields = (metadataQuery: MetadataQueryType): MetadataQueryType => {
|
|
391
|
+
verifyQueryFields = (metadataQuery: MetadataQueryType, fields?: ExternalFilterValues): MetadataQueryType => {
|
|
216
392
|
const clonedQuery = cloneDeep(metadataQuery);
|
|
217
393
|
const clonedFields = isArray(clonedQuery.fields) ? clonedQuery.fields : [];
|
|
218
394
|
|
|
395
|
+
if (fields) {
|
|
396
|
+
const { query: filterQuery, queryParams: filteredQueryParams } = this.buildMetadataQueryParams(fields);
|
|
397
|
+
const { query: customQuery, query_params: customQueryParams } = clonedQuery;
|
|
398
|
+
const query = this.mergeQuery(customQuery, filterQuery);
|
|
399
|
+
const queryParams = mergeQueryParams(filteredQueryParams, customQueryParams);
|
|
400
|
+
if (query) {
|
|
401
|
+
clonedQuery.query = query;
|
|
402
|
+
clonedQuery.query_params = queryParams;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
219
405
|
// Make sure the query fields array has "name" field which is necessary to display info.
|
|
220
|
-
if (!clonedFields.includes(
|
|
221
|
-
clonedFields.push(
|
|
406
|
+
if (!clonedFields.includes(FIELD_ITEM_NAME)) {
|
|
407
|
+
clonedFields.push(FIELD_ITEM_NAME);
|
|
222
408
|
}
|
|
223
409
|
|
|
224
410
|
if (!clonedFields.includes(FIELD_EXTENSION)) {
|
|
225
411
|
clonedFields.push(FIELD_EXTENSION);
|
|
226
412
|
}
|
|
227
413
|
|
|
414
|
+
// This field is necessary to check if the user has permission to update metadata
|
|
415
|
+
if (!clonedFields.includes(FIELD_PERMISSIONS)) {
|
|
416
|
+
clonedFields.push(FIELD_PERMISSIONS);
|
|
417
|
+
}
|
|
418
|
+
|
|
228
419
|
clonedQuery.fields = clonedFields;
|
|
229
420
|
|
|
230
421
|
return clonedQuery;
|