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.
Files changed (113) hide show
  1. package/dist/explorer.css +1 -1
  2. package/dist/explorer.js +1 -1
  3. package/dist/openwith.js +1 -1
  4. package/dist/picker.js +1 -1
  5. package/dist/preview.js +1 -1
  6. package/dist/sharing.js +1 -1
  7. package/dist/sidebar.js +1 -1
  8. package/dist/uploader.js +1 -1
  9. package/es/api/Metadata.js +98 -13
  10. package/es/api/Metadata.js.flow +110 -12
  11. package/es/api/Metadata.js.map +1 -1
  12. package/es/elements/common/messages.js +16 -0
  13. package/es/elements/common/messages.js.flow +25 -0
  14. package/es/elements/common/messages.js.map +1 -1
  15. package/es/elements/content-explorer/Content.js +5 -2
  16. package/es/elements/content-explorer/Content.js.map +1 -1
  17. package/es/elements/content-explorer/ContentExplorer.js +31 -6
  18. package/es/elements/content-explorer/ContentExplorer.js.map +1 -1
  19. package/es/elements/content-explorer/MetadataQueryAPIHelper.js +164 -10
  20. package/es/elements/content-explorer/MetadataQueryAPIHelper.js.map +1 -1
  21. package/es/elements/content-explorer/MetadataQueryBuilder.js +115 -0
  22. package/es/elements/content-explorer/MetadataQueryBuilder.js.map +1 -0
  23. package/es/elements/content-explorer/MetadataSidePanel.js +40 -14
  24. package/es/elements/content-explorer/MetadataSidePanel.js.map +1 -1
  25. package/es/elements/content-explorer/MetadataViewContainer.js +133 -36
  26. package/es/elements/content-explorer/MetadataViewContainer.js.map +1 -1
  27. package/es/elements/content-explorer/stories/MetadataView.stories.js +3 -25
  28. package/es/elements/content-explorer/stories/MetadataView.stories.js.map +1 -1
  29. package/es/elements/content-explorer/stories/tests/MetadataView-visual.stories.js +65 -29
  30. package/es/elements/content-explorer/stories/tests/MetadataView-visual.stories.js.map +1 -1
  31. package/es/elements/content-explorer/utils.js +140 -12
  32. package/es/elements/content-explorer/utils.js.map +1 -1
  33. package/es/src/elements/common/__mocks__/mockMetadata.d.ts +8 -24
  34. package/es/src/elements/content-explorer/Content.d.ts +4 -3
  35. package/es/src/elements/content-explorer/ContentExplorer.d.ts +19 -6
  36. package/es/src/elements/content-explorer/MetadataQueryAPIHelper.d.ts +22 -3
  37. package/es/src/elements/content-explorer/MetadataQueryBuilder.d.ts +27 -0
  38. package/es/src/elements/content-explorer/MetadataSidePanel.d.ts +6 -3
  39. package/es/src/elements/content-explorer/MetadataViewContainer.d.ts +10 -4
  40. package/es/src/elements/content-explorer/__tests__/MetadataQueryBuilder.test.d.ts +1 -0
  41. package/es/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.d.ts +1 -0
  42. package/es/src/elements/content-explorer/utils.d.ts +9 -3
  43. package/i18n/bn-IN.js +4 -0
  44. package/i18n/bn-IN.properties +12 -0
  45. package/i18n/da-DK.js +4 -0
  46. package/i18n/da-DK.properties +12 -0
  47. package/i18n/de-DE.js +5 -1
  48. package/i18n/de-DE.properties +12 -0
  49. package/i18n/en-AU.js +4 -0
  50. package/i18n/en-AU.properties +12 -0
  51. package/i18n/en-CA.js +4 -0
  52. package/i18n/en-CA.properties +12 -0
  53. package/i18n/en-GB.js +4 -0
  54. package/i18n/en-GB.properties +12 -0
  55. package/i18n/en-US.js +4 -0
  56. package/i18n/en-US.properties +8 -0
  57. package/i18n/en-x-pseudo.js +4 -0
  58. package/i18n/es-419.js +5 -1
  59. package/i18n/es-419.properties +12 -0
  60. package/i18n/es-ES.js +5 -1
  61. package/i18n/es-ES.properties +12 -0
  62. package/i18n/fi-FI.js +4 -0
  63. package/i18n/fi-FI.properties +12 -0
  64. package/i18n/fr-CA.js +4 -0
  65. package/i18n/fr-CA.properties +12 -0
  66. package/i18n/fr-FR.js +4 -0
  67. package/i18n/fr-FR.properties +12 -0
  68. package/i18n/hi-IN.js +4 -0
  69. package/i18n/hi-IN.properties +12 -0
  70. package/i18n/it-IT.js +4 -0
  71. package/i18n/it-IT.properties +12 -0
  72. package/i18n/ja-JP.js +6 -2
  73. package/i18n/ja-JP.properties +14 -2
  74. package/i18n/ko-KR.js +4 -0
  75. package/i18n/ko-KR.properties +12 -0
  76. package/i18n/nb-NO.js +4 -0
  77. package/i18n/nb-NO.properties +12 -0
  78. package/i18n/nl-NL.js +4 -0
  79. package/i18n/nl-NL.properties +12 -0
  80. package/i18n/pl-PL.js +4 -0
  81. package/i18n/pl-PL.properties +12 -0
  82. package/i18n/pt-BR.js +4 -0
  83. package/i18n/pt-BR.properties +12 -0
  84. package/i18n/ru-RU.js +5 -1
  85. package/i18n/ru-RU.properties +12 -0
  86. package/i18n/sv-SE.js +4 -0
  87. package/i18n/sv-SE.properties +12 -0
  88. package/i18n/tr-TR.js +5 -1
  89. package/i18n/tr-TR.properties +12 -0
  90. package/i18n/zh-CN.js +4 -0
  91. package/i18n/zh-CN.properties +12 -0
  92. package/i18n/zh-TW.js +4 -0
  93. package/i18n/zh-TW.properties +12 -0
  94. package/package.json +3 -3
  95. package/src/api/Metadata.js +110 -12
  96. package/src/api/__tests__/Metadata.test.js +120 -0
  97. package/src/elements/common/__mocks__/mockMetadata.ts +7 -11
  98. package/src/elements/common/messages.js +25 -0
  99. package/src/elements/content-explorer/Content.tsx +9 -2
  100. package/src/elements/content-explorer/ContentExplorer.tsx +71 -17
  101. package/src/elements/content-explorer/MetadataQueryAPIHelper.ts +199 -8
  102. package/src/elements/content-explorer/MetadataQueryBuilder.ts +159 -0
  103. package/src/elements/content-explorer/MetadataSidePanel.tsx +55 -14
  104. package/src/elements/content-explorer/MetadataViewContainer.tsx +164 -29
  105. package/src/elements/content-explorer/__tests__/Content.test.tsx +1 -0
  106. package/src/elements/content-explorer/__tests__/ContentExplorer.test.tsx +38 -7
  107. package/src/elements/content-explorer/__tests__/MetadataQueryAPIHelper.test.ts +428 -12
  108. package/src/elements/content-explorer/__tests__/MetadataQueryBuilder.test.ts +419 -0
  109. package/src/elements/content-explorer/__tests__/MetadataSidePanel.test.tsx +145 -3
  110. package/src/elements/content-explorer/__tests__/MetadataViewContainer.test.tsx +413 -9
  111. package/src/elements/content-explorer/stories/MetadataView.stories.tsx +3 -21
  112. package/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx +56 -21
  113. package/src/elements/content-explorer/utils.ts +150 -13
