feat: uiSchema clearAncestor (#241)

* feat: uiSchema clearAncestor

* chore: uiSchema action methods

* chore: insertSingleNode
This commit is contained in:
ChengLei Shao 2022-03-16 18:45:20 +08:00 committed by GitHub
parent 4c0af45105
commit 5c92d3ba46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 174 additions and 151 deletions

View File

@ -1063,4 +1063,49 @@ describe('ui_schema repository', () => {
'x-async': false, 'x-async': false,
}); });
}); });
it('should remove schema ancestor', async () => {
const schema = {
'x-uid': 'A',
name: 'A',
properties: {
B: {
'x-uid': 'B',
properties: {
C: {
'x-uid': 'C',
properties: {
D: {
'x-uid': 'D',
},
},
},
F: {
'x-uid': 'F',
},
},
},
E: {
'x-uid': 'E',
},
},
};
await repository.insert(schema);
expect((await repository.getJsonSchema('B')).properties.C).toBeDefined();
await repository.clearAncestor('C');
expect((await repository.getJsonSchema('B')).properties.C).not.toBeDefined();
const c = await repository.getJsonSchema('C');
expect(c).toMatchObject({
'x-uid': 'C',
properties: {
D: {
'x-uid': 'D',
},
},
});
});
}); });

View File

