feat: uiSchema clearAncestor (#241)
* feat: uiSchema clearAncestor * chore: uiSchema action methods * chore: insertSingleNode
This commit is contained in:
parent
4c0af45105
commit
5c92d3ba46
@ -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',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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'),
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user