From 88e48b9304c3a5e6d8b88bf5137430f1ae1188b6 Mon Sep 17 00:00:00 2001 From: kajarosz Date: Wed, 14 Aug 2024 16:07:46 +0200 Subject: [PATCH 01/13] feat: Extend getMetadata return data required by Metadata Redesign --- src/api/Metadata.js | 121 ++++++++++++++- src/api/__tests__/Metadata.test.js | 240 +++++++++++++++++++++++++++++ src/common/types/metadata.js | 26 ++++ 3 files changed, 379 insertions(+), 8 deletions(-) diff --git a/src/api/Metadata.js b/src/api/Metadata.js index 2d378a1c04..ba79ffc595 100644 --- a/src/api/Metadata.js +++ b/src/api/Metadata.js @@ -41,6 +41,8 @@ import type { MetadataEditor, MetadataFields, MetadataSuggestion, + MetadataTemplateInstance, + MetadataInstanceTemplateFields, } from '../common/types/metadata'; import type { BoxItem } from '../common/types/core'; import type APICache from '../utils/Cache'; @@ -354,6 +356,96 @@ class Metadata extends File { return editors; } + /** + * Utility to concat instance and template into one entity. + * + * @param {Object} instance - metadata instance + * @param {Object} template - metadata template + * @return {Object} metadata template instance + */ + createTemplateInstance(instance: MetadataInstanceV2, template: MetadataTemplate): MetadataTemplateInstance { + const metadataFields: MetadataInstanceTemplateFields = {}; + + if (template.templateKey !== METADATA_TEMPLATE_PROPERTIES) { + // Get Metadata Fields for Instances created from predefinied template + const templateFields = template.fields; + templateFields.map(async field => { + metadataFields[field.key] = { + description: field.description, + displayName: field.displayName, + hidden: field.hidden || field.isHidden, + id: field.id, + key: field.key, + options: field.options, + type: field.type, + value: instance[field.key], + }; + }); + } else { + // Get Metadata Fields for Custom Instances + Object.keys(instance).forEach(key => { + if (!key.startsWith('$')) { + // $FlowFixMe + metadataFields[key] = { + key, + type: 'string', + value: instance[key], + }; + } + }); + } + + return { + displayName: template.displayName, + hidden: template.hidden || template.isHidden, + id: template.id, + metadataFields, + scope: template.scope, + templateKey: template.templateKey, + }; + } + + /** + * Creates and returns metadata entities. + * + * @param {string} id - Box file id + * @param {Array} instances - metadata instances + * @param {Object} customPropertiesTemplate - custom properties template + * @param {Array} enterpriseTemplates - enterprise templates + * @param {Array} globalTemplates - global templates + * @return {Array} metadata editors + */ + async getTemplateInstances( + id: string, + instances: Array, + customPropertiesTemplate: MetadataTemplate, + enterpriseTemplates: Array, + globalTemplates: Array, + ): Promise> { + // Get all usable templates for metadata instances + const templates: Array = [customPropertiesTemplate].concat( + enterpriseTemplates, + globalTemplates, + ); + + // Filter out classification + const filteredInstances = this.extractClassification(id, instances); + + // Create Metadata Tmplate Instance from each instance + const templateInstances: Array = []; + + await Promise.all( + filteredInstances.map(async instance => { + const template: ?MetadataTemplate = await this.getTemplateForInstance(id, instance, templates); + if (template) { + templateInstances.push(this.createTemplateInstance(instance, template)); + } + }), + ); + + return templateInstances; + } + /** * API for getting metadata editors * @@ -370,6 +462,7 @@ class Metadata extends File { errorCallback: ElementsErrorCallback, hasMetadataFeature: boolean, options: RequestOptions = {}, + isMetadataRedesign: boolean = false, ): Promise { const { id, permissions, is_externally_owned }: BoxItem = file; this.errorCode = ERROR_CODE_FETCH_METADATA; @@ -407,17 +500,29 @@ class Metadata extends File { hasMetadataFeature ? this.getTemplates(id, METADATA_SCOPE_ENTERPRISE) : Promise.resolve([]), ]); - const editors = await this.getEditors( - id, - instances, - customPropertiesTemplate, - enterpriseTemplates, - globalTemplates, - !!permissions.can_upload, - ); + const templateInstances = isMetadataRedesign + ? await this.getTemplateInstances( + id, + instances, + customPropertiesTemplate, + enterpriseTemplates, + globalTemplates, + ) + : []; + const editors = !isMetadataRedesign + ? await this.getEditors( + id, + instances, + customPropertiesTemplate, + enterpriseTemplates, + globalTemplates, + !!permissions.can_upload, + ) + : []; const metadata = { editors, + templateInstances, templates: this.getUserAddableTemplates( customPropertiesTemplate, enterpriseTemplates, diff --git a/src/api/__tests__/Metadata.test.js b/src/api/__tests__/Metadata.test.js index 7851df8d3b..b8a6e0155b 100644 --- a/src/api/__tests__/Metadata.test.js +++ b/src/api/__tests__/Metadata.test.js @@ -161,6 +161,95 @@ describe('api/Metadata', () => { }); }); + describe('createTemplateInstance()', () => { + test('should return Metadata Template Instance', () => { + expect( + metadata.createTemplateInstance( + { + $id: '321', + $template: '', + testStringField: 'This is string', + testFloatField: '2.1', + }, + { + displayName: 'Test template', + fields: [ + { + description: 'Test description', + displayName: 'Test string field', + id: '123', + key: 'testStringField', + type: 'string', + }, + { + description: 'Test description', + displayName: 'Test float field', + id: '456', + key: 'testFloatField', + type: 'float', + }, + ], + id: '123456', + templateKey: 'instance_from_template', + }, + ), + ).toEqual({ + displayName: 'Test template', + hidden: undefined, + id: '123456', + metadataFields: { + testFloatField: { + description: 'Test description', + displayName: 'Test float field', + id: '456', + key: 'testFloatField', + type: 'float', + value: '2.1', + }, + testStringField: { + description: 'Test description', + displayName: 'Test string field', + id: '123', + key: 'testStringField', + type: 'string', + value: 'This is string', + }, + }, + templateKey: 'instance_from_template', + }); + }); + + test('should return Metadata Template Instance for Custom Metadata', () => { + expect( + metadata.createTemplateInstance( + { + $id: '321', + $template: '', + testCustomField: 'This is string', + }, + { + displayName: 'Test template', + fields: [], + id: '123456', + templateKey: 'properties', + }, + ), + ).toEqual({ + displayName: 'Test template', + hidden: undefined, + id: '123456', + metadataFields: { + testCustomField: { + key: 'testCustomField', + type: 'string', + value: 'This is string', + }, + }, + templateKey: 'properties', + }); + }); + }); + describe('getTemplates()', () => { test('should return templates with enterprise scope', async () => { const templatesFromServer = [ @@ -537,6 +626,89 @@ describe('api/Metadata', () => { }); }); + describe('getTemplateInstances()', () => { + test('should build and return Metadata Template Instances with valid data', async () => { + const instances = [ + { + $id: '1', + $scope: 'global', + $template: 'global1', + }, + { + $id: '2', + $scope: 'enterprise', + $template: 'enterprise2', + }, + { + $id: '3', + $scope: 'enterprise', + $template: 'enterprise3', + }, + { + $id: '4', + $scope: 'global', + $template: 'custom', + }, + { + $id: '5', + $scope: 'global', + $template: 'bogus', + }, + ]; + + metadata.extractClassification = jest.fn().mockReturnValueOnce(instances); + metadata.createTemplateInstance = jest + .fn() + .mockReturnValueOnce('templateInstance1') + .mockReturnValueOnce('templateInstance2') + .mockReturnValueOnce('templateInstance3') + .mockReturnValueOnce('templateInstance4'); + metadata.getTemplateForInstance = jest + .fn() + .mockResolvedValueOnce('template1') + .mockResolvedValueOnce('template2') + .mockResolvedValueOnce('template3') + .mockResolvedValueOnce('template4') + .mockResolvedValueOnce(); + + const templateInstances = await metadata.getTemplateInstances('id', instances, {}, [], []); + expect(templateInstances).toEqual([ + 'templateInstance1', + 'templateInstance2', + 'templateInstance3', + 'templateInstance4', + ]); + expect(metadata.extractClassification).toBeCalledWith('id', instances); + + expect(metadata.createTemplateInstance).toBeCalledTimes(4); + expect(metadata.createTemplateInstance.mock.calls[0][0]).toBe(instances[0]); + expect(metadata.createTemplateInstance.mock.calls[0][1]).toBe('template1'); + expect(metadata.createTemplateInstance.mock.calls[1][0]).toBe(instances[1]); + expect(metadata.createTemplateInstance.mock.calls[1][1]).toBe('template2'); + expect(metadata.createTemplateInstance.mock.calls[2][0]).toBe(instances[2]); + expect(metadata.createTemplateInstance.mock.calls[2][1]).toBe('template3'); + expect(metadata.createTemplateInstance.mock.calls[3][0]).toBe(instances[3]); + expect(metadata.createTemplateInstance.mock.calls[3][1]).toBe('template4'); + + expect(metadata.getTemplateForInstance).toBeCalledTimes(5); + expect(metadata.getTemplateForInstance.mock.calls[0][0]).toBe('id'); + expect(metadata.getTemplateForInstance.mock.calls[0][1]).toBe(instances[0]); + expect(metadata.getTemplateForInstance.mock.calls[0][2]).toEqual([{}]); + expect(metadata.getTemplateForInstance.mock.calls[1][0]).toBe('id'); + expect(metadata.getTemplateForInstance.mock.calls[1][1]).toBe(instances[1]); + expect(metadata.getTemplateForInstance.mock.calls[1][2]).toEqual([{}]); + expect(metadata.getTemplateForInstance.mock.calls[2][0]).toBe('id'); + expect(metadata.getTemplateForInstance.mock.calls[2][1]).toBe(instances[2]); + expect(metadata.getTemplateForInstance.mock.calls[2][2]).toEqual([{}]); + expect(metadata.getTemplateForInstance.mock.calls[3][0]).toBe('id'); + expect(metadata.getTemplateForInstance.mock.calls[3][1]).toBe(instances[3]); + expect(metadata.getTemplateForInstance.mock.calls[3][2]).toEqual([{}]); + expect(metadata.getTemplateForInstance.mock.calls[4][0]).toBe('id'); + expect(metadata.getTemplateForInstance.mock.calls[4][1]).toBe(instances[4]); + expect(metadata.getTemplateForInstance.mock.calls[4][2]).toEqual([{}]); + }); + }); + describe('getMetadata()', () => { test('should call error callback with a bad item error when no id', () => { jest.spyOn(ErrorUtil, 'getBadItemError').mockReturnValueOnce('error'); @@ -577,6 +749,7 @@ describe('api/Metadata', () => { metadata.getMetadataCacheKey = jest.fn().mockReturnValueOnce('cache_id_metadata'); metadata.getInstances = jest.fn().mockResolvedValueOnce('instances'); metadata.getEditors = jest.fn().mockResolvedValueOnce('editors'); + metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); @@ -590,6 +763,7 @@ describe('api/Metadata', () => { expect(metadata.getTemplates).not.toHaveBeenCalledWith(); expect(metadata.getTemplates).not.toHaveBeenCalledWith(); expect(metadata.getEditors).not.toHaveBeenCalledWith(); + expect(metadata.getTemplateInstances).not.toHaveBeenCalledWith(); expect(metadata.getUserAddableTemplates).not.toHaveBeenCalledWith(); expect(metadata.successHandler).toHaveBeenCalledWith('cached_metadata'); expect(metadata.successHandler).toHaveBeenCalledTimes(1); @@ -614,6 +788,7 @@ describe('api/Metadata', () => { metadata.getMetadataCacheKey = jest.fn().mockReturnValueOnce('cache_id_metadata'); metadata.getInstances = jest.fn().mockResolvedValueOnce('instances'); metadata.getEditors = jest.fn().mockResolvedValueOnce('editors'); + metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); @@ -634,6 +809,7 @@ describe('api/Metadata', () => { 'global', true, ); + expect(metadata.getTemplateInstances).not.toHaveBeenCalled(); expect(metadata.getUserAddableTemplates).toHaveBeenCalledWith('custom', 'enterprise', true, true); expect(metadata.successHandler).toHaveBeenCalledWith({ editors: 'editors', @@ -645,6 +821,60 @@ describe('api/Metadata', () => { templates: 'templates', }); }); + test('should make request and update cache and call success handler for Metadata Redesign', async () => { + const file = { + id: 'id', + is_externally_owned: true, + permissions: { + can_upload: true, + }, + }; + + const cache = new Cache(); + + metadata.errorHandler = jest.fn(); + metadata.successHandler = jest.fn(); + metadata.isDestroyed = jest.fn().mockReturnValueOnce(false); + metadata.getCache = jest.fn().mockReturnValueOnce(cache); + metadata.getMetadataCacheKey = jest.fn().mockReturnValueOnce('cache_id_metadata'); + metadata.getInstances = jest.fn().mockResolvedValueOnce('instances'); + metadata.getEditors = jest.fn().mockResolvedValueOnce('editors'); + metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); + metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); + metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); + metadata.getTemplates = jest + .fn() + .mockResolvedValueOnce('global') + .mockResolvedValueOnce('enterprise'); + + await metadata.getMetadata(file, jest.fn(), jest.fn(), true, true); + + expect(metadata.isDestroyed).toHaveBeenCalled(); + expect(metadata.getCache).toHaveBeenCalled(); + expect(metadata.getMetadataCacheKey).toHaveBeenCalledWith(file.id); + expect(metadata.getInstances).toHaveBeenCalledWith(file.id); + expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); + expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'enterprise'); + expect(metadata.getEditors).not.toHaveBeenCalled(); + expect(metadata.getTemplateInstances).toHaveBeenCalled( + file.id, + 'instances', + 'custom', + 'enterprise', + 'global', + ); + expect(metadata.getUserAddableTemplates).toHaveBeenCalledWith('custom', 'enterprise', true, true); + expect(metadata.successHandler).toHaveBeenCalledWith({ + editors: 'editors', + templates: 'templates', + }); + expect(metadata.errorHandler).not.toHaveBeenCalled(); + expect(cache.get('cache_id_metadata')).toEqual({ + editors: 'editors', + templates: 'templates', + }); + }); + test('should make request and update cache and call success handler after returning cached value when refreshCache is true', async () => { const file = { id: 'id', @@ -664,6 +894,7 @@ describe('api/Metadata', () => { metadata.getMetadataCacheKey = jest.fn().mockReturnValueOnce('cache_id_metadata'); metadata.getInstances = jest.fn().mockResolvedValueOnce('instances'); metadata.getEditors = jest.fn().mockResolvedValueOnce('editors'); + metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); @@ -684,6 +915,7 @@ describe('api/Metadata', () => { 'global', true, ); + expect(metadata.getTemplateInstances).not.toHaveBeenCalled(); expect(metadata.getUserAddableTemplates).toHaveBeenCalledWith('custom', 'enterprise', true, true); expect(metadata.successHandler).toHaveBeenCalledTimes(2); expect(metadata.successHandler).toHaveBeenCalledWith('cached_metadata'); @@ -716,6 +948,7 @@ describe('api/Metadata', () => { metadata.getMetadataCacheKey = jest.fn().mockReturnValueOnce('cache_id_metadata'); metadata.getInstances = jest.fn().mockResolvedValueOnce('instances'); metadata.getEditors = jest.fn().mockResolvedValueOnce('editors'); + metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); @@ -736,6 +969,7 @@ describe('api/Metadata', () => { 'global', true, ); + expect(metadata.getTemplateInstances).not.toHaveBeenCalled(); expect(metadata.getUserAddableTemplates).toHaveBeenCalledWith('custom', 'enterprise', true, true); expect(metadata.successHandler).toHaveBeenCalledTimes(1); expect(metadata.successHandler).not.toHaveBeenCalledWith('cached_metadata'); @@ -767,6 +1001,7 @@ describe('api/Metadata', () => { metadata.getMetadataCacheKey = jest.fn().mockReturnValueOnce('cache_id_metadata'); metadata.getInstances = jest.fn().mockResolvedValueOnce('instances'); metadata.getEditors = jest.fn().mockResolvedValueOnce('editors'); + metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); @@ -780,6 +1015,7 @@ describe('api/Metadata', () => { expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).not.toHaveBeenCalledWith(file.id, 'enterprise'); expect(metadata.getEditors).toHaveBeenCalledWith(file.id, 'instances', 'custom', [], 'global', true); + expect(metadata.getTemplateInstances).not.toHaveBeenCalled(); expect(metadata.getUserAddableTemplates).toHaveBeenCalledWith('custom', [], false, true); expect(metadata.successHandler).toHaveBeenCalledTimes(1); expect(metadata.successHandler).toHaveBeenCalledWith({ @@ -810,6 +1046,7 @@ describe('api/Metadata', () => { metadata.getMetadataCacheKey = jest.fn().mockReturnValueOnce('cache_id_metadata'); metadata.getInstances = jest.fn().mockRejectedValueOnce('error'); metadata.getEditors = jest.fn().mockResolvedValueOnce('editors'); + metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); @@ -823,6 +1060,7 @@ describe('api/Metadata', () => { expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'enterprise'); expect(metadata.getEditors).not.toHaveBeenCalled(); + expect(metadata.getTemplateInstances).not.toHaveBeenCalled(); expect(metadata.getUserAddableTemplates).not.toHaveBeenCalled(); expect(metadata.successHandler).not.toHaveBeenCalled(); expect(metadata.errorHandler).toHaveBeenCalledWith('error'); @@ -847,6 +1085,7 @@ describe('api/Metadata', () => { metadata.getMetadataCacheKey = jest.fn().mockReturnValueOnce('cache_id_metadata'); metadata.getInstances = jest.fn().mockResolvedValueOnce('instances'); metadata.getEditors = jest.fn().mockResolvedValueOnce('editors'); + metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); @@ -867,6 +1106,7 @@ describe('api/Metadata', () => { 'global', true, ); + expect(metadata.getTemplateInstances).not.toHaveBeenCalled(); expect(metadata.getUserAddableTemplates).toHaveBeenCalled(); expect(metadata.successHandler).not.toHaveBeenCalled(); expect(metadata.errorHandler).not.toHaveBeenCalled(); diff --git a/src/common/types/metadata.js b/src/common/types/metadata.js index bb89f81174..f7c6da8a4a 100644 --- a/src/common/types/metadata.js +++ b/src/common/types/metadata.js @@ -114,7 +114,33 @@ type MetadataSuggestion = { suggestions: { [key: string]: string | number | string[] }, }; +type MetadataInstanceTemplateField = { + description?: string, + displayName: string, + hidden?: boolean, + id: string, + isHidden?: boolean, + key: string, // V2 + options?: Array, // V3 + type: MetadataFieldType, + value: string, +}; + +type MetadataInstanceTemplateFields = { [string]: MetadataInstanceTemplateField }; + +type MetadataTemplateInstance = { + displayName?: string, + hidden?: boolean, + id: string, + metadataFields: MetadataInstanceTemplateFields, + scope: string, + templateKey: string, +}; + export type { + MetadataInstanceTemplateField, + MetadataInstanceTemplateFields, + MetadataTemplateInstance, MetadataFieldType, MetadataTemplateFieldOption, MetadataTemplateField, From acfce51629d5b5f67fc53b45bece7f0b8a738bf5 Mon Sep 17 00:00:00 2001 From: kajarosz Date: Wed, 14 Aug 2024 16:21:13 +0200 Subject: [PATCH 02/13] feat: Update tests --- src/api/__tests__/Metadata.test.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/api/__tests__/Metadata.test.js b/src/api/__tests__/Metadata.test.js index b8a6e0155b..34b83655c6 100644 --- a/src/api/__tests__/Metadata.test.js +++ b/src/api/__tests__/Metadata.test.js @@ -813,11 +813,13 @@ describe('api/Metadata', () => { expect(metadata.getUserAddableTemplates).toHaveBeenCalledWith('custom', 'enterprise', true, true); expect(metadata.successHandler).toHaveBeenCalledWith({ editors: 'editors', + templateInstances: [], templates: 'templates', }); expect(metadata.errorHandler).not.toHaveBeenCalled(); expect(cache.get('cache_id_metadata')).toEqual({ editors: 'editors', + templateInstances: [], templates: 'templates', }); }); @@ -865,12 +867,14 @@ describe('api/Metadata', () => { ); expect(metadata.getUserAddableTemplates).toHaveBeenCalledWith('custom', 'enterprise', true, true); expect(metadata.successHandler).toHaveBeenCalledWith({ - editors: 'editors', + editors: [], + templateInstances: 'templateInstances', templates: 'templates', }); expect(metadata.errorHandler).not.toHaveBeenCalled(); expect(cache.get('cache_id_metadata')).toEqual({ - editors: 'editors', + editors: [], + templateInstances: 'templateInstances', templates: 'templates', }); }); @@ -921,11 +925,13 @@ describe('api/Metadata', () => { expect(metadata.successHandler).toHaveBeenCalledWith('cached_metadata'); expect(metadata.successHandler).toHaveBeenCalledWith({ editors: 'editors', + templateInstances: [], templates: 'templates', }); expect(metadata.errorHandler).not.toHaveBeenCalled(); expect(cache.get('cache_id_metadata')).toEqual({ editors: 'editors', + templateInstances: [], templates: 'templates', }); }); @@ -975,11 +981,13 @@ describe('api/Metadata', () => { expect(metadata.successHandler).not.toHaveBeenCalledWith('cached_metadata'); expect(metadata.successHandler).toHaveBeenCalledWith({ editors: 'editors', + templateInstances: [], templates: 'templates', }); expect(metadata.errorHandler).not.toHaveBeenCalled(); expect(cache.get('cache_id_metadata')).toEqual({ editors: 'editors', + templateInstances: [], templates: 'templates', }); }); @@ -1020,11 +1028,13 @@ describe('api/Metadata', () => { expect(metadata.successHandler).toHaveBeenCalledTimes(1); expect(metadata.successHandler).toHaveBeenCalledWith({ editors: 'editors', + templateInstances: [], templates: 'templates', }); expect(metadata.errorHandler).not.toHaveBeenCalled(); expect(cache.get('cache_id_metadata')).toEqual({ editors: 'editors', + templateInstances: [], templates: 'templates', }); }); @@ -1112,6 +1122,7 @@ describe('api/Metadata', () => { expect(metadata.errorHandler).not.toHaveBeenCalled(); expect(cache.get('cache_id_metadata')).toEqual({ editors: 'editors', + templateInstances: [], templates: 'templates', }); }); From c4ba81c179fcf0da9f6f29435189400befea5fbc Mon Sep 17 00:00:00 2001 From: kajarosz Date: Wed, 14 Aug 2024 16:25:58 +0200 Subject: [PATCH 03/13] feat: Update tests --- src/api/__tests__/Metadata.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/__tests__/Metadata.test.js b/src/api/__tests__/Metadata.test.js index 34b83655c6..5e6d1ac3fd 100644 --- a/src/api/__tests__/Metadata.test.js +++ b/src/api/__tests__/Metadata.test.js @@ -849,7 +849,7 @@ describe('api/Metadata', () => { .mockResolvedValueOnce('global') .mockResolvedValueOnce('enterprise'); - await metadata.getMetadata(file, jest.fn(), jest.fn(), true, true); + await metadata.getMetadata(file, jest.fn(), jest.fn(), true, {}, true); expect(metadata.isDestroyed).toHaveBeenCalled(); expect(metadata.getCache).toHaveBeenCalled(); From 5c37a0f3c7f720b303802ff175449d68b1784e9b Mon Sep 17 00:00:00 2001 From: kajarosz Date: Wed, 14 Aug 2024 16:33:37 +0200 Subject: [PATCH 04/13] feat: Update tests --- src/api/__tests__/Metadata.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/__tests__/Metadata.test.js b/src/api/__tests__/Metadata.test.js index 5e6d1ac3fd..734cfd8c15 100644 --- a/src/api/__tests__/Metadata.test.js +++ b/src/api/__tests__/Metadata.test.js @@ -858,7 +858,7 @@ describe('api/Metadata', () => { expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'enterprise'); expect(metadata.getEditors).not.toHaveBeenCalled(); - expect(metadata.getTemplateInstances).toHaveBeenCalled( + expect(metadata.getTemplateInstances).toHaveBeenCalledWith( file.id, 'instances', 'custom', From 2c67a27d223d5cbec50bbf5228aa626219e6dafc Mon Sep 17 00:00:00 2001 From: kajarosz Date: Wed, 14 Aug 2024 18:32:41 +0200 Subject: [PATCH 05/13] feat: update templateFields definition --- src/api/Metadata.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/Metadata.js b/src/api/Metadata.js index ba79ffc595..f399a5b6df 100644 --- a/src/api/Metadata.js +++ b/src/api/Metadata.js @@ -368,7 +368,7 @@ class Metadata extends File { if (template.templateKey !== METADATA_TEMPLATE_PROPERTIES) { // Get Metadata Fields for Instances created from predefinied template - const templateFields = template.fields; + const templateFields = template.fields || []; templateFields.map(async field => { metadataFields[field.key] = { description: field.description, From e63994970c244762bb26669e97e261da0f7b5a6f Mon Sep 17 00:00:00 2001 From: kajarosz Date: Wed, 14 Aug 2024 19:01:22 +0200 Subject: [PATCH 06/13] feat(api): update templateFields definition --- src/api/Metadata.js | 11 +++++------ src/api/__tests__/Metadata.test.js | 29 +++++++++++++++-------------- src/common/types/metadata.js | 5 +---- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/api/Metadata.js b/src/api/Metadata.js index f399a5b6df..ddf8e3289b 100644 --- a/src/api/Metadata.js +++ b/src/api/Metadata.js @@ -42,7 +42,6 @@ import type { MetadataFields, MetadataSuggestion, MetadataTemplateInstance, - MetadataInstanceTemplateFields, } from '../common/types/metadata'; import type { BoxItem } from '../common/types/core'; import type APICache from '../utils/Cache'; @@ -364,13 +363,13 @@ class Metadata extends File { * @return {Object} metadata template instance */ createTemplateInstance(instance: MetadataInstanceV2, template: MetadataTemplate): MetadataTemplateInstance { - const metadataFields: MetadataInstanceTemplateFields = {}; + const metadataFields: MetadataInstanceTemplateField[] = []; if (template.templateKey !== METADATA_TEMPLATE_PROPERTIES) { // Get Metadata Fields for Instances created from predefinied template const templateFields = template.fields || []; templateFields.map(async field => { - metadataFields[field.key] = { + metadataFields.push({ description: field.description, displayName: field.displayName, hidden: field.hidden || field.isHidden, @@ -379,18 +378,18 @@ class Metadata extends File { options: field.options, type: field.type, value: instance[field.key], - }; + }); }); } else { // Get Metadata Fields for Custom Instances Object.keys(instance).forEach(key => { if (!key.startsWith('$')) { // $FlowFixMe - metadataFields[key] = { + metadataFields.push({ key, type: 'string', value: instance[key], - }; + }); } }); } diff --git a/src/api/__tests__/Metadata.test.js b/src/api/__tests__/Metadata.test.js index 734cfd8c15..b4fa12c75b 100644 --- a/src/api/__tests__/Metadata.test.js +++ b/src/api/__tests__/Metadata.test.js @@ -197,16 +197,8 @@ describe('api/Metadata', () => { displayName: 'Test template', hidden: undefined, id: '123456', - metadataFields: { - testFloatField: { - description: 'Test description', - displayName: 'Test float field', - id: '456', - key: 'testFloatField', - type: 'float', - value: '2.1', - }, - testStringField: { + metadataFields: [ + { description: 'Test description', displayName: 'Test string field', id: '123', @@ -214,7 +206,16 @@ describe('api/Metadata', () => { type: 'string', value: 'This is string', }, - }, + { + description: 'Test description', + displayName: 'Test float field', + id: '456', + key: 'testFloatField', + type: 'float', + value: '2.1', + }, + ], + scope: undefined, templateKey: 'instance_from_template', }); }); @@ -238,13 +239,13 @@ describe('api/Metadata', () => { displayName: 'Test template', hidden: undefined, id: '123456', - metadataFields: { - testCustomField: { + metadataFields: [ + { key: 'testCustomField', type: 'string', value: 'This is string', }, - }, + ], templateKey: 'properties', }); }); diff --git a/src/common/types/metadata.js b/src/common/types/metadata.js index f7c6da8a4a..a6b08ecac7 100644 --- a/src/common/types/metadata.js +++ b/src/common/types/metadata.js @@ -126,20 +126,17 @@ type MetadataInstanceTemplateField = { value: string, }; -type MetadataInstanceTemplateFields = { [string]: MetadataInstanceTemplateField }; - type MetadataTemplateInstance = { displayName?: string, hidden?: boolean, id: string, - metadataFields: MetadataInstanceTemplateFields, + metadataFields: MetadataInstanceTemplateField[], scope: string, templateKey: string, }; export type { MetadataInstanceTemplateField, - MetadataInstanceTemplateFields, MetadataTemplateInstance, MetadataFieldType, MetadataTemplateFieldOption, From 18e98927a4634abe9e6734b868a190c724b149c0 Mon Sep 17 00:00:00 2001 From: kajarosz Date: Wed, 21 Aug 2024 14:54:18 +0200 Subject: [PATCH 07/13] feat(api): Redundant isHidden param, typo, forEach --- src/api/Metadata.js | 17 +++++++++++------ src/common/types/metadata.js | 1 - 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/api/Metadata.js b/src/api/Metadata.js index ddf8e3289b..edf18acb55 100644 --- a/src/api/Metadata.js +++ b/src/api/Metadata.js @@ -42,6 +42,7 @@ import type { MetadataFields, MetadataSuggestion, MetadataTemplateInstance, + MetadataInstanceTemplateField, } from '../common/types/metadata'; import type { BoxItem } from '../common/types/core'; import type APICache from '../utils/Cache'; @@ -365,14 +366,17 @@ class Metadata extends File { createTemplateInstance(instance: MetadataInstanceV2, template: MetadataTemplate): MetadataTemplateInstance { const metadataFields: MetadataInstanceTemplateField[] = []; - if (template.templateKey !== METADATA_TEMPLATE_PROPERTIES) { + // templateKey is unique identifier for the template, + // but its value is set to 'properties' if instance was created using Custom Metadata option instead of template + const isCustomMetadataInstance = template.templateKey !== METADATA_TEMPLATE_PROPERTIES; + if (isCustomMetadataInstance) { // Get Metadata Fields for Instances created from predefinied template const templateFields = template.fields || []; - templateFields.map(async field => { + templateFields.forEach(async field => { metadataFields.push({ description: field.description, displayName: field.displayName, - hidden: field.hidden || field.isHidden, + hidden: field.hidden, id: field.id, key: field.key, options: field.options, @@ -396,7 +400,7 @@ class Metadata extends File { return { displayName: template.displayName, - hidden: template.hidden || template.isHidden, + hidden: template.hidden, id: template.id, metadataFields, scope: template.scope, @@ -430,7 +434,7 @@ class Metadata extends File { // Filter out classification const filteredInstances = this.extractClassification(id, instances); - // Create Metadata Tmplate Instance from each instance + // Create Metadata Template Instance from each instance const templateInstances: Array = []; await Promise.all( @@ -448,11 +452,12 @@ class Metadata extends File { /** * API for getting metadata editors * - * @param {string} fileId - Box file id + * @param file * @param {Function} successCallback - Success callback * @param {Function} errorCallback - Error callback * @param {boolean} hasMetadataFeature - metadata feature check * @param {Object} options - fetch options + * @param {boolean} isMetadataRedesign - is Metadata Sidebar redesigned * @return {Promise} */ async getMetadata( diff --git a/src/common/types/metadata.js b/src/common/types/metadata.js index a6b08ecac7..b41c9f497d 100644 --- a/src/common/types/metadata.js +++ b/src/common/types/metadata.js @@ -119,7 +119,6 @@ type MetadataInstanceTemplateField = { displayName: string, hidden?: boolean, id: string, - isHidden?: boolean, key: string, // V2 options?: Array, // V3 type: MetadataFieldType, From 9d328ef896635eff40869bbe02c17403049d83e8 Mon Sep 17 00:00:00 2001 From: kajarosz Date: Wed, 21 Aug 2024 15:05:55 +0200 Subject: [PATCH 08/13] feat(api): Move filtering to getMetadata --- src/api/Metadata.js | 18 ++++----- src/api/__tests__/Metadata.test.js | 60 ++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/src/api/Metadata.js b/src/api/Metadata.js index edf18acb55..8edb079b5b 100644 --- a/src/api/Metadata.js +++ b/src/api/Metadata.js @@ -339,14 +339,10 @@ class Metadata extends File { globalTemplates, ); - // Filter out skills and classification - // let filteredInstances = this.extractSkills(id, instances); - const filteredInstances = this.extractClassification(id, instances); - // Create editors from each instance const editors: Array = []; await Promise.all( - filteredInstances.map(async instance => { + instances.map(async instance => { const template: ?MetadataTemplate = await this.getTemplateForInstance(id, instance, templates); if (template) { editors.push(this.createEditor(instance, template, canEdit)); @@ -431,14 +427,11 @@ class Metadata extends File { globalTemplates, ); - // Filter out classification - const filteredInstances = this.extractClassification(id, instances); - // Create Metadata Template Instance from each instance const templateInstances: Array = []; await Promise.all( - filteredInstances.map(async instance => { + instances.map(async instance => { const template: ?MetadataTemplate = await this.getTemplateForInstance(id, instance, templates); if (template) { templateInstances.push(this.createTemplateInstance(instance, template)); @@ -504,10 +497,13 @@ class Metadata extends File { hasMetadataFeature ? this.getTemplates(id, METADATA_SCOPE_ENTERPRISE) : Promise.resolve([]), ]); + // Filter out classification + const filteredInstances = this.extractClassification(id, instances); + const templateInstances = isMetadataRedesign ? await this.getTemplateInstances( id, - instances, + filteredInstances, customPropertiesTemplate, enterpriseTemplates, globalTemplates, @@ -516,7 +512,7 @@ class Metadata extends File { const editors = !isMetadataRedesign ? await this.getEditors( id, - instances, + filteredInstances, customPropertiesTemplate, enterpriseTemplates, globalTemplates, diff --git a/src/api/__tests__/Metadata.test.js b/src/api/__tests__/Metadata.test.js index b4fa12c75b..976e173cd1 100644 --- a/src/api/__tests__/Metadata.test.js +++ b/src/api/__tests__/Metadata.test.js @@ -575,7 +575,6 @@ describe('api/Metadata', () => { }, ]; - metadata.extractClassification = jest.fn().mockReturnValueOnce(instances); metadata.createEditor = jest .fn() .mockReturnValueOnce('editor1') @@ -592,7 +591,6 @@ describe('api/Metadata', () => { const editors = await metadata.getEditors('id', instances, {}, [], [], true); expect(editors).toEqual(['editor1', 'editor2', 'editor3', 'editor4']); - expect(metadata.extractClassification).toBeCalledWith('id', instances); expect(metadata.createEditor).toBeCalledTimes(4); expect(metadata.createEditor.mock.calls[0][0]).toBe(instances[0]); @@ -657,7 +655,6 @@ describe('api/Metadata', () => { }, ]; - metadata.extractClassification = jest.fn().mockReturnValueOnce(instances); metadata.createTemplateInstance = jest .fn() .mockReturnValueOnce('templateInstance1') @@ -679,7 +676,6 @@ describe('api/Metadata', () => { 'templateInstance3', 'templateInstance4', ]); - expect(metadata.extractClassification).toBeCalledWith('id', instances); expect(metadata.createTemplateInstance).toBeCalledTimes(4); expect(metadata.createTemplateInstance.mock.calls[0][0]).toBe(instances[0]); @@ -792,7 +788,11 @@ describe('api/Metadata', () => { metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); - metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); + metadata.getTemplates = jest + .fn() + .mockResolvedValueOnce('global') + .mockResolvedValueOnce('enterprise'); + metadata.extractClassification = jest.fn().mockReturnValueOnce('filteredInstances'); await metadata.getMetadata(file, jest.fn(), jest.fn(), true); @@ -802,9 +802,10 @@ describe('api/Metadata', () => { expect(metadata.getInstances).toHaveBeenCalledWith(file.id); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'enterprise'); + expect(metadata.extractClassification).toBeCalledWith('id', 'instances'); expect(metadata.getEditors).toHaveBeenCalledWith( file.id, - 'instances', + 'filteredInstances', 'custom', 'enterprise', 'global', @@ -849,6 +850,7 @@ describe('api/Metadata', () => { .fn() .mockResolvedValueOnce('global') .mockResolvedValueOnce('enterprise'); + metadata.extractClassification = jest.fn().mockReturnValueOnce('filteredInstances'); await metadata.getMetadata(file, jest.fn(), jest.fn(), true, {}, true); @@ -858,10 +860,11 @@ describe('api/Metadata', () => { expect(metadata.getInstances).toHaveBeenCalledWith(file.id); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'enterprise'); + expect(metadata.extractClassification).toBeCalledWith('id', 'instances'); expect(metadata.getEditors).not.toHaveBeenCalled(); expect(metadata.getTemplateInstances).toHaveBeenCalledWith( file.id, - 'instances', + 'filteredInstances', 'custom', 'enterprise', 'global', @@ -902,7 +905,11 @@ describe('api/Metadata', () => { metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); - metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); + metadata.getTemplates = jest + .fn() + .mockResolvedValueOnce('global') + .mockResolvedValueOnce('enterprise'); + metadata.extractClassification = jest.fn().mockReturnValueOnce('filteredInstances'); await metadata.getMetadata(file, jest.fn(), jest.fn(), true, { refreshCache: true }); @@ -912,9 +919,10 @@ describe('api/Metadata', () => { expect(metadata.getInstances).toHaveBeenCalledWith(file.id); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'enterprise'); + expect(metadata.extractClassification).toBeCalledWith('id', 'instances'); expect(metadata.getEditors).toHaveBeenCalledWith( file.id, - 'instances', + 'filteredInstances', 'custom', 'enterprise', 'global', @@ -958,7 +966,11 @@ describe('api/Metadata', () => { metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); - metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); + metadata.getTemplates = jest + .fn() + .mockResolvedValueOnce('global') + .mockResolvedValueOnce('enterprise'); + metadata.extractClassification = jest.fn().mockReturnValueOnce('filteredInstances'); await metadata.getMetadata(file, jest.fn(), jest.fn(), true, { forceFetch: true }); @@ -968,9 +980,10 @@ describe('api/Metadata', () => { expect(metadata.getInstances).toHaveBeenCalledWith(file.id); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'enterprise'); + expect(metadata.extractClassification).toBeCalledWith('id', 'instances'); expect(metadata.getEditors).toHaveBeenCalledWith( file.id, - 'instances', + 'filteredInstances', 'custom', 'enterprise', 'global', @@ -1013,7 +1026,11 @@ describe('api/Metadata', () => { metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); - metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); + metadata.getTemplates = jest + .fn() + .mockResolvedValueOnce('global') + .mockResolvedValueOnce('enterprise'); + metadata.extractClassification = jest.fn().mockReturnValueOnce('filteredInstances'); await metadata.getMetadata(file, jest.fn(), jest.fn(), false); @@ -1023,7 +1040,15 @@ describe('api/Metadata', () => { expect(metadata.getInstances).toHaveBeenCalledWith(file.id); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).not.toHaveBeenCalledWith(file.id, 'enterprise'); - expect(metadata.getEditors).toHaveBeenCalledWith(file.id, 'instances', 'custom', [], 'global', true); + expect(metadata.extractClassification).toBeCalledWith('id', 'instances'); + expect(metadata.getEditors).toHaveBeenCalledWith( + file.id, + 'filteredInstances', + 'custom', + [], + 'global', + true, + ); expect(metadata.getTemplateInstances).not.toHaveBeenCalled(); expect(metadata.getUserAddableTemplates).toHaveBeenCalledWith('custom', [], false, true); expect(metadata.successHandler).toHaveBeenCalledTimes(1); @@ -1099,7 +1124,11 @@ describe('api/Metadata', () => { metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); - metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); + metadata.getTemplates = jest + .fn() + .mockResolvedValueOnce('global') + .mockResolvedValueOnce('enterprise'); + metadata.extractClassification = jest.fn().mockReturnValueOnce('filteredInstances'); await metadata.getMetadata(file, jest.fn(), jest.fn(), true, { forceFetch: true }); @@ -1109,9 +1138,10 @@ describe('api/Metadata', () => { expect(metadata.getInstances).toHaveBeenCalledWith(file.id); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'enterprise'); + expect(metadata.extractClassification).toBeCalledWith('id', 'instances'); expect(metadata.getEditors).toHaveBeenCalledWith( file.id, - 'instances', + 'filteredInstances', 'custom', 'enterprise', 'global', From 949c6be989af372c43f4c0d5f27411390ec861bc Mon Sep 17 00:00:00 2001 From: kajarosz Date: Wed, 21 Aug 2024 15:08:18 +0200 Subject: [PATCH 09/13] feat(api): Add templateInstances to successCallBack --- src/api/Metadata.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/api/Metadata.js b/src/api/Metadata.js index 8edb079b5b..e351c2db81 100644 --- a/src/api/Metadata.js +++ b/src/api/Metadata.js @@ -455,7 +455,11 @@ class Metadata extends File { */ async getMetadata( file: BoxItem, - successCallback: ({ editors: Array, templates: Array }) => void, + successCallback: ({ + editors: Array, + templateInstances: Array, + templates: Array, + }) => void, errorCallback: ElementsErrorCallback, hasMetadataFeature: boolean, options: RequestOptions = {}, From 971559d758409d180c110a06db140c468ca67edf Mon Sep 17 00:00:00 2001 From: kajarosz Date: Thu, 22 Aug 2024 13:07:59 +0200 Subject: [PATCH 10/13] feat(api): Address small comments --- src/api/Metadata.js | 35 ++++++++++-------------------- src/api/__tests__/Metadata.test.js | 34 +++++++---------------------- src/common/types/metadata.js | 10 ++++----- 3 files changed, 25 insertions(+), 54 deletions(-) diff --git a/src/api/Metadata.js b/src/api/Metadata.js index e351c2db81..8c17434880 100644 --- a/src/api/Metadata.js +++ b/src/api/Metadata.js @@ -42,7 +42,7 @@ import type { MetadataFields, MetadataSuggestion, MetadataTemplateInstance, - MetadataInstanceTemplateField, + MetadataTemplateInstanceField, } from '../common/types/metadata'; import type { BoxItem } from '../common/types/core'; import type APICache from '../utils/Cache'; @@ -360,23 +360,17 @@ class Metadata extends File { * @return {Object} metadata template instance */ createTemplateInstance(instance: MetadataInstanceV2, template: MetadataTemplate): MetadataTemplateInstance { - const metadataFields: MetadataInstanceTemplateField[] = []; + const fields: MetadataTemplateInstanceField[] = []; // templateKey is unique identifier for the template, // but its value is set to 'properties' if instance was created using Custom Metadata option instead of template - const isCustomMetadataInstance = template.templateKey !== METADATA_TEMPLATE_PROPERTIES; - if (isCustomMetadataInstance) { - // Get Metadata Fields for Instances created from predefinied template + const isInstanceFromTemplate = template.templateKey !== METADATA_TEMPLATE_PROPERTIES; + if (isInstanceFromTemplate) { + // Get Metadata Fields for Instances created from predefined template const templateFields = template.fields || []; - templateFields.forEach(async field => { - metadataFields.push({ - description: field.description, - displayName: field.displayName, - hidden: field.hidden, - id: field.id, - key: field.key, - options: field.options, - type: field.type, + templateFields.forEach(field => { + fields.push({ + ...field, value: instance[field.key], }); }); @@ -384,8 +378,7 @@ class Metadata extends File { // Get Metadata Fields for Custom Instances Object.keys(instance).forEach(key => { if (!key.startsWith('$')) { - // $FlowFixMe - metadataFields.push({ + fields.push({ key, type: 'string', value: instance[key], @@ -395,12 +388,8 @@ class Metadata extends File { } return { - displayName: template.displayName, - hidden: template.hidden, - id: template.id, - metadataFields, - scope: template.scope, - templateKey: template.templateKey, + ...template, + fields, }; } @@ -445,7 +434,7 @@ class Metadata extends File { /** * API for getting metadata editors * - * @param file + * @param {Object} file * @param {Function} successCallback - Success callback * @param {Function} errorCallback - Error callback * @param {boolean} hasMetadataFeature - metadata feature check diff --git a/src/api/__tests__/Metadata.test.js b/src/api/__tests__/Metadata.test.js index 976e173cd1..6f50416adc 100644 --- a/src/api/__tests__/Metadata.test.js +++ b/src/api/__tests__/Metadata.test.js @@ -197,7 +197,7 @@ describe('api/Metadata', () => { displayName: 'Test template', hidden: undefined, id: '123456', - metadataFields: [ + fields: [ { description: 'Test description', displayName: 'Test string field', @@ -239,7 +239,7 @@ describe('api/Metadata', () => { displayName: 'Test template', hidden: undefined, id: '123456', - metadataFields: [ + fields: [ { key: 'testCustomField', type: 'string', @@ -788,10 +788,7 @@ describe('api/Metadata', () => { metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); - metadata.getTemplates = jest - .fn() - .mockResolvedValueOnce('global') - .mockResolvedValueOnce('enterprise'); + metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); metadata.extractClassification = jest.fn().mockReturnValueOnce('filteredInstances'); await metadata.getMetadata(file, jest.fn(), jest.fn(), true); @@ -846,10 +843,7 @@ describe('api/Metadata', () => { metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); - metadata.getTemplates = jest - .fn() - .mockResolvedValueOnce('global') - .mockResolvedValueOnce('enterprise'); + metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); metadata.extractClassification = jest.fn().mockReturnValueOnce('filteredInstances'); await metadata.getMetadata(file, jest.fn(), jest.fn(), true, {}, true); @@ -905,10 +899,7 @@ describe('api/Metadata', () => { metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); - metadata.getTemplates = jest - .fn() - .mockResolvedValueOnce('global') - .mockResolvedValueOnce('enterprise'); + metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); metadata.extractClassification = jest.fn().mockReturnValueOnce('filteredInstances'); await metadata.getMetadata(file, jest.fn(), jest.fn(), true, { refreshCache: true }); @@ -966,10 +957,7 @@ describe('api/Metadata', () => { metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); - metadata.getTemplates = jest - .fn() - .mockResolvedValueOnce('global') - .mockResolvedValueOnce('enterprise'); + metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); metadata.extractClassification = jest.fn().mockReturnValueOnce('filteredInstances'); await metadata.getMetadata(file, jest.fn(), jest.fn(), true, { forceFetch: true }); @@ -1026,10 +1014,7 @@ describe('api/Metadata', () => { metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); - metadata.getTemplates = jest - .fn() - .mockResolvedValueOnce('global') - .mockResolvedValueOnce('enterprise'); + metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); metadata.extractClassification = jest.fn().mockReturnValueOnce('filteredInstances'); await metadata.getMetadata(file, jest.fn(), jest.fn(), false); @@ -1124,10 +1109,7 @@ describe('api/Metadata', () => { metadata.getTemplateInstances = jest.fn().mockResolvedValueOnce('templateInstances'); metadata.getCustomPropertiesTemplate = jest.fn().mockReturnValueOnce('custom'); metadata.getUserAddableTemplates = jest.fn().mockReturnValueOnce('templates'); - metadata.getTemplates = jest - .fn() - .mockResolvedValueOnce('global') - .mockResolvedValueOnce('enterprise'); + metadata.getTemplates = jest.fn().mockResolvedValueOnce('global').mockResolvedValueOnce('enterprise'); metadata.extractClassification = jest.fn().mockReturnValueOnce('filteredInstances'); await metadata.getMetadata(file, jest.fn(), jest.fn(), true, { forceFetch: true }); diff --git a/src/common/types/metadata.js b/src/common/types/metadata.js index b41c9f497d..627c9b08f6 100644 --- a/src/common/types/metadata.js +++ b/src/common/types/metadata.js @@ -114,11 +114,11 @@ type MetadataSuggestion = { suggestions: { [key: string]: string | number | string[] }, }; -type MetadataInstanceTemplateField = { +type MetadataTemplateInstanceField = { description?: string, - displayName: string, + displayName?: string, hidden?: boolean, - id: string, + id?: string, key: string, // V2 options?: Array, // V3 type: MetadataFieldType, @@ -129,13 +129,13 @@ type MetadataTemplateInstance = { displayName?: string, hidden?: boolean, id: string, - metadataFields: MetadataInstanceTemplateField[], + fields: MetadataTemplateInstanceField[], scope: string, templateKey: string, }; export type { - MetadataInstanceTemplateField, + MetadataTemplateInstanceField, MetadataTemplateInstance, MetadataFieldType, MetadataTemplateFieldOption, From 6f5f3190edcc7a4c2daa21568af2a807df38777e Mon Sep 17 00:00:00 2001 From: kajarosz Date: Thu, 22 Aug 2024 13:53:18 +0200 Subject: [PATCH 11/13] feat(api): Add canEdit prop --- src/api/Metadata.js | 13 +++++++++++-- src/api/__tests__/Metadata.test.js | 9 ++++++++- src/common/types/metadata.js | 1 + 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/api/Metadata.js b/src/api/Metadata.js index 8c17434880..ad1d22e902 100644 --- a/src/api/Metadata.js +++ b/src/api/Metadata.js @@ -357,9 +357,14 @@ class Metadata extends File { * * @param {Object} instance - metadata instance * @param {Object} template - metadata template + * @param {boolean} canEdit - can user edit item * @return {Object} metadata template instance */ - createTemplateInstance(instance: MetadataInstanceV2, template: MetadataTemplate): MetadataTemplateInstance { + createTemplateInstance( + instance: MetadataInstanceV2, + template: MetadataTemplate, + canEdit: boolean, + ): MetadataTemplateInstance { const fields: MetadataTemplateInstanceField[] = []; // templateKey is unique identifier for the template, @@ -389,6 +394,7 @@ class Metadata extends File { return { ...template, + canEdit: instance.$canEdit && canEdit, fields, }; } @@ -401,6 +407,7 @@ class Metadata extends File { * @param {Object} customPropertiesTemplate - custom properties template * @param {Array} enterpriseTemplates - enterprise templates * @param {Array} globalTemplates - global templates + * @param {boolean} canEdit * @return {Array} metadata editors */ async getTemplateInstances( @@ -409,6 +416,7 @@ class Metadata extends File { customPropertiesTemplate: MetadataTemplate, enterpriseTemplates: Array, globalTemplates: Array, + canEdit: boolean, ): Promise> { // Get all usable templates for metadata instances const templates: Array = [customPropertiesTemplate].concat( @@ -423,7 +431,7 @@ class Metadata extends File { instances.map(async instance => { const template: ?MetadataTemplate = await this.getTemplateForInstance(id, instance, templates); if (template) { - templateInstances.push(this.createTemplateInstance(instance, template)); + templateInstances.push(this.createTemplateInstance(instance, template, canEdit)); } }), ); @@ -500,6 +508,7 @@ class Metadata extends File { customPropertiesTemplate, enterpriseTemplates, globalTemplates, + !!permissions.can_upload, ) : []; const editors = !isMetadataRedesign diff --git a/src/api/__tests__/Metadata.test.js b/src/api/__tests__/Metadata.test.js index 6f50416adc..0b482a15f0 100644 --- a/src/api/__tests__/Metadata.test.js +++ b/src/api/__tests__/Metadata.test.js @@ -168,6 +168,7 @@ describe('api/Metadata', () => { { $id: '321', $template: '', + $canEdit: true, testStringField: 'This is string', testFloatField: '2.1', }, @@ -192,8 +193,10 @@ describe('api/Metadata', () => { id: '123456', templateKey: 'instance_from_template', }, + true, ), ).toEqual({ + canEdit: true, displayName: 'Test template', hidden: undefined, id: '123456', @@ -224,6 +227,7 @@ describe('api/Metadata', () => { expect( metadata.createTemplateInstance( { + $canEdit: true, $id: '321', $template: '', testCustomField: 'This is string', @@ -234,8 +238,10 @@ describe('api/Metadata', () => { id: '123456', templateKey: 'properties', }, + false, ), ).toEqual({ + canEdit: false, displayName: 'Test template', hidden: undefined, id: '123456', @@ -669,7 +675,7 @@ describe('api/Metadata', () => { .mockResolvedValueOnce('template4') .mockResolvedValueOnce(); - const templateInstances = await metadata.getTemplateInstances('id', instances, {}, [], []); + const templateInstances = await metadata.getTemplateInstances('id', instances, {}, [], [], true); expect(templateInstances).toEqual([ 'templateInstance1', 'templateInstance2', @@ -862,6 +868,7 @@ describe('api/Metadata', () => { 'custom', 'enterprise', 'global', + true, ); expect(metadata.getUserAddableTemplates).toHaveBeenCalledWith('custom', 'enterprise', true, true); expect(metadata.successHandler).toHaveBeenCalledWith({ diff --git a/src/common/types/metadata.js b/src/common/types/metadata.js index 627c9b08f6..fd585ca292 100644 --- a/src/common/types/metadata.js +++ b/src/common/types/metadata.js @@ -126,6 +126,7 @@ type MetadataTemplateInstanceField = { }; type MetadataTemplateInstance = { + canEdit: boolean, displayName?: string, hidden?: boolean, id: string, From 42e818730bec7453684f50602ec32fc2417227b2 Mon Sep 17 00:00:00 2001 From: kajarosz Date: Thu, 22 Aug 2024 15:12:42 +0200 Subject: [PATCH 12/13] feat(api): Fix flow and linting --- src/api/Metadata.js | 6 +++++- src/common/types/metadata.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/api/Metadata.js b/src/api/Metadata.js index ad1d22e902..855cd3fba4 100644 --- a/src/api/Metadata.js +++ b/src/api/Metadata.js @@ -393,9 +393,13 @@ class Metadata extends File { } return { - ...template, canEdit: instance.$canEdit && canEdit, + displayName: template.displayName, + hidden: template.hidden, + id: template.id, fields, + scope: template.scope, + templateKey: template.templateKey, }; } diff --git a/src/common/types/metadata.js b/src/common/types/metadata.js index fd585ca292..2407ca27d6 100644 --- a/src/common/types/metadata.js +++ b/src/common/types/metadata.js @@ -122,7 +122,7 @@ type MetadataTemplateInstanceField = { key: string, // V2 options?: Array, // V3 type: MetadataFieldType, - value: string, + value: MetadataFieldValue, }; type MetadataTemplateInstance = { From c3cd5ca29a67172c40562f5b615cf6a623146904 Mon Sep 17 00:00:00 2001 From: kajarosz Date: Thu, 22 Aug 2024 17:05:39 +0200 Subject: [PATCH 13/13] feat(api): Update missing param in type def --- src/api/Metadata.js | 1 + src/common/types/metadata.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/api/Metadata.js b/src/api/Metadata.js index 855cd3fba4..3e6849e293 100644 --- a/src/api/Metadata.js +++ b/src/api/Metadata.js @@ -400,6 +400,7 @@ class Metadata extends File { fields, scope: template.scope, templateKey: template.templateKey, + type: instance.$type, }; } diff --git a/src/common/types/metadata.js b/src/common/types/metadata.js index 2407ca27d6..04eebdc590 100644 --- a/src/common/types/metadata.js +++ b/src/common/types/metadata.js @@ -133,6 +133,7 @@ type MetadataTemplateInstance = { fields: MetadataTemplateInstanceField[], scope: string, templateKey: string, + type: string, }; export type {