Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(metadata-sidebar): Handle create metadata isntance #3663

Merged
merged 25 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
39a26aa
chore(content-sidebar): Temporarily remove files
jankowiakdawid Aug 1, 2024
78d9f62
feat(metadata-sidebar): onSave api calls
karolinaru Sep 11, 2024
e51de76
feat(metadata-sidebar): tests fix
karolinaru Sep 13, 2024
ff9df2e
feat(metadata-sidebar): create metadata
karolinaru Sep 18, 2024
04bfbbc
feat(metadata-sidebar): fix errors
karolinaru Sep 18, 2024
70137d1
feat(metadata-sidebar): fix test
karolinaru Sep 18, 2024
cc30903
feat(metadata-sidebar): additional error test
karolinaru Sep 18, 2024
038e83b
feat(metadata-sidebar): gitignore
karolinaru Sep 18, 2024
c87459e
feat(metadata-sidebar): restore files
karolinaru Sep 18, 2024
5a95811
feat(metadata-sidebar): pr comments
karolinaru Sep 19, 2024
d0f1e32
feat(metadata-sidebar): test fix
karolinaru Sep 19, 2024
8882751
feat(metadata-sidebar): success callback and api exceptions
karolinaru Sep 19, 2024
ee1a8d1
feat(metadata-sidebar): unit test
karolinaru Sep 19, 2024
3539708
feat(metadata-sidebar): test fix and pr comment
karolinaru Sep 23, 2024
b336308
feat(metadata-sidebar): changes to separate types to pass pipeline
karolinaru Sep 23, 2024
892b923
feat(metadata-sidebar): function name change
karolinaru Sep 23, 2024
870909c
feat(metadata-sidebar): yarn.lock and nme change
karolinaru Sep 23, 2024
6325da9
feat(metadata-sidebar): function type change
karolinaru Sep 23, 2024
3d0c7d4
feat(metadata-sidebar): function type change
karolinaru Sep 23, 2024
834eab0
feat(metadata-sidebar): yarn.lock update
karolinaru Sep 23, 2024
de1f37e
feat(metadata-sidebar): optimize saving function
karolinaru Sep 24, 2024
bbe4109
feat(metadata-sidebar): direct imports and extracted function
karolinaru Sep 24, 2024
ca2cd0d
feat(metadata-sidebar): remove empty update function
karolinaru Sep 24, 2024
c391902
feat(metadata-sidebar): metadata-editor bump
karolinaru Sep 24, 2024
a51563d
feat(metadata-sidebar): isLoadin prop remove after bump
karolinaru Sep 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading