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,
});
});
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 UiSchemaRepository from '../repository';
import lodash from 'lodash';
const getRepositoryFromCtx = (ctx: Context) => {
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 = {
async getJsonSchema(ctx: Context, next) {
const { resourceIndex } = ctx.action.params;
const repository = getRepositoryFromCtx(ctx);
ctx.body = await repository.getJsonSchema(resourceIndex);
await next();
},
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();
},
getJsonSchema: callRepositoryMethod('getJsonSchema', 'resourceIndex'),
getProperties: callRepositoryMethod('getProperties', 'resourceIndex'),
insert: callRepositoryMethod('insert', 'values'),
remove: callRepositoryMethod('remove', 'resourceIndex'),
patch: callRepositoryMethod('patch', 'values'),
clearAncestor: callRepositoryMethod('clearAncestor', 'resourceIndex'),
async insertAdjacent(ctx: Context, next) {
const { resourceIndex, position, values, removeParentsIfNoChildren, breakRemoveOn } = ctx.action.params;
@ -63,6 +40,7 @@ export const uiSchemaActions = {
await next();
},
insertBeforeBegin: insertPositionActionBuilder('beforeBegin'),
insertAfterBegin: insertPositionActionBuilder('afterBegin'),
insertBeforeEnd: insertPositionActionBuilder('beforeEnd'),

View File

@ -13,7 +13,7 @@ type BreakRemoveOnType = {
[key: string]: any;
};
export interface removeParentOptions {
export interface removeParentOptions extends TransactionAble {
removeParentsIfNoChildren?: boolean;
breakRemoveOn?: BreakRemoveOnType;
}
@ -22,6 +22,45 @@ interface InsertAdjacentOptions extends removeParentOptions {}
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 {
tableNameAdapter(tableName) {
if (this.database.sequelize.getDialect() === 'postgres') {
@ -212,19 +251,32 @@ export class UiSchemaRepository extends Repository {
return buildTree(nodes.find((node) => node['x-uid'] == rootUid));
}
treeCollection() {
return this.database.getCollection('uiSchemaTreePath');
@transaction()
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?) {
let handleTransaction = true;
let transaction;
if (options?.transaction) {
handleTransaction = false;
transaction = options.transaction;
} else {
transaction = await this.database.sequelize.transaction();
}
const { transaction } = options;
const rootUid = newSchema['x-uid'];
const oldTree = await this.getJsonSchema(rootUid);
@ -244,14 +296,7 @@ export class UiSchemaRepository extends Repository {
}
};
try {
await traverSchemaTree(newSchema);
handleTransaction && (await transaction.commit());
} catch (err) {
handleTransaction && (await transaction.rollback());
throw err;
}
await traverSchemaTree(newSchema);
}
async updateNode(uid: string, schema: any, transaction?: Transaction) {
@ -406,65 +451,44 @@ export class UiSchemaRepository extends Repository {
await removeLeafNode(uid);
}
@transaction()
async remove(uid: string, options?: TransactionAble & removeParentOptions) {
let handleTransaction: boolean = true;
let transaction;
let { transaction } = options;
if (options?.transaction) {
transaction = options.transaction;
handleTransaction = false;
} else {
transaction = await this.database.sequelize.transaction();
if (options?.removeParentsIfNoChildren) {
await this.removeEmptyParents({ transaction, uid, breakRemoveOn: options.breakRemoveOn });
return;
}
try {
if (options?.removeParentsIfNoChildren) {
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 (
await this.database.sequelize.query(
this.sqlAdapter(`DELETE FROM ${this.uiSchemasTableName} WHERE "x-uid" IN (
SELECT descendant FROM ${this.uiSchemaTreePathTableName} WHERE ancestor = :uid
)`),
{
replacements: {
uid,
},
transaction,
{
replacements: {
uid,
},
);
transaction,
},
);
await this.database.sequelize.query(
`
DELETE FROM ${this.uiSchemaTreePathTableName}
await this.database.sequelize.query(
` DELETE FROM ${this.uiSchemaTreePathTableName}
WHERE descendant IN (
select descendant FROM
(SELECT descendant
FROM ${this.uiSchemaTreePathTableName}
WHERE ancestor = :uid)as descendantTable) `,
{
replacements: {
uid,
},
transaction,
{
replacements: {
uid,
},
);
if (handleTransaction) {
await transaction.commit();
}
} catch (err) {
if (handleTransaction) {
await transaction.rollback();
}
throw err;
}
transaction,
},
);
}
@transaction()
async insertBeside(targetUid: string, schema: any, side: 'before' | 'after', options?: InsertAdjacentOptions) {
const targetParent = await this.findParentUid(targetUid);
@ -496,6 +520,7 @@ export class UiSchemaRepository extends Repository {
return await this.getJsonSchema(insertedNodes[0].get('x-uid'));
}
@transaction()
async insertInner(targetUid: string, schema: any, position: 'first' | 'last', options?: InsertAdjacentOptions) {
const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
const rootNode = nodes[0];
@ -510,6 +535,7 @@ export class UiSchemaRepository extends Repository {
return await this.getJsonSchema(insertedNodes[0].get('x-uid'));
}
@transaction()
async insertAdjacent(
position: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd',
target: string,
@ -519,58 +545,45 @@ export class UiSchemaRepository extends Repository {
return await this[`insert${lodash.upperFirst(position)}`](target, schema, options);
}
@transaction()
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) {
return await this.insertInner(targetUid, schema, 'last', options);
}
@transaction()
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) {
return await this.insertBeside(targetUid, schema, 'after', options);
}
async insertNodes(nodes: SchemaNode[], options?) {
let handleTransaction: boolean = true;
let transaction;
if (options?.transaction) {
transaction = options.transaction;
handleTransaction = false;
} else {
transaction = await this.database.sequelize.transaction();
}
@transaction()
async insertNodes(nodes: SchemaNode[], options?: TransactionAble) {
const { transaction } = options;
const insertedNodes = [];
try {
for (const node of nodes) {
insertedNodes.push(
await this.insertSingleNode(node, {
...options,
transaction,
}),
);
}
if (handleTransaction) {
await transaction.commit();
}
return insertedNodes;
} catch (err) {
console.log({ err });
if (handleTransaction) {
await transaction.rollback();
}
throw err;
for (const node of nodes) {
insertedNodes.push(
await this.insertSingleNode(node, {
...options,
transaction,
}),
);
}
return insertedNodes;
}
@transaction()
async insert(schema: any, options?: TransactionAble) {
const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
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 (isTree) {
// delete old tree path
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,
},
);
await this.clearAncestor(uid, { transaction });
// insert new tree path
await db.sequelize.query(