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,
|
'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 { 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'),
|
||||||
|
@ -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(
|
||||||
|
Loading…
Reference in New Issue
Block a user