@@ -23,7 +23,7 @@ import partition from 'lodash/partition';
23
23
  import uniq from 'lodash/uniq';
24
24
  import uniqueId from 'lodash/uniqueId';
25
25
  import { getBadItemError, getBadPermissionsError, isUserCorrectableError } from '../utils/error';
26
- import { getTypedFileId } from '../utils/file';
26
+ import { getTypedFileId, getTypedFolderId } from '../utils/file';
27
27
  import { handleOnAbort, formatMetadataFieldValue } from './utils';
28
28
  import File from './File';
29
29
  import { HEADER_CONTENT_TYPE, METADATA_SCOPE_ENTERPRISE, METADATA_SCOPE_GLOBAL, METADATA_TEMPLATE_FETCH_LIMIT, METADATA_TEMPLATE_PROPERTIES, METADATA_TEMPLATE_CLASSIFICATION, METADATA_TEMPLATE_SKILLS, METADATA_SUGGESTIONS_CONFIDENCE_EXPERIMENTAL, FIELD_METADATA_SKILLS, CACHE_PREFIX_METADATA, ERROR_CODE_UPDATE_SKILLS, ERROR_CODE_UPDATE_METADATA, ERROR_CODE_CREATE_METADATA, ERROR_CODE_DELETE_METADATA, ERROR_CODE_FETCH_METADATA, ERROR_CODE_FETCH_METADATA_TEMPLATES, ERROR_CODE_FETCH_SKILLS, ERROR_CODE_FETCH_METADATA_OPTIONS, ERROR_CODE_FETCH_METADATA_SUGGESTIONS, ERROR_CODE_EMPTY_METADATA_SUGGESTIONS, TYPE_FILE, ERROR_CODE_FETCH_METADATA_TAXONOMY_NODE, ERROR_CODE_FETCH_METADATA_TAXONOMY } from '../constants';
