From cccb0b084a52838eb9f7e16c084507ef30ba30e1 Mon Sep 17 00:00:00 2001 From: Tyler Gauntlett Date: Mon, 21 Oct 2024 11:46:00 -0400 Subject: [PATCH] feat(taxonomy): added view/create/updated taxonomy field support (#3716) * feat(taxonomy): added create/updated taxonomy support * feat(taxonomy): fixed tests and lint errors --- src/api/Metadata.js | 32 +++++++++++++++++++++++------- src/api/__tests__/Metadata.test.js | 28 +++++++++++++++++++------- src/api/__tests__/utils.test.js | 31 ++++++++++++++++++++++++++++- src/api/utils.js | 21 +++++++++++++++----- 4 files changed, 92 insertions(+), 20 deletions(-) diff --git a/src/api/Metadata.js b/src/api/Metadata.js index 76192a82cc..7f8391e311 100644 --- a/src/api/Metadata.js +++ b/src/api/Metadata.js @@ -9,7 +9,7 @@ import uniqueId from 'lodash/uniqueId'; import isEmpty from 'lodash/isEmpty'; import { getBadItemError, getBadPermissionsError, isUserCorrectableError } from '../utils/error'; import { getTypedFileId } from '../utils/file'; -import { handleOnAbort } from './utils'; +import { handleOnAbort, formatMetadataFieldValue } from './utils'; import File from './File'; import { HEADER_CONTENT_TYPE, @@ -226,14 +226,17 @@ class Metadata extends File { * Gets metadata instances for a Box file * * @param {string} id - file id + * @param {boolean} isMetadataRedesign - feature flag * @return {Object} array of metadata instances */ - async getInstances(id: string): Promise> { + async getInstances(id: string, isMetadataRedesign: boolean = false): Promise> { this.errorCode = ERROR_CODE_FETCH_METADATA; + const baseUrl = this.getMetadataUrl(id); + const url = isMetadataRedesign ? `${baseUrl}?view=hydrated` : baseUrl; let instances = {}; try { instances = await this.xhr.get({ - url: this.getMetadataUrl(id), + url, id: getTypedFileId(id), }); } catch (e) { @@ -377,9 +380,11 @@ class Metadata extends File { // Get Metadata Fields for Instances created from predefined template const templateFields = template.fields || []; templateFields.forEach(field => { + const value = formatMetadataFieldValue(field, instance[field.key]); + fields.push({ ...field, - value: instance[field.key], + value, }); }); } else { @@ -501,7 +506,7 @@ class Metadata extends File { try { const customPropertiesTemplate: MetadataTemplate = this.getCustomPropertiesTemplate(); const [instances, globalTemplates, enterpriseTemplates] = await Promise.all([ - this.getInstances(id), + this.getInstances(id, isMetadataRedesign), this.getTemplates(id, METADATA_SCOPE_GLOBAL), hasMetadataFeature ? this.getTemplates(id, METADATA_SCOPE_ENTERPRISE) : Promise.resolve([]), ]); @@ -887,11 +892,24 @@ class Metadata extends File { try { const fieldsValues = template.fields.reduce((acc, obj) => { let { value } = obj; + // API does not accept string for float type - if (obj.type === 'float' && value) value = parseFloat(obj.value); + if (obj.type === 'float' && value) { + value = parseFloat(obj.value); + } + // API does not accept empty string for enum type - if (obj.type === 'enum' && value && value.length === 0) value = undefined; + if (obj.type === 'enum' && value && value.length === 0) { + value = undefined; + } + + // API expects values as an array of strings + if (obj.type === 'taxonomy' && value && Array.isArray(value)) { + value = value.map(option => option.value); + } + acc[obj.key] = value; + return acc; }, {}); diff --git a/src/api/__tests__/Metadata.test.js b/src/api/__tests__/Metadata.test.js index e9e72186a0..f3d65094b8 100644 --- a/src/api/__tests__/Metadata.test.js +++ b/src/api/__tests__/Metadata.test.js @@ -25,6 +25,7 @@ import { handleOnAbort } from '../utils'; let metadata: Metadata; jest.mock('../utils', () => ({ + ...jest.requireActual('../utils'), handleOnAbort: jest.fn(), })); @@ -443,6 +444,19 @@ describe('api/Metadata', () => { id: 'file_id', }); }); + test('should apply hydrated query string param for isMetadataRedesign', async () => { + metadata.getMetadataUrl = jest.fn().mockReturnValueOnce('metadata_url'); + metadata.xhr.get = jest.fn().mockReturnValueOnce({ + data: { + entries: [], + }, + }); + await metadata.getInstances('id', true); + expect(metadata.xhr.get).toHaveBeenCalledWith({ + url: 'metadata_url?view=hydrated', + id: 'file_id', + }); + }); }); describe('getUserAddableTemplates()', () => { @@ -810,7 +824,7 @@ describe('api/Metadata', () => { expect(metadata.isDestroyed).toHaveBeenCalled(); expect(metadata.getCache).toHaveBeenCalled(); expect(metadata.getMetadataCacheKey).toHaveBeenCalledWith(file.id); - expect(metadata.getInstances).toHaveBeenCalledWith(file.id); + expect(metadata.getInstances).toHaveBeenCalledWith(file.id, false); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'enterprise'); expect(metadata.extractClassification).toBeCalledWith('id', 'instances'); @@ -865,7 +879,7 @@ describe('api/Metadata', () => { expect(metadata.isDestroyed).toHaveBeenCalled(); expect(metadata.getCache).toHaveBeenCalled(); expect(metadata.getMetadataCacheKey).toHaveBeenCalledWith(file.id); - expect(metadata.getInstances).toHaveBeenCalledWith(file.id); + expect(metadata.getInstances).toHaveBeenCalledWith(file.id, true); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'enterprise'); expect(metadata.extractClassification).toBeCalledWith('id', 'instances'); @@ -922,7 +936,7 @@ describe('api/Metadata', () => { expect(metadata.isDestroyed).toHaveBeenCalled(); expect(metadata.getCache).toHaveBeenCalled(); expect(metadata.getMetadataCacheKey).toHaveBeenCalledWith(file.id); - expect(metadata.getInstances).toHaveBeenCalledWith(file.id); + expect(metadata.getInstances).toHaveBeenCalledWith(file.id, false); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'enterprise'); expect(metadata.extractClassification).toBeCalledWith('id', 'instances'); @@ -980,7 +994,7 @@ describe('api/Metadata', () => { expect(metadata.isDestroyed).toHaveBeenCalled(); expect(metadata.getCache).toHaveBeenCalled(); expect(metadata.getMetadataCacheKey).toHaveBeenCalledWith(file.id); - expect(metadata.getInstances).toHaveBeenCalledWith(file.id); + expect(metadata.getInstances).toHaveBeenCalledWith(file.id, false); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'enterprise'); expect(metadata.extractClassification).toBeCalledWith('id', 'instances'); @@ -1037,7 +1051,7 @@ describe('api/Metadata', () => { expect(metadata.isDestroyed).toHaveBeenCalled(); expect(metadata.getCache).toHaveBeenCalled(); expect(metadata.getMetadataCacheKey).toHaveBeenCalledWith(file.id); - expect(metadata.getInstances).toHaveBeenCalledWith(file.id); + expect(metadata.getInstances).toHaveBeenCalledWith(file.id, false); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).not.toHaveBeenCalledWith(file.id, 'enterprise'); expect(metadata.extractClassification).toBeCalledWith('id', 'instances'); @@ -1092,7 +1106,7 @@ describe('api/Metadata', () => { expect(metadata.isDestroyed).not.toHaveBeenCalled(); expect(metadata.getCache).toHaveBeenCalled(); expect(metadata.getMetadataCacheKey).toHaveBeenCalledWith(file.id); - expect(metadata.getInstances).toHaveBeenCalledWith(file.id); + expect(metadata.getInstances).toHaveBeenCalledWith(file.id, false); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'enterprise'); expect(metadata.getEditors).not.toHaveBeenCalled(); @@ -1132,7 +1146,7 @@ describe('api/Metadata', () => { expect(metadata.isDestroyed).toHaveBeenCalled(); expect(metadata.getCache).toHaveBeenCalled(); expect(metadata.getMetadataCacheKey).toHaveBeenCalledWith(file.id); - expect(metadata.getInstances).toHaveBeenCalledWith(file.id); + expect(metadata.getInstances).toHaveBeenCalledWith(file.id, false); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'global'); expect(metadata.getTemplates).toHaveBeenCalledWith(file.id, 'enterprise'); expect(metadata.extractClassification).toBeCalledWith('id', 'instances'); diff --git a/src/api/__tests__/utils.test.js b/src/api/__tests__/utils.test.js index c3255046a9..6850802b8f 100644 --- a/src/api/__tests__/utils.test.js +++ b/src/api/__tests__/utils.test.js @@ -1,7 +1,8 @@ import Xhr from '../../utils/Xhr'; import { getAbortError } from '../../utils/error'; -import { formatComment, handleOnAbort } from '../utils'; +import { formatComment, formatMetadataFieldValue, handleOnAbort } from '../utils'; import { threadedComments, threadedCommentsFormatted } from '../fixtures'; +import { FIELD_TYPE_STRING, FIELD_TYPE_TAXONOMY } from '../../features/metadata-instance-fields/constants'; jest.mock('../../utils/Xhr', () => { return jest.fn().mockImplementation(() => ({ @@ -26,4 +27,32 @@ describe('api/utils', () => { expect(xhr.abort).toHaveBeenCalled(); }); }); + + describe('formatMetadataFieldValue()', () => { + test('should format string field value', async () => { + const stringField = { + displayName: 'State', + id: '1', + key: 'stateField', + type: FIELD_TYPE_STRING, + }; + const value = 'California'; + + expect(formatMetadataFieldValue(stringField, value)).toEqual(value); + }); + + test('should format taxonomy field value', async () => { + const taxonomyField = { + displayName: 'State', + id: '1', + key: 'stateField', + type: FIELD_TYPE_TAXONOMY, + }; + const id = '123-456'; + const displayName = 'California'; + const expectedValue = [{ value: id, displayValue: displayName }]; + + expect(formatMetadataFieldValue(taxonomyField, [{ id, displayName }])).toEqual(expectedValue); + }); + }); }); diff --git a/src/api/utils.js b/src/api/utils.js index ce985c0eb0..494fe13be2 100644 --- a/src/api/utils.js +++ b/src/api/utils.js @@ -7,6 +7,8 @@ import type Xhr from '../utils/Xhr'; import type { Comment } from '../common/types/feed'; import { getAbortError } from '../utils/error'; +import type { MetadataTemplateField, MetadataFieldValue } from '../common/types/metadata'; +import { FIELD_TYPE_TAXONOMY } from '../features/metadata-instance-fields/constants'; /** * Formats comment data (including replies) for use in components. @@ -14,7 +16,7 @@ import { getAbortError } from '../utils/error'; * @param {Comment} comment - An individual comment entry from the API * @return {Comment} Updated comment */ -export const formatComment = (comment: Comment): Comment => { +const formatComment = (comment: Comment): Comment => { const formattedComment = { ...comment, tagged_message: comment.message, @@ -27,12 +29,21 @@ export const formatComment = (comment: Comment): Comment => { return formattedComment; }; -export const handleOnAbort = (xhr: Xhr) => { +const formatMetadataFieldValue = (field: MetadataTemplateField, value: MetadataFieldValue): MetadataFieldValue => { + if (field.type === FIELD_TYPE_TAXONOMY && Array.isArray(value)) { + return value.map((option: { id: string, displayName: string }) => ({ + value: option.id, + displayValue: option.displayName, + })); + } + + return value; +}; + +const handleOnAbort = (xhr: Xhr) => { xhr.abort(); throw getAbortError(); }; -export default { - formatComment, -}; +export { formatComment, formatMetadataFieldValue, handleOnAbort };