feat: insertNewSchema (#245)
* fix: transaction error * stage * feat: insertNewSchema * feat: insertNewSchema Return Value * test * fix: insertAdjacent with root node * feat: insertAdjacent with wrap * fix: test * feat: wrap with new schema * feat: action with wrap params * feat: improve client * feat: improve client * fix: test Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
4422324980
commit
ed8c60eb85
@ -41,7 +41,7 @@ const RequestSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) =>
|
||||
}
|
||||
return (
|
||||
<FormProvider form={form}>
|
||||
<SchemaComponent memoized scope={scope} schema={schemaTransform(data?.data || {})} />;
|
||||
<SchemaComponent memoized scope={scope} schema={schemaTransform(data?.data || {})} />
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,87 @@
|
||||
import { Schema } from '@formily/react';
|
||||
import { splitWrapSchema } from '../useDesignable';
|
||||
|
||||
describe('splitWrapSchema', () => {
|
||||
test('case 1', () => {
|
||||
const wrap = new Schema({
|
||||
'x-uid': 'aaa',
|
||||
properties: {
|
||||
aa: {
|
||||
'x-uid': 'aa',
|
||||
properties: {
|
||||
a: {
|
||||
'x-uid': '123',
|
||||
properties: {
|
||||
bbb: {
|
||||
'x-uid': 'bbb',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const [schema1, schema2] = splitWrapSchema(wrap, { ['x-uid']: 'aaa' });
|
||||
expect(schema1).toBeNull();
|
||||
expect(schema2).toMatchObject({
|
||||
'x-uid': 'aaa',
|
||||
properties: {
|
||||
aa: {
|
||||
'x-uid': 'aa',
|
||||
properties: {
|
||||
a: {
|
||||
'x-uid': '123',
|
||||
properties: {
|
||||
bbb: {
|
||||
'x-uid': 'bbb',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('case 2', () => {
|
||||
const wrap = new Schema({
|
||||
'x-uid': 'aaa',
|
||||
properties: {
|
||||
aa: {
|
||||
'x-uid': 'aa',
|
||||
properties: {
|
||||
a: {
|
||||
'x-uid': '123',
|
||||
properties: {
|
||||
bbb: {
|
||||
'x-uid': 'bbb',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const [schema1, schema2] = splitWrapSchema(wrap, { ['x-uid']: '123' });
|
||||
expect(schema1).toMatchObject({
|
||||
'x-uid': 'aaa',
|
||||
properties: {
|
||||
aa: {
|
||||
'x-uid': 'aa',
|
||||
properties: {},
|
||||
name: 'aa',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(schema2).toMatchObject({
|
||||
'x-uid': '123',
|
||||
properties: {
|
||||
bbb: {
|
||||
'x-uid': 'bbb',
|
||||
name: 'bbb',
|
||||
},
|
||||
},
|
||||
name: 'a',
|
||||
});
|
||||
});
|
||||
});
|
@ -67,6 +67,29 @@ const matchSchema = (source: ISchema, target: ISchema) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const splitWrapSchema = (wrapped: Schema, schema: ISchema) => {
|
||||
if (wrapped['x-uid'] && wrapped['x-uid'] === schema['x-uid']) {
|
||||
return [null, wrapped.toJSON()];
|
||||
}
|
||||
const wrappedJson: ISchema = wrapped.toJSON();
|
||||
let schema1 = { ...wrappedJson, properties: {} };
|
||||
let schema2 = null;
|
||||
const findSchema = (properties, parent) => {
|
||||
Object.keys(properties || {}).forEach((key) => {
|
||||
const current = properties[key];
|
||||
if (current['x-uid'] === schema['x-uid']) {
|
||||
schema2 = properties[key];
|
||||
return;
|
||||
} else {
|
||||
parent.properties[key] = { ...current, properties: {} };
|
||||
findSchema(current?.properties, parent.properties[key]);
|
||||
}
|
||||
});
|
||||
};
|
||||
findSchema(wrappedJson.properties, schema1);
|
||||
return [schema1, schema2];
|
||||
};
|
||||
|
||||
export class Designable {
|
||||
current: Schema;
|
||||
options: CreateDesignableProps;
|
||||
@ -82,12 +105,15 @@ export class Designable {
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
this.on('insertAdjacent', async ({ onSuccess, current, position, schema, removed }) => {
|
||||
this.on('insertAdjacent', async ({ onSuccess, current, position, schema, wrap, removed }) => {
|
||||
refresh();
|
||||
await api.request({
|
||||
url: `/uiSchemas:insertAdjacent/${current['x-uid']}?position=${position}`,
|
||||
method: 'post',
|
||||
data: schema.toJSON(),
|
||||
data: {
|
||||
schema,
|
||||
wrap,
|
||||
},
|
||||
});
|
||||
if (removed?.['x-uid']) {
|
||||
await api.request({
|
||||
@ -313,9 +339,11 @@ export class Designable {
|
||||
s['x-index'] = newOrder;
|
||||
s.parent = this.current.parent;
|
||||
this.current.parent.setProperties(properties);
|
||||
const [schema1, schema2] = splitWrapSchema(s, schema);
|
||||
this.emit('insertAdjacent', {
|
||||
position: 'beforeBegin',
|
||||
schema: s,
|
||||
schema: schema2,
|
||||
wrap: schema1,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
@ -359,9 +387,11 @@ export class Designable {
|
||||
s['x-index'] = 0;
|
||||
s.parent = this.current;
|
||||
this.current.setProperties(properties);
|
||||
const [schema1, schema2] = splitWrapSchema(s, schema);
|
||||
this.emit('insertAdjacent', {
|
||||
position: 'afterBegin',
|
||||
schema: s,
|
||||
schema: schema2,
|
||||
wrap: schema1,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
@ -395,9 +425,11 @@ export class Designable {
|
||||
const wrapped = wrap(schema);
|
||||
const s = this.current.addProperty(wrapped.name || uid(), wrapped);
|
||||
s.parent = this.current;
|
||||
const [schema1, schema2] = splitWrapSchema(s, schema);
|
||||
this.emit('insertAdjacent', {
|
||||
position: 'beforeEnd',
|
||||
schema: s,
|
||||
schema: schema2,
|
||||
wrap: schema1,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
@ -451,9 +483,11 @@ export class Designable {
|
||||
s.parent = this.current.parent;
|
||||
s['x-index'] = newOrder;
|
||||
this.current.parent.setProperties(properties);
|
||||
const [schema1, schema2] = splitWrapSchema(s, schema);
|
||||
this.emit('insertAdjacent', {
|
||||
position: 'afterEnd',
|
||||
schema: s,
|
||||
schema: schema2,
|
||||
wrap: schema1,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
|
@ -229,4 +229,33 @@ describe('action test', () => {
|
||||
const { data } = response.body;
|
||||
expect(data.properties.e['x-uid']).toEqual('n5');
|
||||
});
|
||||
|
||||
test('insert adjacent with bit schema', async () => {
|
||||
const schema = require('./fixtures/data').default;
|
||||
|
||||
await app
|
||||
.agent()
|
||||
.resource('uiSchemas')
|
||||
.insert({
|
||||
values: {
|
||||
'x-uid': 'root',
|
||||
properties: {
|
||||
A: {
|
||||
'x-uid': 'A',
|
||||
},
|
||||
B: {
|
||||
'x-uid': 'B',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let response = await app.agent().resource('uiSchemas').insertAdjacent({
|
||||
resourceIndex: 'A',
|
||||
position: 'afterEnd',
|
||||
values: schema,
|
||||
});
|
||||
|
||||
expect(response.statusCode).toEqual(200);
|
||||
});
|
||||
});
|
||||
|
6350
packages/plugin-ui-schema-storage/src/__tests__/fixtures/data.ts
Normal file
6350
packages/plugin-ui-schema-storage/src/__tests__/fixtures/data.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,35 @@
|
||||
export default {
|
||||
name: 'root-name',
|
||||
'x-uid': 'root',
|
||||
properties: {
|
||||
p1: {
|
||||
'x-uid': 'p1',
|
||||
'x-component-props': {
|
||||
title: "alias's",
|
||||
},
|
||||
},
|
||||
p2: {
|
||||
'x-uid': 'p2',
|
||||
properties: {
|
||||
p21: {
|
||||
'x-uid': 'p21',
|
||||
properties: {
|
||||
p211: {
|
||||
'x-uid': 'p211',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
items: [
|
||||
{
|
||||
name: 'i1',
|
||||
'x-uid': 'i1',
|
||||
},
|
||||
{
|
||||
name: 'i2',
|
||||
'x-uid': 'i2',
|
||||
},
|
||||
],
|
||||
};
|
@ -338,20 +338,24 @@ describe('server hooks', () => {
|
||||
|
||||
await uiSchemaRepository.insert(schema);
|
||||
|
||||
await uiSchemaRepository.insertAfterEnd('E', {
|
||||
await uiSchemaRepository.insertAdjacent(
|
||||
'afterEnd',
|
||||
'E',
|
||||
{
|
||||
'x-uid': 'D',
|
||||
},
|
||||
{
|
||||
wrap: {
|
||||
'x-uid': 'F',
|
||||
name: 'F',
|
||||
properties: {
|
||||
G: {
|
||||
'x-uid': 'G',
|
||||
properties: {
|
||||
D: {
|
||||
'x-uid': 'D',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
const A = await uiSchemaRepository.getJsonSchema('A');
|
||||
expect(A).toEqual({
|
||||
|
@ -316,25 +316,24 @@ describe('server hooks', () => {
|
||||
|
||||
await uiSchemaRepository.insert(schema);
|
||||
|
||||
await uiSchemaRepository.insertAfterEnd(
|
||||
await uiSchemaRepository.insertAdjacent(
|
||||
'afterEnd',
|
||||
'E',
|
||||
{
|
||||
'x-uid': 'D',
|
||||
},
|
||||
{
|
||||
removeParentsIfNoChildren: true,
|
||||
wrap: {
|
||||
'x-uid': 'F',
|
||||
name: 'F',
|
||||
properties: {
|
||||
G: {
|
||||
'x-uid': 'G',
|
||||
properties: {
|
||||
D: {
|
||||
'x-uid': 'D',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
removeParentsIfNoChildren: true,
|
||||
},
|
||||
);
|
||||
|
||||
expect(jestFn).toHaveBeenCalled();
|
||||
|
@ -418,7 +418,7 @@ describe('ui_schema repository', () => {
|
||||
};
|
||||
|
||||
await repository.insert(schema);
|
||||
await repository.insertBeforeBegin('i1', {
|
||||
await repository.insertAdjacent('beforeBegin', 'i1', {
|
||||
'x-uid': 'i2',
|
||||
});
|
||||
|
||||
@ -476,7 +476,7 @@ describe('ui_schema repository', () => {
|
||||
name: 'newNode',
|
||||
};
|
||||
|
||||
await repository.insertAfterBegin(rootUid, newNode);
|
||||
await repository.insertAdjacent('afterBegin', rootUid, newNode);
|
||||
const schema = await repository.getJsonSchema(rootUid);
|
||||
expect(schema['properties']['newNode']['x-index']).toEqual(1);
|
||||
expect(schema['properties'].a1['x-index']).toEqual(2);
|
||||
@ -488,7 +488,7 @@ describe('ui_schema repository', () => {
|
||||
name: 'newNode',
|
||||
};
|
||||
|
||||
await repository.insertBeforeEnd(rootUid, newNode);
|
||||
await repository.insertAdjacent('beforeEnd', rootUid, newNode);
|
||||
const schema = await repository.getJsonSchema(rootUid);
|
||||
expect(schema['properties']['newNode']['x-index']).toEqual(3);
|
||||
expect(schema['properties'].a1['x-index']).toEqual(1);
|
||||
@ -506,7 +506,7 @@ describe('ui_schema repository', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await repository.insertBeforeBegin(b1Node.get('x-uid') as string, newNode);
|
||||
await repository.insertAdjacent('beforeBegin', b1Node.get('x-uid') as string, newNode);
|
||||
|
||||
const schema = await repository.getJsonSchema(rootUid);
|
||||
expect(schema['properties']['newNode']['x-index']).toEqual(2);
|
||||
@ -525,7 +525,7 @@ describe('ui_schema repository', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await repository.insertAfterEnd(a1Node.get('x-uid') as string, newNode);
|
||||
await repository.insertAdjacent('afterEnd', a1Node.get('x-uid') as string, newNode);
|
||||
|
||||
const schema = await repository.getJsonSchema(rootUid);
|
||||
expect(schema['properties']['newNode']['x-index']).toEqual(2);
|
||||
@ -544,7 +544,7 @@ describe('ui_schema repository', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await repository.insertAfterEnd(b1Node.get('x-uid') as string, newNode);
|
||||
await repository.insertAdjacent('afterEnd', b1Node.get('x-uid') as string, newNode);
|
||||
|
||||
const schema = await repository.getJsonSchema(rootUid);
|
||||
expect(schema['properties']['newNode']['x-index']).toEqual(3);
|
||||
@ -578,21 +578,28 @@ describe('ui_schema repository', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await repository.insertAfterBegin('n1', {
|
||||
await repository.insertAdjacent(
|
||||
'afterBegin',
|
||||
'n1',
|
||||
{
|
||||
'x-uid': 'n4',
|
||||
},
|
||||
{
|
||||
wrap: {
|
||||
name: 'f',
|
||||
'x-uid': 'n6',
|
||||
properties: {
|
||||
g: {
|
||||
'x-uid': 'n7',
|
||||
properties: {
|
||||
d: { 'x-uid': 'n4' },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const schema = await repository.getJsonSchema('n1');
|
||||
expect(schema.properties.f.properties.g.properties.d.properties.e['x-uid']).toEqual('n5');
|
||||
expect(schema.properties.f.properties.g.properties.d['x-uid']).toEqual('n4');
|
||||
});
|
||||
|
||||
it('should insertAfterBegin by node', async () => {
|
||||
@ -612,7 +619,7 @@ describe('ui_schema repository', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await repository.insertAfterBegin('n2', 'n4');
|
||||
await repository.insertAdjacent('afterBegin', 'n2', 'n4');
|
||||
const schema = await repository.getJsonSchema('n1');
|
||||
expect(schema['properties'].b.properties.d['x-uid']).toEqual('n4');
|
||||
});
|
||||
@ -878,25 +885,24 @@ describe('ui_schema repository', () => {
|
||||
|
||||
await repository.insert(schema);
|
||||
|
||||
await repository.insertAfterBegin(
|
||||
await repository.insertAdjacent(
|
||||
'afterBegin',
|
||||
'E',
|
||||
{
|
||||
'x-uid': 'D',
|
||||
},
|
||||
{
|
||||
removeParentsIfNoChildren: true,
|
||||
wrap: {
|
||||
'x-uid': 'F',
|
||||
name: 'F',
|
||||
properties: {
|
||||
G: {
|
||||
'x-uid': 'G',
|
||||
properties: {
|
||||
D: {
|
||||
'x-uid': 'D',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
removeParentsIfNoChildren: true,
|
||||
},
|
||||
);
|
||||
|
||||
const A = await repository.getJsonSchema('A');
|
||||
@ -962,25 +968,24 @@ describe('ui_schema repository', () => {
|
||||
|
||||
await repository.insert(schema);
|
||||
|
||||
await repository.insertAfterEnd(
|
||||
await repository.insertAdjacent(
|
||||
'afterEnd',
|
||||
'E',
|
||||
{
|
||||
'x-uid': 'D',
|
||||
},
|
||||
{
|
||||
removeParentsIfNoChildren: true,
|
||||
wrap: {
|
||||
'x-uid': 'F',
|
||||
name: 'F',
|
||||
properties: {
|
||||
G: {
|
||||
'x-uid': 'G',
|
||||
properties: {
|
||||
D: {
|
||||
'x-uid': 'D',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
removeParentsIfNoChildren: true,
|
||||
},
|
||||
);
|
||||
|
||||
const A = await repository.getJsonSchema('A');
|
||||
@ -1108,4 +1113,490 @@ describe('ui_schema repository', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should insert big schema', async () => {
|
||||
const schema = require('./fixtures/data').default;
|
||||
|
||||
console.time('test');
|
||||
await repository.insertNewSchema(schema);
|
||||
console.timeEnd('test');
|
||||
|
||||
const rootUid = schema['x-uid'];
|
||||
const savedSchema = await repository.getJsonSchema(rootUid);
|
||||
expect(savedSchema).toBeDefined();
|
||||
});
|
||||
|
||||
it('should insert new with insertAfterEnd', async () => {
|
||||
const root = {
|
||||
'x-uid': 'root',
|
||||
name: 'root',
|
||||
properties: {
|
||||
c1: {
|
||||
'x-uid': 'c1',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const newNode = {
|
||||
'x-uid': 'new',
|
||||
name: 'new',
|
||||
properties: {
|
||||
nc1: {
|
||||
'x-uid': 'nc1',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await repository.insertNewSchema(root);
|
||||
await repository.insertNewSchema(newNode);
|
||||
|
||||
await repository.insertAdjacent('afterEnd', 'c1', {
|
||||
'x-uid': 'new',
|
||||
});
|
||||
|
||||
const json = await repository.getJsonSchema('root');
|
||||
expect(json).toEqual({
|
||||
properties: {
|
||||
c1: {
|
||||
'x-uid': 'c1',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
new: {
|
||||
properties: {
|
||||
nc1: {
|
||||
'x-uid': 'nc1',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'new',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
},
|
||||
name: 'root',
|
||||
'x-uid': 'root',
|
||||
'x-async': false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should insert big schema using insertAfterEnd', async () => {
|
||||
const tree = {
|
||||
'x-uid': 'root',
|
||||
properties: {
|
||||
A: {
|
||||
'x-uid': 'A',
|
||||
},
|
||||
B: {
|
||||
'x-uid': 'B',
|
||||
},
|
||||
},
|
||||
};
|
||||
await repository.insert(tree);
|
||||
const schema = require('./fixtures/data').default;
|
||||
|
||||
await repository.insertAdjacent('afterEnd', 'A', schema);
|
||||
const rootUid = schema['x-uid'];
|
||||
const savedSchema = await repository.getJsonSchema(rootUid);
|
||||
expect(savedSchema).toBeDefined();
|
||||
});
|
||||
|
||||
describe('schemaToSingleNodes', () => {
|
||||
it('should with parent Paths', async () => {
|
||||
const schema = {
|
||||
name: 'root-name',
|
||||
'x-uid': 'root',
|
||||
properties: {
|
||||
p1: {
|
||||
'x-uid': 'p1',
|
||||
},
|
||||
p2: {
|
||||
'x-uid': 'p2',
|
||||
properties: {
|
||||
p21: {
|
||||
'x-uid': 'p21',
|
||||
properties: {
|
||||
p211: {
|
||||
'x-uid': 'p211',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
items: [
|
||||
{
|
||||
name: 'i1',
|
||||
'x-uid': 'i1',
|
||||
},
|
||||
{
|
||||
name: 'i2',
|
||||
'x-uid': 'i2',
|
||||
},
|
||||
],
|
||||
};
|
||||
const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
|
||||
const p211Node = nodes.find((node) => node['x-uid'] === 'p211');
|
||||
expect(p211Node['childOptions'].parentPath).toEqual(['p21', 'p2', 'root']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertAdjacent', () => {
|
||||
it('should works with wrap and new schema', async () => {
|
||||
const schema = {
|
||||
name: 'root-name',
|
||||
'x-uid': 'root',
|
||||
properties: {
|
||||
p1: {
|
||||
'x-uid': 'p1',
|
||||
properties: {
|
||||
p11: {
|
||||
'x-uid': 'p11',
|
||||
},
|
||||
},
|
||||
},
|
||||
p2: {
|
||||
'x-uid': 'p2',
|
||||
properties: {
|
||||
p21: {
|
||||
'x-uid': 'p21',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await repository.insert(schema);
|
||||
|
||||
await repository.insertAdjacent(
|
||||
'afterEnd',
|
||||
'p2',
|
||||
{
|
||||
name: 'p311',
|
||||
'x-uid': 'p311',
|
||||
},
|
||||
{
|
||||
wrap: {
|
||||
'x-uid': 'p3',
|
||||
name: 'p3',
|
||||
properties: {
|
||||
p31: {
|
||||
'x-uid': 'p31',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const root = await repository.getJsonSchema('root');
|
||||
expect(root).toEqual({
|
||||
properties: {
|
||||
p1: {
|
||||
properties: {
|
||||
p11: {
|
||||
'x-uid': 'p11',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'p1',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
p2: {
|
||||
properties: {
|
||||
p21: {
|
||||
'x-uid': 'p21',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'p2',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
p3: {
|
||||
properties: {
|
||||
p31: {
|
||||
properties: {
|
||||
p311: {
|
||||
'x-uid': 'p311',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'p31',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'p3',
|
||||
'x-async': false,
|
||||
'x-index': 3,
|
||||
},
|
||||
},
|
||||
name: 'root-name',
|
||||
'x-uid': 'root',
|
||||
'x-async': false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should works with wrap', async () => {
|
||||
const schema = {
|
||||
name: 'root-name',
|
||||
'x-uid': 'root',
|
||||
properties: {
|
||||
p1: {
|
||||
'x-uid': 'p1',
|
||||
properties: {
|
||||
p11: {
|
||||
'x-uid': 'p11',
|
||||
},
|
||||
},
|
||||
},
|
||||
p2: {
|
||||
'x-uid': 'p2',
|
||||
properties: {
|
||||
p21: {
|
||||
'x-uid': 'p21',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await repository.insert(schema);
|
||||
|
||||
await repository.insertAdjacent(
|
||||
'afterEnd',
|
||||
'p1',
|
||||
{
|
||||
'x-uid': 'p21',
|
||||
},
|
||||
{
|
||||
removeParentsIfNoChildren: true,
|
||||
breakRemoveOn: {
|
||||
'x-uid': 'root',
|
||||
},
|
||||
wrap: {
|
||||
'x-uid': 'p3',
|
||||
name: 'p3',
|
||||
properties: {
|
||||
p31: {
|
||||
'x-uid': 'p31',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const root = await repository.getJsonSchema('root');
|
||||
expect(root).toEqual({
|
||||
properties: {
|
||||
p1: {
|
||||
properties: {
|
||||
p11: {
|
||||
'x-uid': 'p11',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'p1',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
p3: {
|
||||
properties: {
|
||||
p31: {
|
||||
properties: {
|
||||
p21: {
|
||||
'x-uid': 'p21',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'p31',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'p3',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
},
|
||||
name: 'root-name',
|
||||
'x-uid': 'root',
|
||||
'x-async': false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should insert newSchema using insertNewSchema', async () => {
|
||||
const schema = {
|
||||
name: 'root-name',
|
||||
'x-uid': 'root',
|
||||
properties: {
|
||||
p1: {
|
||||
'x-uid': 'p1',
|
||||
properties: {
|
||||
p11: {
|
||||
'x-uid': 'p11',
|
||||
},
|
||||
},
|
||||
},
|
||||
p2: {
|
||||
'x-uid': 'p2',
|
||||
properties: {
|
||||
p21: {
|
||||
'x-uid': 'p21',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await repository.insert(schema);
|
||||
|
||||
await repository.insertAdjacent('afterEnd', 'p2', {
|
||||
'x-uid': 'p3',
|
||||
name: 'p3',
|
||||
properties: {
|
||||
p31: {
|
||||
'x-uid': 'p31',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const root = await repository.getJsonSchema('root');
|
||||
expect(root).toEqual({
|
||||
properties: {
|
||||
p1: {
|
||||
properties: {
|
||||
p11: {
|
||||
'x-uid': 'p11',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'p1',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
p2: {
|
||||
properties: {
|
||||
p21: {
|
||||
'x-uid': 'p21',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'p2',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
p3: {
|
||||
properties: {
|
||||
p31: {
|
||||
'x-uid': 'p31',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'p3',
|
||||
'x-async': false,
|
||||
'x-index': 3,
|
||||
},
|
||||
},
|
||||
name: 'root-name',
|
||||
'x-uid': 'root',
|
||||
'x-async': false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should insert oldSchema using first x-uid', async () => {
|
||||
const schema = {
|
||||
name: 'root-name',
|
||||
'x-uid': 'root',
|
||||
properties: {
|
||||
p1: {
|
||||
'x-uid': 'p1',
|
||||
properties: {
|
||||
p11: {
|
||||
'x-uid': 'p11',
|
||||
},
|
||||
},
|
||||
},
|
||||
p2: {
|
||||
'x-uid': 'p2',
|
||||
properties: {
|
||||
p21: {
|
||||
'x-uid': 'p21',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await repository.insert(schema);
|
||||
|
||||
const insertSchema = {
|
||||
'x-uid': 'p3',
|
||||
name: 'p3',
|
||||
properties: {
|
||||
p31: {
|
||||
'x-uid': 'p31',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await repository.insert(insertSchema);
|
||||
await repository.insertAdjacent('afterEnd', 'p2', insertSchema);
|
||||
|
||||
const root = await repository.getJsonSchema('root');
|
||||
expect(root).toEqual({
|
||||
properties: {
|
||||
p1: {
|
||||
properties: {
|
||||
p11: {
|
||||
'x-uid': 'p11',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'p1',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
p2: {
|
||||
properties: {
|
||||
p21: {
|
||||
'x-uid': 'p21',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'p2',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
p3: {
|
||||
properties: {
|
||||
p31: {
|
||||
'x-uid': 'p31',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'p3',
|
||||
'x-async': false,
|
||||
'x-index': 3,
|
||||
},
|
||||
},
|
||||
name: 'root-name',
|
||||
'x-uid': 'root',
|
||||
'x-async': false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Context } from '@nocobase/actions';
|
||||
import UiSchemaRepository from '../repository';
|
||||
import lodash from 'lodash';
|
||||
import UiSchemaRepository from '../repository';
|
||||
|
||||
const getRepositoryFromCtx = (ctx: Context) => {
|
||||
return ctx.db.getCollection('uiSchemas').repository as UiSchemaRepository;
|
||||
@ -21,10 +21,19 @@ const callRepositoryMethod = (method, paramsKey: 'resourceIndex' | 'values') =>
|
||||
};
|
||||
};
|
||||
|
||||
function parseInsertAdjacentValues(values) {
|
||||
if (lodash.has(values, 'schema')) {
|
||||
return values;
|
||||
}
|
||||
|
||||
return { schema: values, wrap: null };
|
||||
}
|
||||
|
||||
export const uiSchemaActions = {
|
||||
getJsonSchema: callRepositoryMethod('getJsonSchema', 'resourceIndex'),
|
||||
getProperties: callRepositoryMethod('getProperties', 'resourceIndex'),
|
||||
insert: callRepositoryMethod('insert', 'values'),
|
||||
insertNewSchema: callRepositoryMethod('insertNewSchema', 'values'),
|
||||
remove: callRepositoryMethod('remove', 'resourceIndex'),
|
||||
patch: callRepositoryMethod('patch', 'values'),
|
||||
clearAncestor: callRepositoryMethod('clearAncestor', 'resourceIndex'),
|
||||
@ -33,9 +42,12 @@ export const uiSchemaActions = {
|
||||
const { resourceIndex, position, values, removeParentsIfNoChildren, breakRemoveOn } = ctx.action.params;
|
||||
const repository = getRepositoryFromCtx(ctx);
|
||||
|
||||
ctx.body = await repository.insertAdjacent(position, resourceIndex, values, {
|
||||
const { schema, wrap } = parseInsertAdjacentValues(values);
|
||||
|
||||
ctx.body = await repository.insertAdjacent(position, resourceIndex, schema, {
|
||||
removeParentsIfNoChildren,
|
||||
breakRemoveOn,
|
||||
wrap,
|
||||
});
|
||||
|
||||
await next();
|
||||
@ -51,9 +63,12 @@ function insertPositionActionBuilder(position: 'beforeBegin' | 'afterBegin' | 'b
|
||||
return async function (ctx: Context, next) {
|
||||
const { resourceIndex, values, removeParentsIfNoChildren, breakRemoveOn } = ctx.action.params;
|
||||
const repository = getRepositoryFromCtx(ctx);
|
||||
ctx.body = await repository.insertAdjacent(position, resourceIndex, values, {
|
||||
const { schema, wrap } = parseInsertAdjacentValues(values);
|
||||
|
||||
ctx.body = await repository.insertAdjacent(position, resourceIndex, schema, {
|
||||
removeParentsIfNoChildren,
|
||||
breakRemoveOn,
|
||||
wrap,
|
||||
});
|
||||
await next();
|
||||
};
|
||||
|
@ -4,8 +4,10 @@ export interface TargetPosition {
|
||||
}
|
||||
export interface ChildOptions {
|
||||
parentUid: string;
|
||||
parentPath?: string[];
|
||||
type: string;
|
||||
position?: 'first' | 'last' | TargetPosition;
|
||||
sort?: number;
|
||||
}
|
||||
|
||||
export interface SchemaNode {
|
||||
|
@ -18,7 +18,9 @@ export interface removeParentOptions extends TransactionAble {
|
||||
breakRemoveOn?: BreakRemoveOnType;
|
||||
}
|
||||
|
||||
interface InsertAdjacentOptions extends removeParentOptions {}
|
||||
interface InsertAdjacentOptions extends removeParentOptions {
|
||||
wrap?: any;
|
||||
}
|
||||
|
||||
const nodeKeys = ['properties', 'definitions', 'patternProperties', 'additionalProperties', 'items'];
|
||||
|
||||
@ -111,31 +113,30 @@ export class UiSchemaRepository extends Repository {
|
||||
|
||||
for (const nodeKey of nodeKeys) {
|
||||
const nodeProperty = lodash.get(node, nodeKey);
|
||||
const childNodeChildOptions = {
|
||||
parentUid: node['x-uid'],
|
||||
parentPath: [node['x-uid'], ...lodash.get(childOptions, 'parentPath', [])],
|
||||
type: nodeKey,
|
||||
};
|
||||
|
||||
// array items
|
||||
if (nodeKey === 'items' && nodeProperty) {
|
||||
const handleItems = lodash.isArray(nodeProperty) ? nodeProperty : [nodeProperty];
|
||||
for (const item of handleItems) {
|
||||
carry = this.schemaToSingleNodes(item, carry, {
|
||||
parentUid: node['x-uid'],
|
||||
type: nodeKey,
|
||||
});
|
||||
for (const [i, item] of handleItems.entries()) {
|
||||
carry = this.schemaToSingleNodes(item, carry, { ...childNodeChildOptions, sort: i + 1 });
|
||||
}
|
||||
} else if (lodash.isPlainObject(nodeProperty)) {
|
||||
const subNodeNames = lodash.keys(lodash.get(node, nodeKey));
|
||||
|
||||
delete node[nodeKey];
|
||||
|
||||
for (const subNodeName of subNodeNames) {
|
||||
for (const [i, subNodeName] of subNodeNames.entries()) {
|
||||
const subSchema = {
|
||||
name: subNodeName,
|
||||
...lodash.get(nodeProperty, subNodeName),
|
||||
};
|
||||
|
||||
carry = this.schemaToSingleNodes(subSchema, carry, {
|
||||
parentUid: node['x-uid'],
|
||||
type: nodeKey,
|
||||
});
|
||||
carry = this.schemaToSingleNodes(subSchema, carry, { ...childNodeChildOptions, sort: i + 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -538,11 +539,32 @@ export class UiSchemaRepository extends Repository {
|
||||
};
|
||||
|
||||
const insertedNodes = await this.insertNodes(nodes, options);
|
||||
|
||||
return await this.getJsonSchema(insertedNodes[0].get('x-uid'), {
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
||||
private async schemaExists(schema: any, options?: TransactionAble): Promise<boolean> {
|
||||
if (lodash.isObject(schema) && !schema['x-uid']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { transaction } = options;
|
||||
const result = await this.database.sequelize.query(
|
||||
this.sqlAdapter(`select "x-uid" from ${this.uiSchemasTableName} where "x-uid" = :uid`),
|
||||
{
|
||||
type: 'SELECT',
|
||||
replacements: {
|
||||
uid: lodash.isString(schema) ? schema : schema['x-uid'],
|
||||
},
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
return result.length > 0;
|
||||
}
|
||||
|
||||
@transaction()
|
||||
async insertAdjacent(
|
||||
position: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd',
|
||||
@ -550,26 +572,57 @@ export class UiSchemaRepository extends Repository {
|
||||
schema: any,
|
||||
options?: InsertAdjacentOptions,
|
||||
) {
|
||||
const { transaction } = options;
|
||||
|
||||
if (options.wrap) {
|
||||
// insert wrap schema using insertNewSchema
|
||||
const wrapSchemaNodes = await this.insertNewSchema(options.wrap, {
|
||||
transaction,
|
||||
returnNode: true,
|
||||
});
|
||||
|
||||
const lastWrapNode = wrapSchemaNodes[wrapSchemaNodes.length - 1];
|
||||
|
||||
// insert schema into wrap schema
|
||||
await this.insertAdjacent('afterBegin', lastWrapNode['x-uid'], schema, lodash.omit(options, 'wrap'));
|
||||
|
||||
schema = wrapSchemaNodes[0]['x-uid'];
|
||||
|
||||
options.removeParentsIfNoChildren = false;
|
||||
} else {
|
||||
const schemaExists = await this.schemaExists(schema, { transaction });
|
||||
if (schemaExists) {
|
||||
schema = lodash.isString(schema) ? schema : schema['x-uid'];
|
||||
} else {
|
||||
const insertedSchema = await this.insertNewSchema(schema, {
|
||||
transaction,
|
||||
returnNode: true,
|
||||
});
|
||||
|
||||
schema = insertedSchema[0]['x-uid'];
|
||||
}
|
||||
}
|
||||
|
||||
return await this[`insert${lodash.upperFirst(position)}`](target, schema, options);
|
||||
}
|
||||
|
||||
@transaction()
|
||||
async insertAfterBegin(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||
protected async insertAfterBegin(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||
return await this.insertInner(targetUid, schema, 'first', options);
|
||||
}
|
||||
|
||||
@transaction()
|
||||
async insertBeforeEnd(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||
protected async insertBeforeEnd(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||
return await this.insertInner(targetUid, schema, 'last', options);
|
||||
}
|
||||
|
||||
@transaction()
|
||||
async insertBeforeBegin(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||
protected async insertBeforeBegin(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||
return await this.insertBeside(targetUid, schema, 'before', options);
|
||||
}
|
||||
|
||||
@transaction()
|
||||
async insertAfterEnd(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||
protected async insertAfterEnd(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||
return await this.insertBeside(targetUid, schema, 'after', options);
|
||||
}
|
||||
|
||||
@ -600,6 +653,70 @@ export class UiSchemaRepository extends Repository {
|
||||
});
|
||||
}
|
||||
|
||||
@transaction()
|
||||
async insertNewSchema(
|
||||
schema: any,
|
||||
options?: TransactionAble & {
|
||||
returnNode?: boolean;
|
||||
},
|
||||
) {
|
||||
const { transaction } = options;
|
||||
|
||||
const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
|
||||
// insert schema fist
|
||||
await this.database.sequelize.query(
|
||||
this.sqlAdapter(
|
||||
`INSERT INTO ${this.uiSchemasTableName} ("x-uid", "name", "schema") VALUES ${nodes
|
||||
.map((n) => '(?)')
|
||||
.join(',')};`,
|
||||
),
|
||||
{
|
||||
replacements: lodash.cloneDeep(nodes).map((node) => {
|
||||
const { uid, name } = this.prepareSingleNodeForInsert(node);
|
||||
return [uid, name, JSON.stringify(node)];
|
||||
}),
|
||||
type: 'insert',
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
const treePathData: Array<any> = lodash.cloneDeep(nodes).reduce((carry, item) => {
|
||||
const { uid, childOptions, async } = this.prepareSingleNodeForInsert(item);
|
||||
|
||||
return [
|
||||
...carry,
|
||||
// self reference
|
||||
[uid, uid, 0, childOptions?.type || null, async, null],
|
||||
// parent references
|
||||
...lodash.get(childOptions, 'parentPath', []).map((parentUid, index) => {
|
||||
return [parentUid, uid, index + 1, null, null, childOptions.sort];
|
||||
}),
|
||||
];
|
||||
}, []);
|
||||
|
||||
// insert tree path
|
||||
await this.database.sequelize.query(
|
||||
this.sqlAdapter(
|
||||
`INSERT INTO ${
|
||||
this.uiSchemaTreePathTableName
|
||||
} (ancestor, descendant, depth, type, async, sort) VALUES ${treePathData.map((item) => '(?)').join(',')}`,
|
||||
),
|
||||
{
|
||||
replacements: treePathData,
|
||||
type: 'insert',
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
if (options?.returnNode) {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
return this.getJsonSchema(nodes[0]['x-uid'], {
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
||||
private async insertSchemaRecord(name, uid, schema, transaction) {
|
||||
const serverHooks = schema['x-server-hooks'] || [];
|
||||
|
||||
@ -619,11 +736,7 @@ export class UiSchemaRepository extends Repository {
|
||||
return node;
|
||||
}
|
||||
|
||||
async insertSingleNode(schema: SchemaNode, options: TransactionAble & removeParentOptions) {
|
||||
const { transaction } = options;
|
||||
|
||||
const db = this.database;
|
||||
|
||||
private prepareSingleNodeForInsert(schema: SchemaNode) {
|
||||
const uid = schema['x-uid'];
|
||||
const name = schema['name'];
|
||||
const async = lodash.get(schema, 'x-async', false);
|
||||
@ -634,6 +747,15 @@ export class UiSchemaRepository extends Repository {
|
||||
delete schema['name'];
|
||||
delete schema['childOptions'];
|
||||
|
||||
return { uid, name, async, childOptions };
|
||||
}
|
||||
|
||||
async insertSingleNode(schema: SchemaNode, options: TransactionAble & removeParentOptions) {
|
||||
const { transaction } = options;
|
||||
|
||||
const db = this.database;
|
||||
|
||||
const { uid, name, async, childOptions } = this.prepareSingleNodeForInsert(schema);
|
||||
let savedNode;
|
||||
|
||||
// check node exists or not
|
||||
@ -656,18 +778,9 @@ export class UiSchemaRepository extends Repository {
|
||||
const oldParentUid = await this.findParentUid(uid, transaction);
|
||||
const parentUid = childOptions.parentUid;
|
||||
|
||||
const isTreeQuery = await db.sequelize.query(
|
||||
`SELECT COUNT(*) as childrenCount from ${treeTable} WHERE ancestor = :ancestor AND descendant != ancestor`,
|
||||
{
|
||||
type: 'SELECT',
|
||||
replacements: {
|
||||
ancestor: uid,
|
||||
},
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
const childrenCount = await this.childrenCount(uid, transaction);
|
||||
|
||||
const isTree = isTreeQuery[0]['childrenCount'];
|
||||
const isTree = childrenCount > 0;
|
||||
|
||||
// if node is a tree root move tree to new path
|
||||
if (isTree) {
|
||||
@ -692,6 +805,19 @@ export class UiSchemaRepository extends Repository {
|
||||
);
|
||||
}
|
||||
|
||||
// update type
|
||||
await db.sequelize.query(
|
||||
`UPDATE ${treeTable} SET type = :type WHERE depth = 0 AND ancestor = :uid AND descendant = :uid`,
|
||||
{
|
||||
type: 'update',
|
||||
transaction,
|
||||
replacements: {
|
||||
type: childOptions.type,
|
||||
uid,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!isTree) {
|
||||
if (existsNode) {
|
||||
// remove old path
|
||||
|
Loading…
Reference in New Issue
Block a user