@@ -58,6 +58,16 @@ class Metadata extends File {
58
58
  return `${this.getMetadataCacheKey(id)}_classification`;
59
59
  }
60
60
 
61
+ /**
62
+ * Creates a key for the metadata template schema cache
63
+ *
64
+ * @param {string} templateKey - template key
65
+ * @return {string} key
66
+ */
67
+ getMetadataTemplateSchemaCacheKey(templateKey) {
68
+ return `${CACHE_PREFIX_METADATA}template_schema_${templateKey}`;
69
+ }
70
+
61
71
  /**
62
72
  * API URL for metadata
63
73
  *
@@ -73,6 +83,21 @@ class Metadata extends File {
73
83
  return baseUrl;
74
84
  }
75
85
 
86
+ /**
87
+ * API URL for metadata
88
+ *
89
+ * @param {string} id - a Box folder id
90
+ * @param {string} field - metadata field
91
+ * @return {string} base url for files
92
+ */
93
+ getMetadataUrlForFolder(id, scope, template) {
94
+ const baseUrl = `${this.getBaseApiUrl()}/folders/${id}/metadata`;
95
+ if (scope && template) {
96
+ return `${baseUrl}/${scope}/${template}`;
97
+ }
98
+ return baseUrl;
99
+ }
100
+
76
101
  /**
77
102
  * API URL for metadata templates for a scope
78
103
  *
@@ -276,11 +301,24 @@ class Metadata extends File {
276
301
  * @param {string} templateKey - template key
277
302
  * @return {Promise} Promise object of metadata template
278
303
  */
