Skip to content

Commit

Permalink
feat(metadata-sidebar): Handle create metadata isntance (#3663)
Browse files Browse the repository at this point in the history
* chore(content-sidebar): Temporarily remove files

while we be working with not yet publish internal library.
Will remove this commit after the library will become publicly
available on NPM and added to BUIE.

* feat(metadata-sidebar): onSave api calls

* feat(metadata-sidebar): tests fix

* feat(metadata-sidebar): create metadata

* feat(metadata-sidebar): fix errors

* feat(metadata-sidebar): fix test

* feat(metadata-sidebar): additional error test

* feat(metadata-sidebar): gitignore

* feat(metadata-sidebar): restore files

* feat(metadata-sidebar): pr comments

* feat(metadata-sidebar): test fix

* feat(metadata-sidebar): success callback and api exceptions

* feat(metadata-sidebar): unit test

* feat(metadata-sidebar): test fix and pr comment

* feat(metadata-sidebar): changes to separate types to pass pipeline

* feat(metadata-sidebar): function name change

* feat(metadata-sidebar): yarn.lock and nme change

* feat(metadata-sidebar): function type change

* feat(metadata-sidebar): function type change

* feat(metadata-sidebar): yarn.lock update

* feat(metadata-sidebar): optimize saving function

* feat(metadata-sidebar): direct imports and extracted function

* feat(metadata-sidebar): remove empty update function

* feat(metadata-sidebar): metadata-editor bump

* feat(metadata-sidebar): isLoadin prop remove after bump

---------

Co-authored-by: Dawid Jankowiak <jankowiak.dawid@gmail.com>
  • Loading branch information
karolinaru and jankowiakdawid authored Sep 24, 2024
1 parent ebe8348 commit 321ba7e
Show file tree
Hide file tree
Showing 12 changed files with 572 additions and 39 deletions.
14 changes: 4 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,7 @@
"last 2 Edge versions",
"last 2 iOS versions"
],
"development": [
"last 1 Chrome versions",
"last 1 Firefox versions",
"last 1 Safari versions"
]
"development": ["last 1 Chrome versions", "last 1 Firefox versions", "last 1 Safari versions"]
},
"husky": {
"hooks": {
Expand Down Expand Up @@ -132,7 +128,7 @@
"@box/cldr-data": "^34.2.0",
"@box/frontend": "^10.0.0",
"@box/languages": "^1.0.0",
"@box/metadata-editor": "^0.50.5",
"@box/metadata-editor": "^0.53.0",
"@box/react-virtualized": "9.22.3-rc-box.9",
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
"@chromatic-com/storybook": "^1.6.1",
Expand Down Expand Up @@ -308,7 +304,7 @@
"@box/blueprint-web-assets": "^4.21.0",
"@box/box-ai-content-answers": "^0.49.1",
"@box/cldr-data": ">=34.2.0",
"@box/metadata-editor": "^0.50.5",
"@box/metadata-editor": "^0.53.0",
"@box/react-virtualized": "9.22.3-rc-box.9",
"@hapi/address": "^2.1.4",
"axios": "^0.25.0",
Expand Down Expand Up @@ -366,8 +362,6 @@
}
},
"msw": {
"workerDirectory": [
".storybook/public"
]
"workerDirectory": [".storybook/public"]
}
}
71 changes: 70 additions & 1 deletion src/api/Metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,6 @@ class Metadata extends File {
errorCallback(getBadPermissionsError(), this.errorCode);
return;
}

this.successCallback = successCallback;
this.errorCallback = errorCallback;

Expand All @@ -779,6 +778,76 @@ class Metadata extends File {
}
}