@ -1,56 +1,33 @@
import { Context } from '@nocobase/actions'; import { Context } from '@nocobase/actions';
import UiSchemaRepository from '../repository'; import UiSchemaRepository from '../repository';
import lodash from 'lodash';
const getRepositoryFromCtx = (ctx: Context) => { const getRepositoryFromCtx = (ctx: Context) => {
return ctx.db.getCollection('uiSchemas').repository as UiSchemaRepository; return ctx.db.getCollection('uiSchemas').repository as UiSchemaRepository;
}; };
const callRepositoryMethod = (method, paramsKey: 'resourceIndex' | 'values') => {
return async (ctx, next) => {
const params = lodash.get(ctx.action.params, paramsKey);
const repository = getRepositoryFromCtx(ctx);
const returnValue = await repository[method](params);
ctx.body = returnValue || {
result: 'ok',
};
await next();
};
};
export const uiSchemaActions = { export const uiSchemaActions = {
async getJsonSchema(ctx: Context, next) { getJsonSchema: callRepositoryMethod('getJsonSchema', 'resourceIndex'),
const { resourceIndex } = ctx.action.params; getProperties: callRepositoryMethod('getProperties', 'resourceIndex'),
const repository = getRepositoryFromCtx(ctx); insert: callRepositoryMethod('insert', 'values'),
ctx.body = await repository.getJsonSchema(resourceIndex); remove: callRepositoryMethod('remove', 'resourceIndex'),
await next(); patch: callRepositoryMethod('patch', 'values'),
}, clearAncestor: callRepositoryMethod('clearAncestor', 'resourceIndex'),
async getProperties(ctx: Context, next) {
const { resourceIndex } = ctx.action.params;
const repository = getRepositoryFromCtx(ctx);
ctx.body = await repository.getProperties(resourceIndex);
await next();
},
async insert(ctx: Context, next) {
const { values } = ctx.action.params;
const repository = getRepositoryFromCtx(ctx);
ctx.body = await repository.insert(values);
await next();
},
async remove(ctx: Context, next) {
const { resourceIndex } = ctx.action.params;
const repository = getRepositoryFromCtx(ctx);
await repository.remove(resourceIndex);
ctx.body = {
result: 'ok',
};
await next();
},
async patch(ctx: Context, next) {
const { values } = ctx.action.params;
const repository = getRepositoryFromCtx(ctx);
await repository.patch(values);
ctx.body = {
result: 'ok',
};
await next();
},
async insertAdjacent(ctx: Context, next) { async insertAdjacent(ctx: Context, next) {
const { resourceIndex, position, values, removeParentsIfNoChildren, breakRemoveOn } = ctx.action.params; const { resourceIndex, position, values, removeParentsIfNoChildren, breakRemoveOn } = ctx.action.params;
@ -63,6 +40,7 @@ export const uiSchemaActions = {
await next(); await next();
}, },
insertBeforeBegin: insertPositionActionBuilder('beforeBegin'), insertBeforeBegin: insertPositionActionBuilder('beforeBegin'),
insertAfterBegin: insertPositionActionBuilder('afterBegin'), insertAfterBegin: insertPositionActionBuilder('afterBegin'),
insertBeforeEnd: insertPositionActionBuilder('beforeEnd'), insertBeforeEnd: insertPositionActionBuilder('beforeEnd'),

View File

@ -13,7 +13,7 @@ type BreakRemoveOnType = {
[key: string]: any; [key: string]: any;
}; };
export interface removeParentOptions { export interface removeParentOptions extends TransactionAble {
removeParentsIfNoChildren?: boolean; removeParentsIfNoChildren?: boolean;
breakRemoveOn?: BreakRemoveOnType; breakRemoveOn?: BreakRemoveOnType;
} }
@ -22,6 +22,45 @@ interface InsertAdjacentOptions extends removeParentOptions {}
const nodeKeys = ['properties', 'definitions', 'patternProperties', 'additionalProperties', 'items']; const nodeKeys = ['properties', 'definitions', 'patternProperties', 'additionalProperties', 'items'];
function transaction(transactionAbleArgPosition?: number) {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
const originalMethod = descriptor.value;
descriptor.value = async function (...args) {
if (!lodash.isNumber(transactionAbleArgPosition)) {
transactionAbleArgPosition = originalMethod.length - 1;
}
let transaction = lodash.get(args, [transactionAbleArgPosition, 'transaction']);
let handleTransaction = false;
if (!transaction) {
transaction = await this.database.sequelize.transaction();
handleTransaction = true;
lodash.set(args, transactionAbleArgPosition, {
...lodash.get(args, transactionAbleArgPosition, {}),
transaction,
});
}
if (handleTransaction) {
try {
const results = await originalMethod.apply(this, args);
await transaction.commit();
return results;
} catch (e) {
await transaction.rollback();
throw e;
}
} else {
return await originalMethod.apply(this, args);
}
};
return descriptor;
};
}
export class UiSchemaRepository extends Repository { export class UiSchemaRepository extends Repository {
tableNameAdapter(tableName) { tableNameAdapter(tableName) {
if (this.database.sequelize.getDialect() === 'postgres') { if (this.database.sequelize.getDialect() === 'postgres') {
@ -212,19 +251,32 @@ export class UiSchemaRepository extends Repository {
return buildTree(nodes.find((node) => node['x-uid'] == rootUid)); return buildTree(nodes.find((node) => node['x-uid'] == rootUid));
} }
treeCollection() { @transaction()
return this.database.getCollection('uiSchemaTreePath'); async clearAncestor(uid: string, options?: TransactionAble) {
const db = this.database;
const treeTable = this.uiSchemaTreePathTableName;
await db.sequelize.query(
`DELETE
FROM ${treeTable}
WHERE descendant IN
(SELECT descendant FROM (SELECT descendant FROM ${treeTable} WHERE ancestor = :uid) as descendantTable)
AND ancestor IN (SELECT ancestor
FROM (SELECT ancestor FROM ${treeTable} WHERE descendant = :uid AND ancestor != descendant) as ancestorTable)
`,
{
type: 'DELETE',
replacements: {
uid,
},
transaction: options.transaction,
},
);
} }
@transaction()
async patch(newSchema: any, options?) { async patch(newSchema: any, options?) {
let handleTransaction = true; const { transaction } = options;
let transaction;
if (options?.transaction) {
handleTransaction = false;
transaction = options.transaction;
} else {
transaction = await this.database.sequelize.transaction();
}
const rootUid = newSchema['x-uid']; const rootUid = newSchema['x-uid'];
const oldTree = await this.getJsonSchema(rootUid); const oldTree = await this.getJsonSchema(rootUid);
@ -244,14 +296,7 @@ export class UiSchemaRepository extends Repository {
} }
}; };
try { await traverSchemaTree(newSchema);
await traverSchemaTree(newSchema);
handleTransaction && (await transaction.commit());
} catch (err) {
handleTransaction && (await transaction.rollback());
throw err;
}
} }
async updateNode(uid: string, schema: any, transaction?: Transaction) { async updateNode(uid: string, schema: any, transaction?: Transaction) {
@ -406,65 +451,44 @@ export class UiSchemaRepository extends Repository {
await removeLeafNode(uid); await removeLeafNode(uid);
} }
@transaction()
async remove(uid: string, options?: TransactionAble & removeParentOptions) { async remove(uid: string, options?: TransactionAble & removeParentOptions) {
let handleTransaction: boolean = true; let { transaction } = options;
let transaction;
if (options?.transaction) { if (options?.removeParentsIfNoChildren) {
transaction = options.transaction; await this.removeEmptyParents({ transaction, uid, breakRemoveOn: options.breakRemoveOn });
handleTransaction = false; return;
} else {
transaction = await this.database.sequelize.transaction();
} }
try { await this.database.sequelize.query(
if (options?.removeParentsIfNoChildren) { this.sqlAdapter(`DELETE FROM ${this.uiSchemasTableName} WHERE "x-uid" IN (
await this.removeEmptyParents({ transaction, uid, breakRemoveOn: options.breakRemoveOn });
if (handleTransaction) {
await transaction.commit();
}
return;
}
await this.database.sequelize.query(
this.sqlAdapter(`DELETE FROM ${this.uiSchemasTableName} WHERE "x-uid" IN (
SELECT descendant FROM ${this.uiSchemaTreePathTableName} WHERE ancestor = :uid SELECT descendant FROM ${this.uiSchemaTreePathTableName} WHERE ancestor = :uid
)`), )`),
{ {
replacements: { replacements: {
uid, uid,
},
transaction,
}, },
); transaction,
},
);
await this.database.sequelize.query( await this.database.sequelize.query(
` ` DELETE FROM ${this.uiSchemaTreePathTableName}
DELETE FROM ${this.uiSchemaTreePathTableName}
WHERE descendant IN ( WHERE descendant IN (
select descendant FROM select descendant FROM
(SELECT descendant (SELECT descendant
FROM ${this.uiSchemaTreePathTableName} FROM ${this.uiSchemaTreePathTableName}
WHERE ancestor = :uid)as descendantTable) `, WHERE ancestor = :uid)as descendantTable) `,
{ {
replacements: { replacements: {
uid, uid,
},
transaction,
}, },
); transaction,
},
if (handleTransaction) { );
await transaction.commit();
}
} catch (err) {
if (handleTransaction) {
await transaction.rollback();
}
throw err;
}
} }
@transaction()
async insertBeside(targetUid: string, schema: any, side: 'before' | 'after', options?: InsertAdjacentOptions) { async insertBeside(targetUid: string, schema: any, side: 'before' | 'after', options?: InsertAdjacentOptions) {
const targetParent = await this.findParentUid(targetUid); const targetParent = await this.findParentUid(targetUid);
@ -496,6 +520,7 @@ export class UiSchemaRepository extends Repository {
return await this.getJsonSchema(insertedNodes[0].get('x-uid')); return await this.getJsonSchema(insertedNodes[0].get('x-uid'));
} }
@transaction()
async insertInner(targetUid: string, schema: any, position: 'first' | 'last', options?: InsertAdjacentOptions) { async insertInner(targetUid: string, schema: any, position: 'first' | 'last', options?: InsertAdjacentOptions) {
const nodes = UiSchemaRepository.schemaToSingleNodes(schema); const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
const rootNode = nodes[0]; const rootNode = nodes[0];
@ -510,6 +535,7 @@ export class UiSchemaRepository extends Repository {
return await this.getJsonSchema(insertedNodes[0].get('x-uid')); return await this.getJsonSchema(insertedNodes[0].get('x-uid'));
} }
@transaction()
async insertAdjacent( async insertAdjacent(
position: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd', position: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd',
target: string, target: string,
@ -519,58 +545,45 @@ export class UiSchemaRepository extends Repository {
return await this[`insert${lodash.upperFirst(position)}`](target, schema, options); return await this[`insert${lodash.upperFirst(position)}`](target, schema, options);
} }
@transaction()
async insertAfterBegin(targetUid: string, schema: any, options?: InsertAdjacentOptions) { async insertAfterBegin(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
return await this.insertInner(targetUid, schema, 'first', options); return await this.insertInner(targetUid, schema, 'first', options);
} }
@transaction()
async insertBeforeEnd(targetUid: string, schema: any, options?: InsertAdjacentOptions) { async insertBeforeEnd(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
return await this.insertInner(targetUid, schema, 'last', options); return await this.insertInner(targetUid, schema, 'last', options);
} }
@transaction()
async insertBeforeBegin(targetUid: string, schema: any, options?: InsertAdjacentOptions) { async insertBeforeBegin(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
return await this.insertBeside(targetUid, schema, 'before', options); return await this.insertBeside(targetUid, schema, 'before', options);
} }
@transaction()
async insertAfterEnd(targetUid: string, schema: any, options?: InsertAdjacentOptions) { async insertAfterEnd(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
return await this.insertBeside(targetUid, schema, 'after', options); return await this.insertBeside(targetUid, schema, 'after', options);
} }
async insertNodes(nodes: SchemaNode[], options?) { @transaction()
let handleTransaction: boolean = true; async insertNodes(nodes: SchemaNode[], options?: TransactionAble) {
let transaction; const { transaction } = options;
if (options?.transaction) {
transaction = options.transaction;
handleTransaction = false;
} else {
transaction = await this.database.sequelize.transaction();
}
const insertedNodes = []; const insertedNodes = [];
try { for (const node of nodes) {
for (const node of nodes) { insertedNodes.push(
insertedNodes.push( await this.insertSingleNode(node, {
await this.insertSingleNode(node, { ...options,
...options, transaction,
transaction, }),
}), );
);
}
if (handleTransaction) {
await transaction.commit();
}
return insertedNodes;
} catch (err) {
console.log({ err });
if (handleTransaction) {
await transaction.rollback();
}
throw err;
} }
return insertedNodes;
} }
@transaction()
async insert(schema: any, options?: TransactionAble) { async insert(schema: any, options?: TransactionAble) {
const nodes = UiSchemaRepository.schemaToSingleNodes(schema); const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
const insertedNodes = await this.insertNodes(nodes, options); const insertedNodes = await this.insertNodes(nodes, options);
@ -650,20 +663,7 @@ export class UiSchemaRepository extends Repository {
// if node is a tree root move tree to new path // if node is a tree root move tree to new path
if (isTree) { if (isTree) {
// delete old tree path await this.clearAncestor(uid, { transaction });
await db.sequelize.query(
`DELETE FROM ${treeTable}
WHERE descendant IN (SELECT descendant FROM (SELECT descendant FROM ${treeTable} WHERE ancestor = :uid) as descendantTable )
AND ancestor IN (SELECT ancestor FROM (SELECT ancestor FROM ${treeTable} WHERE descendant = :uid AND ancestor != descendant) as ancestorTable)
`,
{
type: 'DELETE',
replacements: {
uid,
},
transaction,
},
);
// insert new tree path // insert new tree path
await db.sequelize.query( await db.sequelize.query(