279
- getSchemaByTemplateKey(templateKey) {
304
+ async getSchemaByTemplateKey(templateKey) {
305
+ const cache = this.getCache();
306
+ const key = this.getMetadataTemplateSchemaCacheKey(templateKey);
307
+
308
+ // Return cached value if it exists
309
+ if (cache.has(key)) {
310
+ return cache.get(key);
311
+ }
312
+
313
+ // Fetch from API if not cached
280
314
  const url = this.getMetadataTemplateSchemaUrl(templateKey);
281
- return this.xhr.get({
315
+ const response = await this.xhr.get({
282
316
  url
283
317
  });
318
+
319
+ // Cache the response
320
+ cache.set(key, response);
321
+ return response;
284
322
  }
285
323
 
286
324
  /**
@@ -631,23 +669,29 @@ class Metadata extends File {
631
669
  }
632
670
 
633
671
  /**
634
- * API for patching metadata on file
672
+ * API for patching metadata on item (file/folder)
635
673
  *
636
- * @param {BoxItem} file - File object for which we are changing the description
674
+ * @param {BoxItem} item - File/Folder object for which we are changing the description
637
675
  * @param {Object} template - Metadata template
638
676
  * @param {Array} operations - Array of JSON patch operations
639
677
  * @param {Function} successCallback - Success callback
640
678
  * @param {Function} errorCallback - Error callback
679
+ * @param {boolean} suppressCallbacks - Boolean to decide whether suppress callbacks or not
641
680
  * @return {Promise}
642
681
  */
643
- async updateMetadata(file, template, operations, successCallback, errorCallback) {
682
+ async updateMetadata(item, template, operations, successCallback, errorCallback, suppressCallbacks) {
644
683
  this.errorCode = ERROR_CODE_UPDATE_METADATA;
645
- this.successCallback = successCallback;
646
- this.errorCallback = errorCallback;
684
+ if (!suppressCallbacks) {
685
+ // Only set callbacks when we intend to invoke them for this call
686
+ // so that callers performing bulk operations can suppress per-item callbacks
687
+ this.successCallback = successCallback;
688
+ this.errorCallback = errorCallback;
689
+ }
647
690
  const {
648
691
  id,
649
- permissions
650
- } = file;
692
+ permissions,
693
+ type
694
+ } = item;
651
695
  if (!id || !permissions) {
652
696
  this.errorHandler(getBadItemError());
653
697
  return;
@@ -659,11 +703,11 @@ class Metadata extends File {
659
703
  }
660
704
  try {
661
705
  const metadata = await this.xhr.put({
662
- url: this.getMetadataUrl(id, template.scope, template.templateKey),
706
+ url: type === 'file' ? this.getMetadataUrl(id, template.scope, template.templateKey) : this.getMetadataUrlForFolder(id, template.scope, template.templateKey),
663
707
  headers: {
664
708
  [HEADER_CONTENT_TYPE]: 'application/json-patch+json'
665
709
  },
666
- id: getTypedFileId(id),
710
+ id: type === 'file' ? getTypedFileId(id) : getTypedFolderId(id),
667
711
  data: operations
668
712
  });
669
713
  if (!this.isDestroyed()) {
@@ -676,13 +720,54 @@ class Metadata extends File {
676
720
  instance
677
721
  }) => instance.id === editor.instance.id), 1, editor);
678
722
  }
679
- this.successHandler(editor);
723
+ if (!suppressCallbacks) {
724
+ this.successHandler(editor);
725
+ }
680
726
  }
681
727
  } catch (e) {
728
+ if (suppressCallbacks) {
729
+ // Let the caller decide how to handle errors (e.g., aggregate for bulk operations)
730
+ throw e;
731
+ }
682
732
  this.errorHandler(e);
683
733
  }
684
734
  }
685
735
 
