fix: resolve unauthorized error on button click for normal roles (#5206)

* feat(server): add 'upgrade' action for uiSchemas

* test: add unit tests

* chore: update unit test

* refactor: rename 'upgrade' to 'initializeActionContext'

* fix: transaction

* chore: fix build

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
Zeke Zhang 2024-09-07 20:41:41 +08:00 committed by GitHub
parent 4365c3ea4a
commit 68c3d1d62a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 198 additions and 8 deletions

View File

@ -54,12 +54,11 @@ describe('usePopupContextInActionOrAssociationField', () => {
dataSource: 'dataSource', dataSource: 'dataSource',
collection: 'collection', collection: 'collection',
association: 'association', association: 'association',
sourceId: 'sourceId',
}; };
result.current.updatePopupContext(context); result.current.updatePopupContext(context);
expect(dnMock.emit).toHaveBeenCalledWith('patch', { expect(dnMock.emit).toHaveBeenCalledWith('initializeActionContext', {
schema: { schema: {
'x-uid': fieldSchemaMock['x-uid'], 'x-uid': fieldSchemaMock['x-uid'],
'x-action-context': context, 'x-action-context': context,
@ -84,9 +83,8 @@ describe('usePopupContextInActionOrAssociationField', () => {
result.current.updatePopupContext({ result.current.updatePopupContext({
...context, ...context,
collection: undefined, collection: undefined,
sourceId: null,
}); });
expect(dnMock.emit).toHaveBeenCalledWith('patch', { expect(dnMock.emit).toHaveBeenCalledWith('initializeActionContext', {
schema: { schema: {
'x-uid': fieldSchemaMock['x-uid'], 'x-uid': fieldSchemaMock['x-uid'],
'x-action-context': { 'x-action-context': {

View File

@ -39,7 +39,7 @@ export const usePopupContextInActionOrAssociationField = () => {
customSchema[CONTEXT_SCHEMA_KEY] = context; customSchema[CONTEXT_SCHEMA_KEY] = context;
return dn.emit('patch', { return dn.emit('initializeActionContext', {
schema: { schema: {
'x-uid': customSchema['x-uid'], 'x-uid': customSchema['x-uid'],
[CONTEXT_SCHEMA_KEY]: context, [CONTEXT_SCHEMA_KEY]: context,

View File

@ -217,6 +217,19 @@ export class Designable {
}); });
message.success(t('Saved successfully'), 0.2); message.success(t('Saved successfully'), 0.2);
}); });
this.on('initializeActionContext', async ({ schema }) => {
this.refresh();
if (!schema?.['x-uid']) {
return;
}
await api.request({
url: `/uiSchemas:initializeActionContext`,
method: 'post',
data: {
...schema,
},
});
});
this.on('batchPatch', async ({ schemas }) => { this.on('batchPatch', async ({ schemas }) => {
this.refresh(); this.refresh();
await api.request({ await api.request({
@ -267,14 +280,17 @@ export class Designable {
generateUid(schema); generateUid(schema);
} }
on(name: 'insertAdjacent' | 'remove' | 'error' | 'patch' | 'batchPatch', listener: any) { on(name: 'insertAdjacent' | 'remove' | 'error' | 'patch' | 'batchPatch' | 'initializeActionContext', listener: any) {
if (!this.events[name]) { if (!this.events[name]) {
this.events[name] = []; this.events[name] = [];
} }
this.events[name].push(listener); this.events[name].push(listener);
} }
async emit(name: 'insertAdjacent' | 'remove' | 'error' | 'patch' | 'batchPatch', ...args) { async emit(
name: 'insertAdjacent' | 'remove' | 'error' | 'patch' | 'batchPatch' | 'initializeActionContext',
...args
) {
if (!this.events[name]) { if (!this.events[name]) {
return; return;
} }

View File

@ -248,6 +248,65 @@ describe('action test', () => {
expect(data.properties.b['properties']['c']['title']).toEqual('c-title'); expect(data.properties.b['properties']['c']['title']).toEqual('c-title');
}); });
test('initializeActionContext', async () => {
await app
.agent()
.resource('uiSchemas')
.insert({
values: {
'x-uid': 'n1',
name: 'a',
type: 'object',
properties: {
b: {
'x-uid': 'n2',
type: 'object',
properties: {
c: { 'x-uid': 'n3' },
},
},
d: { 'x-uid': 'n4' },
},
},
});
let response = await app
.agent()
.resource('uiSchemas')
.initializeActionContext({
values: {
'x-uid': 'n1',
'x-action-context': {
field1: 'field1',
field2: 'field2',
},
properties: {
b: {
properties: {
c: {
title: 'c-title',
},
},
},
},
},
});
expect(response.statusCode).toEqual(200);
response = await app.agent().resource('uiSchemas').getJsonSchema({
resourceIndex: 'n1',
});
const { data } = response.body;
// only update the x-action-context
expect(data.properties.b['properties']['c']['title']).toBe(undefined);
expect(data['x-action-context']).toEqual({
field1: 'field1',
field2: 'field2',
});
});
test('insert adjacent', async () => { test('insert adjacent', async () => {
await app await app
.agent() .agent()

View File

@ -952,6 +952,96 @@ describe('ui_schema repository', () => {
}); });
}); });
describe('initializeActionContext', function () {
let rootNode;
let rootUid: string;
let oldTree;
beforeEach(async () => {
const root = {
type: 'object',
title: 'title',
name: 'root',
properties: {
a1: {
type: 'string',
title: 'A1',
'x-component': 'Input',
},
b1: {
type: 'string',
title: 'B1',
properties: {
c1: {
type: 'string',
title: 'C1',
},
d1: {
type: 'string',
title: 'D1',
},
},
},
},
};
await repository.insert(root);
rootNode = await repository.findOne({
filter: {
name: 'root',
},
});
rootUid = rootNode.get('x-uid') as string;
oldTree = await repository.getJsonSchema(rootUid);
});
it('should update root ui schema and only have x-action-context to be updated', async () => {
await repository.initializeActionContext({
'x-uid': rootUid,
title: 'test-title',
['x-action-context']: {
field1: 'field1',
field2: 'field2',
},
properties: {
a1: {
type: 'string',
title: 'new a1 title',
'x-component': 'Input',
},
},
});
const newTree = await repository.getJsonSchema(rootUid);
expect(newTree).toEqual({
...oldTree,
['x-action-context']: {
field1: 'field1',
field2: 'field2',
},
});
// will not updated when x-action-context existed
await repository.initializeActionContext({
'x-uid': rootUid,
['x-action-context']: {
field3: 'field3',
field4: 'field4',
},
});
expect(newTree).toEqual({
...oldTree,
['x-action-context']: {
field1: 'field1',
field2: 'field2',
},
});
});
});
it('should insertInner with removeParent', async () => { it('should insertInner with removeParent', async () => {
const schema = { const schema = {
'x-uid': 'A', 'x-uid': 'A',

View File

@ -67,6 +67,7 @@ export const uiSchemaActions = {
insertNewSchema: callRepositoryMethod('insertNewSchema', 'values'), insertNewSchema: callRepositoryMethod('insertNewSchema', 'values'),
remove: callRepositoryMethod('remove', 'resourceIndex'), remove: callRepositoryMethod('remove', 'resourceIndex'),
patch: callRepositoryMethod('patch', 'values'), patch: callRepositoryMethod('patch', 'values'),
initializeActionContext: callRepositoryMethod('initializeActionContext', 'values'),
batchPatch: callRepositoryMethod('batchPatch', 'values'), batchPatch: callRepositoryMethod('batchPatch', 'values'),
clearAncestor: callRepositoryMethod('clearAncestor', 'resourceIndex'), clearAncestor: callRepositoryMethod('clearAncestor', 'resourceIndex'),

View File

@ -331,6 +331,28 @@ export class UiSchemaRepository extends Repository {
await traverSchemaTree(newSchema); await traverSchemaTree(newSchema);
} }
@transaction()
async initializeActionContext(newSchema: any, options: any = {}) {
if (!newSchema['x-uid'] || !newSchema['x-action-context']) {
return;
}
const { transaction } = options;
const nodeModel = await this.findOne({
filter: {
'x-uid': newSchema['x-uid'],
},
transaction,
});
if (!lodash.isEmpty(nodeModel?.get('schema')['x-action-context'])) {
return;
}
return this.patch(lodash.pick(newSchema, ['x-uid', 'x-action-context']), options);
}
@transaction() @transaction()
async batchPatch(schemas: any[], options?) { async batchPatch(schemas: any[], options?) {
const { transaction } = options; const { transaction } = options;

View File

@ -80,7 +80,11 @@ export class PluginUISchemaStorageServer extends Plugin {
actions: uiSchemaActions, actions: uiSchemaActions,
}); });
this.app.acl.allow('uiSchemas', ['getProperties', 'getJsonSchema', 'getParentJsonSchema'], 'loggedIn'); this.app.acl.allow(
'uiSchemas',
['getProperties', 'getJsonSchema', 'getParentJsonSchema', 'initializeActionContext'],
'loggedIn',
);
this.app.acl.allow('uiSchemaTemplates', ['get', 'list'], 'loggedIn'); this.app.acl.allow('uiSchemaTemplates', ['get', 'list'], 'loggedIn');
} }