/**
* API for creating metadata on file
*
* @param {BoxItem} file - File object for which we are changing the description
* @param {Object} template - Metadata Redesign template
* @param {Function} successCallback - Success callback
* @param {Function} errorCallback - Error callback
* @return {Promise}
*/
async createMetadataRedesign(
file: BoxItem,
template: MetadataTemplateInstance,
successCallback: Function,
errorCallback: ElementsErrorCallback,
): Promise<void> {
this.errorCode = ERROR_CODE_CREATE_METADATA;
if (!file || !template) {
errorCallback(getBadItemError(), this.errorCode);
return;
}

const { id, permissions, is_externally_owned }: BoxItem = file;

if (!id || !permissions) {
errorCallback(getBadItemError(), this.errorCode);
return;
}

const canEdit = !!permissions.can_upload;
const isProperties =
template.templateKey === METADATA_TEMPLATE_PROPERTIES && template.scope === METADATA_SCOPE_GLOBAL;

if (!canEdit || (is_externally_owned && !isProperties)) {
errorCallback(getBadPermissionsError(), this.errorCode);
return;
}
this.successCallback = successCallback;
this.errorCallback = errorCallback;

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);
// API does not accept empty string for enum type
if (obj.type === 'enum' && value && value.length === 0) value = undefined;
acc[obj.key] = value;
return acc;
}, {});

const metadata = await this.xhr.post({
url: this.getMetadataUrl(id, template.scope, template.templateKey),
id: getTypedFileId(id),
data: fieldsValues,
});

if (!this.isDestroyed()) {
const cache: APICache = this.getCache();
const key = this.getMetadataCacheKey(id);
const cachedMetadata = cache.get(key);

const templateInstance = { ...template, type: metadata.data.$type };
cachedMetadata.templateInstances.push(templateInstance);
this.successHandler(templateInstance);
}
} catch (e) {
this.errorHandler(e);
}
}

/**
* API for deleting metadata on file
*
Expand Down
264 changes: 264 additions & 0 deletions src/api/__tests__/Metadata.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2048,6 +2048,270 @@ describe('api/Metadata', () => {
});
});

describe('createMetadataRedesign()', () => {
test('should call error callback with a bad item error when no file', () => {
jest.spyOn(ErrorUtil, 'getBadItemError').mockReturnValueOnce('error');
const successCallback = jest.fn();
const errorCallback = jest.fn();
metadata.createMetadataRedesign(undefined, {}, successCallback, errorCallback);
expect(errorCallback).toBeCalledWith('error', ERROR_CODE_CREATE_METADATA);
expect(successCallback).not.toBeCalled();
expect(ErrorUtil.getBadItemError).toBeCalled();
});
test('should call error callback with a bad item error when no template', () => {
jest.spyOn(ErrorUtil, 'getBadItemError').mockReturnValueOnce('error');
const successCallback = jest.fn();
const errorCallback = jest.fn();
metadata.createMetadataRedesign({}, undefined, successCallback, errorCallback);
expect(errorCallback).toBeCalledWith('error', ERROR_CODE_CREATE_METADATA);
expect(successCallback).not.toBeCalled();
expect(ErrorUtil.getBadItemError).toBeCalled();
});
test('should call error callback with a bad item error when no id', () => {
jest.spyOn(ErrorUtil, 'getBadItemError').mockReturnValueOnce('error');
const successCallback = jest.fn();
const errorCallback = jest.fn();
metadata.createMetadataRedesign({}, {}, successCallback, errorCallback);
expect(errorCallback).toBeCalledWith('error', ERROR_CODE_CREATE_METADATA);
expect(successCallback).not.toBeCalled();
expect(ErrorUtil.getBadItemError).toBeCalled();
});
test('should call error callback with a bad item error when no permissions', () => {
jest.spyOn(ErrorUtil, 'getBadItemError').mockReturnValueOnce('error');
const successCallback = jest.fn();
const errorCallback = jest.fn();
metadata.createMetadataRedesign({ id: 'id' }, {}, successCallback, errorCallback);
expect(errorCallback).toBeCalledWith('error', ERROR_CODE_CREATE_METADATA);
expect(successCallback).not.toBeCalled();
expect(ErrorUtil.getBadItemError).toBeCalled();
});
test('should call error callback with a bad permissions error', () => {
ErrorUtil.getBadPermissionsError = jest.fn().mockReturnValueOnce('error');
const successCallback = jest.fn();
const errorCallback = jest.fn();
metadata.createMetadataRedesign({ id: 'id', permissions: {} }, {}, successCallback, errorCallback);
expect(errorCallback).toBeCalledWith('error', ERROR_CODE_CREATE_METADATA);
expect(successCallback).not.toBeCalled();
expect(ErrorUtil.getBadPermissionsError).toBeCalled();
});
test('should call error callback with a bad permissions error when can upload is false', () => {
ErrorUtil.getBadPermissionsError = jest.fn().mockReturnValueOnce('error');
const successCallback = jest.fn();
const errorCallback = jest.fn();
metadata.createMetadataRedesign(
{ id: 'id', permissions: { can_upload: false } },
{},
successCallback,
errorCallback,
);
expect(errorCallback).toBeCalledWith('error', ERROR_CODE_CREATE_METADATA);
expect(successCallback).not.toBeCalled();
expect(ErrorUtil.getBadPermissionsError).toBeCalled();
});
test('should call error callback when file is externally owned and template isnt global', () => {
ErrorUtil.getBadPermissionsError = jest.fn().mockReturnValueOnce('error');
const successCallback = jest.fn();
const errorCallback = jest.fn();
metadata.createMetadataRedesign(
{
id: 'id',
permissions: { can_upload: true },
is_externally_owned: true,
},
{ scope: 'global', template: 'foo' },
successCallback,
errorCallback,
);
expect(errorCallback).toBeCalledWith('error', ERROR_CODE_CREATE_METADATA);
expect(successCallback).not.toBeCalled();
expect(ErrorUtil.getBadPermissionsError).toBeCalled();
});
test('should call error callback when file is externally owned and template isnt properties', () => {
ErrorUtil.getBadPermissionsError = jest.fn().mockReturnValueOnce('error');
const successCallback = jest.fn();
const errorCallback = jest.fn();
metadata.createMetadataRedesign(
{
id: 'id',
permissions: { can_upload: true },
is_externally_owned: true,
},
{ scope: 'blah', template: 'properties' },
successCallback,
errorCallback,
);
expect(errorCallback).toBeCalledWith('error', ERROR_CODE_CREATE_METADATA);
expect(successCallback).not.toBeCalled();
expect(ErrorUtil.getBadPermissionsError).toBeCalled();
});
test('should make request and update cache and call success handler', async () => {
const success = jest.fn();
const error = jest.fn();
const file = {
id: 'id',
permissions: {
can_upload: true,
},
};
const cache = new Cache();
const template = { scope: 'scope', templateKey: 'templateKey', fields: [] };

const priorMetadata = {
instance: {
id: 'instance_id',
data: {
foo: 'bar',
},
},
};

const updatedMetadata = {
...template,
type: undefined,
};

cache.set('metadata_id', {
templateInstances: [priorMetadata],
});

metadata.getMetadataUrl = jest.fn().mockReturnValueOnce('url');
metadata.xhr.post = jest.fn().mockReturnValueOnce({ data: 'foo' });
metadata.isDestroyed = jest.fn().mockReturnValueOnce(false);
metadata.getCache = jest.fn().mockReturnValueOnce(cache);
metadata.getCacheKey = jest.fn().mockReturnValueOnce('cache_id');
metadata.getMetadataCacheKey = jest.fn().mockReturnValueOnce('metadata_id');
metadata.merge = jest.fn().mockReturnValueOnce('file');
metadata.successHandler = jest.fn();
metadata.errorHandler = jest.fn();
await metadata.createMetadataRedesign(file, template, success, error);

expect(metadata.successCallback).toBe(success);
expect(metadata.errorCallback).toBe(error);
expect(metadata.getMetadataUrl).toHaveBeenCalledWith(file.id, 'scope', 'templateKey');
expect(metadata.xhr.post).toHaveBeenCalledWith({
url: 'url',
id: 'file_id',
data: {},
});
expect(metadata.isDestroyed).toHaveBeenCalled();
expect(metadata.getCache).toHaveBeenCalled();
expect(metadata.getMetadataCacheKey).toHaveBeenCalledWith(file.id);
expect(metadata.successHandler).toHaveBeenCalledWith(updatedMetadata);
expect(metadata.errorHandler).not.toHaveBeenCalled();
expect(cache.get('metadata_id')).toEqual({
templateInstances: [priorMetadata, updatedMetadata],
});
});
test('should make request but not update cache or call success handler when destroyed', async () => {
const success = jest.fn();
const error = jest.fn();
const file = {
id: 'id',
permissions: {
can_upload: true,
},
};
const cache = new Cache();
const template = { scope: 'scope', templateKey: 'templateKey', fields: [] };

const priorMetadata = {
instance: {
id: 'instance_id',
data: {
foo: 'bar',
},
},
};

cache.set('metadata_id', {
templateInstances: [priorMetadata],
});

metadata.getMetadataUrl = jest.fn().mockReturnValueOnce('url');
metadata.xhr.post = jest.fn().mockReturnValueOnce({ data: 'foo' });
metadata.isDestroyed = jest.fn().mockReturnValueOnce(true);
metadata.getCache = jest.fn().mockReturnValueOnce(cache);
metadata.getCacheKey = jest.fn().mockReturnValueOnce('cache_id');
metadata.getMetadataCacheKey = jest.fn().mockReturnValueOnce('metadata_id');
metadata.merge = jest.fn().mockReturnValueOnce('file');
metadata.successHandler = jest.fn();
metadata.errorHandler = jest.fn();

await metadata.createMetadataRedesign(file, template, success, error);

expect(metadata.successCallback).toBe(success);
expect(metadata.errorCallback).toBe(error);
expect(metadata.getMetadataUrl).toHaveBeenCalledWith(file.id, 'scope', 'templateKey');
expect(metadata.xhr.post).toHaveBeenCalledWith({
url: 'url',
id: 'file_id',
data: {},
});
expect(metadata.isDestroyed).toHaveBeenCalled();
expect(metadata.getCache).not.toHaveBeenCalled();
expect(metadata.getMetadataCacheKey).not.toHaveBeenCalled();
expect(metadata.successHandler).not.toHaveBeenCalled();
expect(metadata.errorHandler).not.toHaveBeenCalled();
expect(cache.get('metadata_id')).toEqual({
templateInstances: [priorMetadata],
});
});
test('should make request and call error handler for error', async () => {
const success = jest.fn();
const error = jest.fn();
const file = {
id: 'id',
permissions: {
can_upload: true,
},
};
const cache = new Cache();
const template = { scope: 'scope', templateKey: 'templateKey', fields: [] };
const xhrError = new Error('error');
const priorMetadata = {
instance: {
id: 'instance_id',
data: {
foo: 'bar',
},
},
};

cache.set('metadata_id', {
templateInstances: [priorMetadata],
});

metadata.getMetadataUrl = jest.fn().mockReturnValueOnce('url');
metadata.xhr.post = jest.fn().mockReturnValueOnce(Promise.reject(xhrError));
metadata.isDestroyed = jest.fn().mockReturnValueOnce(false);
metadata.getCache = jest.fn().mockReturnValueOnce(cache);
metadata.getCacheKey = jest.fn().mockReturnValueOnce('cache_id');
metadata.getMetadataCacheKey = jest.fn().mockReturnValueOnce('metadata_id');
metadata.merge = jest.fn().mockReturnValueOnce('file');
metadata.successHandler = jest.fn();
metadata.errorHandler = jest.fn();

await metadata.createMetadataRedesign(file, template, success, error);

expect(metadata.successCallback).toBe(success);
expect(metadata.errorCallback).toBe(error);
expect(metadata.getMetadataUrl).toHaveBeenCalledWith(file.id, 'scope', 'templateKey');
expect(metadata.xhr.post).toHaveBeenCalledWith({
url: 'url',
id: 'file_id',
data: {},
});
expect(metadata.isDestroyed).not.toHaveBeenCalled();
expect(metadata.getCache).not.toHaveBeenCalled();
expect(metadata.getMetadataCacheKey).not.toHaveBeenCalled();
expect(metadata.successHandler).not.toHaveBeenCalled();
expect(cache.get('metadata_id')).toEqual({
templateInstances: [priorMetadata],
});
expect(metadata.errorHandler).toHaveBeenCalledWith(xhrError);
});
});

describe('deleteMetadata()', () => {
test('should call error callback with a bad item error when no file', () => {
jest.spyOn(ErrorUtil, 'getBadItemError').mockReturnValueOnce('error');
Expand Down
Loading

0 comments on commit 321ba7e

Please sign in to comment.