736
+ /**
737
+ * API for bulk patching metadata on items (file/folder)
738
+ *
739
+ * @param {BoxItem[]} items - File/Folder object for which we are changing the description
740
+ * @param {Object} template - Metadata template
741
+ * @param {Array} operations - Array of JSON patch operations for each item
742
+ * @param {Function} successCallback - Success callback
743
+ * @param {Function} errorCallback - Error callback
744
+ * @return {Promise}
745
+ */
746
+ async bulkUpdateMetadata(items, template, operations, successCallback, errorCallback) {
747
+ this.errorCode = ERROR_CODE_UPDATE_METADATA;
748
+ this.successCallback = successCallback;
749
+ this.errorCallback = errorCallback;
750
+ try {
751
+ const updatePromises = items.map(async (item, index) => {
752
+ try {
753
+ // Suppress per-item callbacks; aggregate outcome at the bulk level only
754
+ await this.updateMetadata(item, template, operations[index], successCallback, errorCallback, true);
755
+ } catch (e) {
756
+ // Re-throw to be caught by Promise.all and handled once below
757
+ throw new Error(`Failed to update metadata: ${e.message || e}`);
758
+ }
759
+ });
760
+ await Promise.all(updatePromises);
761
+ if (!this.isDestroyed()) {
762
+ this.successHandler();
763
+ }
764
+ } catch (e) {
765
+ if (!this.isDestroyed()) {
766
+ this.errorHandler(e);
767
+ }
768
+ }
769
+ }
770
+
686
771
  /**
687
772
  * API for patching metadata on file
688
773
  *
@@ -16,7 +16,7 @@ import partition from 'lodash/partition';
16
16
  import uniq from 'lodash/uniq';
17
17
  import uniqueId from 'lodash/uniqueId';
18
18
  import { getBadItemError, getBadPermissionsError, isUserCorrectableError } from '../utils/error';
19
- import { getTypedFileId } from '../utils/file';
19
+ import { getTypedFileId, getTypedFolderId } from '../utils/file';
20
20
  import { handleOnAbort, formatMetadataFieldValue } from './utils';
21
21
  import File from './File';
22
22
  import {
@@ -90,6 +90,16 @@ class Metadata extends File {
90
90
  return `${this.getMetadataCacheKey(id)}_classification`;
91
91
  }
92
92
 
93
+ /**
94
+ * Creates a key for the metadata template schema cache
95
+ *
96
+ * @param {string} templateKey - template key
97
+ * @return {string} key
98
+ */
99
+ getMetadataTemplateSchemaCacheKey(templateKey: string): string {
100
+ return `${CACHE_PREFIX_METADATA}template_schema_${templateKey}`;
101
+ }
102
+
93
103
  /**
94
104
  * API URL for metadata
95
105
  *
@@ -105,6 +115,21 @@ class Metadata extends File {
105
115
  return baseUrl;
106
116
  }
107
117
 
118
+ /**
119
+ * API URL for metadata
120
+ *
121
+ * @param {string} id - a Box folder id
122
+ * @param {string} field - metadata field
123
+ * @return {string} base url for files
124
+ */
125
+ getMetadataUrlForFolder(id: string, scope?: string, template?: string): string {
126
+ const baseUrl = `${this.getBaseApiUrl()}/folders/${id}/metadata`;
127
+ if (scope && template) {
128
+ return `${baseUrl}/${scope}/${template}`;
129
+ }
130
+ return baseUrl;
131
+ }
132
+
108
133
  /**
109
134
  * API URL for metadata templates for a scope
110
135
  *
@@ -337,9 +362,23 @@ class Metadata extends File {
337
362
  * @param {string} templateKey - template key
338
363
  * @return {Promise} Promise object of metadata template
339
364
  */
340
- getSchemaByTemplateKey(templateKey: string): Promise<MetadataTemplateSchemaResponse> {
365
+ async getSchemaByTemplateKey(templateKey: string): Promise<MetadataTemplateSchemaResponse> {
366
+ const cache: APICache = this.getCache();
367
+ const key = this.getMetadataTemplateSchemaCacheKey(templateKey);
368
+
369
+ // Return cached value if it exists
370
+ if (cache.has(key)) {
371
+ return cache.get(key);
372
+ }
373
+
374
+ // Fetch from API if not cached
341
375
  const url = this.getMetadataTemplateSchemaUrl(templateKey);
342
- return this.xhr.get({ url });
376
+ const response = await this.xhr.get({ url });
377
+
378
+ // Cache the response
379
+ cache.set(key, response);
380
+
381
+ return response;
343
382
  }
344
383
 
345
384
  /**
@@ -786,27 +825,33 @@ class Metadata extends File {
786
825
  }
787
826
 
788
827
  /**
789
- * API for patching metadata on file
828
+ * API for patching metadata on item (file/folder)
790
829
  *
791
- * @param {BoxItem} file - File object for which we are changing the description
830
+ * @param {BoxItem} item - File/Folder object for which we are changing the description
792
831
  * @param {Object} template - Metadata template
793
832
  * @param {Array} operations - Array of JSON patch operations
794
833
  * @param {Function} successCallback - Success callback
795
834
  * @param {Function} errorCallback - Error callback
835
+ * @param {boolean} suppressCallbacks - Boolean to decide whether suppress callbacks or not
796
836
  * @return {Promise}
797
837
  */
798
838
  async updateMetadata(
799
- file: BoxItem,
839
+ item: BoxItem,
800
840
  template: MetadataTemplate,
801
841
  operations: JSONPatchOperations,
802
842
  successCallback: Function,
803
843
  errorCallback: ElementsErrorCallback,
844
+ suppressCallbacks?: boolean,
804
845
  ): Promise<void> {
805
846
  this.errorCode = ERROR_CODE_UPDATE_METADATA;
806
- this.successCallback = successCallback;
807
- this.errorCallback = errorCallback;
847
+ if (!suppressCallbacks) {
848
+ // Only set callbacks when we intend to invoke them for this call
849
+ // so that callers performing bulk operations can suppress per-item callbacks
850
+ this.successCallback = successCallback;
851
+ this.errorCallback = errorCallback;
852
+ }
808
853
 
809
- const { id, permissions } = file;
854
+ const { id, permissions, type } = item;
810
855
  if (!id || !permissions) {
811
856
  this.errorHandler(getBadItemError());
812
857
  return;
@@ -821,11 +866,14 @@ class Metadata extends File {
821
866
 
822
867
  try {
823
868
  const metadata = await this.xhr.put({
824
- url: this.getMetadataUrl(id, template.scope, template.templateKey),
869
+ url:
870
+ type === 'file'
871
+ ? this.getMetadataUrl(id, template.scope, template.templateKey)
872
+ : this.getMetadataUrlForFolder(id, template.scope, template.templateKey),
825
873
  headers: {
826
874
  [HEADER_CONTENT_TYPE]: 'application/json-patch+json',
827
875
  },
828
- id: getTypedFileId(id),
876
+ id: type === 'file' ? getTypedFileId(id) : getTypedFolderId(id),
829
877
  data: operations,
830
878
  });
831
879
  if (!this.isDestroyed()) {
@@ -840,13 +888,63 @@ class Metadata extends File {
840
888
  editor,
841
889
  );
842
890
  }
843
- this.successHandler(editor);
891
+ if (!suppressCallbacks) {
892
+ this.successHandler(editor);
893
+ }
844
894
  }
845
895
  } catch (e) {
896
+ if (suppressCallbacks) {
897
+ // Let the caller decide how to handle errors (e.g., aggregate for bulk operations)
898
+ throw e;
899
+ }
846
900
  this.errorHandler(e);
847
901
  }
848
902
  }
849
903
 
904
+ /**
905
+ * API for bulk patching metadata on items (file/folder)
906
+ *
907
+ * @param {BoxItem[]} items - File/Folder object for which we are changing the description
908
+ * @param {Object} template - Metadata template
909
+ * @param {Array} operations - Array of JSON patch operations for each item
910
+ * @param {Function} successCallback - Success callback
911
+ * @param {Function} errorCallback - Error callback
912
+ * @return {Promise}
913
+ */
914
+ async bulkUpdateMetadata(
915
+ items: BoxItem[],
916
+ template: MetadataTemplate,
917
+ operations: JSONPatchOperations[],
918
+ successCallback: Function,
919
+ errorCallback: ElementsErrorCallback,
920
+ ): Promise<void> {
921
+ this.errorCode = ERROR_CODE_UPDATE_METADATA;
922
+ this.successCallback = successCallback;
923
+ this.errorCallback = errorCallback;
924
+
925
+ try {
926
+ const updatePromises = items.map(async (item, index) => {
927
+ try {
928
+ // Suppress per-item callbacks; aggregate outcome at the bulk level only
929
+ await this.updateMetadata(item, template, operations[index], successCallback, errorCallback, true);
930
+ } catch (e) {
931
+ // Re-throw to be caught by Promise.all and handled once below
932
+ throw new Error(`Failed to update metadata: ${e.message || e}`);
933
+ }
934
+ });
935
+
936
+ await Promise.all(updatePromises);
937
+
938
+ if (!this.isDestroyed()) {
939
+ this.successHandler();
940
+ }
941
+ } catch (e) {
942
+ if (!this.isDestroyed()) {
943
+ this.errorHandler(e);
944
+ }
945
+ }
946
+ }
947
+
850
948
  /**
851
949
  * API for patching metadata on file
852
950
  *