mirror of
https://gitee.com/nocobase/nocobase.git
synced 2024-11-30 19:28:34 +08:00
feat: uiSchema remove api (#196)
* feat: recursiveRemoveIfNoChildren * feat: remove with removeParents * refactor: removeSchema server hook * fix: server hook test * feat: insertInner with remove parent * feat: onSelfMove server hook * feat: removeParentsIfNoChildren server hook method * refactor: ui_schema_tree_path to uiSchemaTreePath * feat: insertAdjacent api params
This commit is contained in:
parent
6c381313cb
commit
b9bbbc8516
@ -111,8 +111,8 @@ describe('server hooks', () => {
|
||||
field: 'title',
|
||||
method: 'removeSchema',
|
||||
params: {
|
||||
breakComponent: 'Grid',
|
||||
removeEmptyParents: true,
|
||||
breakRemoveOn: { 'x-component': 'Grid' },
|
||||
removeParentsIfNoChildren: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -137,8 +137,8 @@ describe('server hooks', () => {
|
||||
field: 'intro',
|
||||
method: 'removeSchema',
|
||||
params: {
|
||||
breakComponent: 'Grid',
|
||||
removeEmptyParents: true,
|
||||
breakRemoveOn: { 'x-component': 'Grid' },
|
||||
removeParentsIfNoChildren: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -188,8 +188,8 @@ describe('server hooks', () => {
|
||||
collection: 'posts',
|
||||
method: 'removeSchema',
|
||||
params: {
|
||||
breakComponent: 'row',
|
||||
removeEmptyParents: true,
|
||||
breakRemoveOn: { 'x-component': 'row' },
|
||||
removeParentsIfNoChildren: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -304,4 +304,91 @@ describe('server hooks', () => {
|
||||
const role2Menus = await db.getRepository<BelongsToManyRepository>('roles.menuUiSchemas', 'role2').find();
|
||||
expect(role2Menus.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should remove parents on self move', async () => {
|
||||
const schema = {
|
||||
'x-uid': 'A',
|
||||
name: 'A',
|
||||
properties: {
|
||||
B: {
|
||||
'x-uid': 'B',
|
||||
properties: {
|
||||
C: {
|
||||
'x-uid': 'C',
|
||||
properties: {
|
||||
D: {
|
||||
'x-uid': 'D',
|
||||
'x-server-hooks': [
|
||||
{
|
||||
type: 'onSelfMove',
|
||||
method: 'removeParentsIfNoChildren',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
E: {
|
||||
'x-uid': 'E',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await uiSchemaRepository.insert(schema);
|
||||
|
||||
await uiSchemaRepository.insertAfterEnd('E', {
|
||||
'x-uid': 'F',
|
||||
name: 'F',
|
||||
properties: {
|
||||
G: {
|
||||
'x-uid': 'G',
|
||||
properties: {
|
||||
D: {
|
||||
'x-uid': 'D',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const A = await uiSchemaRepository.getJsonSchema('A');
|
||||
expect(A).toEqual({
|
||||
properties: {
|
||||
E: {
|
||||
'x-uid': 'E',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
F: {
|
||||
properties: {
|
||||
G: {
|
||||
properties: {
|
||||
D: {
|
||||
'x-server-hooks': [
|
||||
{
|
||||
type: 'onSelfMove',
|
||||
method: 'removeParentsIfNoChildren',
|
||||
},
|
||||
],
|
||||
'x-uid': 'D',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'G',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'F',
|
||||
'x-async': false,
|
||||
'x-index': 3,
|
||||
},
|
||||
},
|
||||
name: 'A',
|
||||
'x-uid': 'A',
|
||||
'x-async': false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -274,4 +274,68 @@ describe('server hooks', () => {
|
||||
}),
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it('should call onSelfMove', async () => {
|
||||
const schema = {
|
||||
'x-uid': 'A',
|
||||
name: 'A',
|
||||
properties: {
|
||||
B: {
|
||||
'x-uid': 'B',
|
||||
properties: {
|
||||
C: {
|
||||
'x-uid': 'C',
|
||||
properties: {
|
||||
D: {
|
||||
'x-uid': 'D',
|
||||
'x-server-hooks': [
|
||||
{
|
||||
type: 'onSelfMove',
|
||||
method: 'testOnSelfMove',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
E: {
|
||||
'x-uid': 'E',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const serverHooks = uiSchemaPlugin.serverHooks;
|
||||
|
||||
const jestFn = jest.fn();
|
||||
|
||||
serverHooks.register('onSelfMove', 'testOnSelfMove', async ({ options }) => {
|
||||
jestFn();
|
||||
});
|
||||
|
||||
await uiSchemaRepository.insert(schema);
|
||||
|
||||
await uiSchemaRepository.insertAfterEnd(
|
||||
'E',
|
||||
{
|
||||
'x-uid': 'F',
|
||||
name: 'F',
|
||||
properties: {
|
||||
G: {
|
||||
'x-uid': 'G',
|
||||
properties: {
|
||||
D: {
|
||||
'x-uid': 'D',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
removeParentsIfNoChildren: true,
|
||||
},
|
||||
);
|
||||
|
||||
expect(jestFn).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -35,7 +35,7 @@ describe('ui_schema repository', () => {
|
||||
},
|
||||
});
|
||||
repository = db.getCollection('uiSchemas').repository as UiSchemaRepository;
|
||||
treePathCollection = db.getCollection('ui_schema_tree_path');
|
||||
treePathCollection = db.getCollection('uiSchemaTreePath');
|
||||
});
|
||||
|
||||
it('should be registered', async () => {
|
||||
@ -50,7 +50,7 @@ describe('ui_schema repository', () => {
|
||||
};
|
||||
|
||||
const transaction = await db.sequelize.transaction();
|
||||
await repository.insertSingleNode(singleNode, transaction);
|
||||
await repository.insertSingleNode(singleNode, { transaction });
|
||||
|
||||
await transaction.commit();
|
||||
// it should save in ui schema tables
|
||||
@ -76,7 +76,7 @@ describe('ui_schema repository', () => {
|
||||
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
await repository.insertSingleNode(singleNode, transaction);
|
||||
await repository.insertSingleNode(singleNode, { transaction });
|
||||
|
||||
const child1: SchemaNode = {
|
||||
name: 'child1',
|
||||
@ -88,7 +88,7 @@ describe('ui_schema repository', () => {
|
||||
},
|
||||
};
|
||||
|
||||
await repository.insertSingleNode(child1, transaction);
|
||||
await repository.insertSingleNode(child1, { transaction });
|
||||
|
||||
const child11: SchemaNode = {
|
||||
name: 'child11',
|
||||
@ -99,7 +99,7 @@ describe('ui_schema repository', () => {
|
||||
type: 'test',
|
||||
},
|
||||
};
|
||||
await repository.insertSingleNode(child11, transaction);
|
||||
await repository.insertSingleNode(child11, { transaction });
|
||||
|
||||
await transaction.commit();
|
||||
expect(
|
||||
@ -726,4 +726,215 @@ describe('ui_schema repository', () => {
|
||||
expect(newTree.properties.a1.title).toEqual('new a1 title');
|
||||
});
|
||||
});
|
||||
|
||||
it('should insertInner with removeParent', async () => {
|
||||
const schema = {
|
||||
'x-uid': 'A',
|
||||
name: 'A',
|
||||
properties: {
|
||||
B: {
|
||||
'x-uid': 'B',
|
||||
properties: {
|
||||
C: {
|
||||
'x-uid': 'C',
|
||||
properties: {
|
||||
D: {
|
||||
'x-uid': 'D',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
E: {
|
||||
'x-uid': 'E',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await repository.insert(schema);
|
||||
|
||||
await repository.insertAfterBegin(
|
||||
'E',
|
||||
{
|
||||
'x-uid': 'F',
|
||||
name: 'F',
|
||||
properties: {
|
||||
G: {
|
||||
'x-uid': 'G',
|
||||
properties: {
|
||||
D: {
|
||||
'x-uid': 'D',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
removeParentsIfNoChildren: true,
|
||||
},
|
||||
);
|
||||
|
||||
const A = await repository.getJsonSchema('A');
|
||||
|
||||
expect(A).toEqual({
|
||||
properties: {
|
||||
E: {
|
||||
properties: {
|
||||
F: {
|
||||
properties: {
|
||||
G: {
|
||||
properties: {
|
||||
D: {
|
||||
'x-uid': 'D',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'G',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'F',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'E',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
},
|
||||
name: 'A',
|
||||
'x-uid': 'A',
|
||||
'x-async': false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should insertBeside with removeParent', async () => {
|
||||
const schema = {
|
||||
'x-uid': 'A',
|
||||
name: 'A',
|
||||
properties: {
|
||||
B: {
|
||||
'x-uid': 'B',
|
||||
properties: {
|
||||
C: {
|
||||
'x-uid': 'C',
|
||||
properties: {
|
||||
D: {
|
||||
'x-uid': 'D',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
E: {
|
||||
'x-uid': 'E',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await repository.insert(schema);
|
||||
|
||||
await repository.insertAfterEnd(
|
||||
'E',
|
||||
{
|
||||
'x-uid': 'F',
|
||||
name: 'F',
|
||||
properties: {
|
||||
G: {
|
||||
'x-uid': 'G',
|
||||
properties: {
|
||||
D: {
|
||||
'x-uid': 'D',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
removeParentsIfNoChildren: true,
|
||||
},
|
||||
);
|
||||
|
||||
const A = await repository.getJsonSchema('A');
|
||||
|
||||
expect(A).toEqual({
|
||||
properties: {
|
||||
E: {
|
||||
'x-uid': 'E',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
F: {
|
||||
properties: {
|
||||
G: {
|
||||
'x-uid': 'G',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
properties: {
|
||||
D: {
|
||||
'x-uid': 'D',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'x-uid': 'F',
|
||||
'x-async': false,
|
||||
'x-index': 3,
|
||||
},
|
||||
},
|
||||
name: 'A',
|
||||
'x-uid': 'A',
|
||||
'x-async': false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove with breakOn', async () => {
|
||||
const schema = {
|
||||
'x-uid': 'A',
|
||||
name: 'A',
|
||||
properties: {
|
||||
B: {
|
||||
'x-uid': 'B',
|
||||
properties: {
|
||||
C: {
|
||||
'x-uid': 'C',
|
||||
properties: {
|
||||
D: {
|
||||
'x-uid': 'D',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
E: {
|
||||
'x-uid': 'E',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await repository.insert(schema);
|
||||
|
||||
await repository.remove('D', {
|
||||
removeParentsIfNoChildren: true,
|
||||
});
|
||||
|
||||
const A = await repository.getJsonSchema('A');
|
||||
expect(A).toEqual({
|
||||
properties: {
|
||||
E: {
|
||||
'x-uid': 'E',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
},
|
||||
name: 'A',
|
||||
'x-uid': 'A',
|
||||
'x-async': false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -53,10 +53,13 @@ export const uiSchemaActions = {
|
||||
},
|
||||
|
||||
async insertAdjacent(ctx: Context, next) {
|
||||
const { resourceIndex, position, values } = ctx.action.params;
|
||||
const { resourceIndex, position, values, removeParentsIfNoChildren, breakRemoveOn } = ctx.action.params;
|
||||
const repository = getRepositoryFromCtx(ctx);
|
||||
|
||||
ctx.body = await repository.insertAdjacent(position, resourceIndex, values);
|
||||
ctx.body = await repository.insertAdjacent(position, resourceIndex, values, {
|
||||
removeParentsIfNoChildren,
|
||||
breakRemoveOn,
|
||||
});
|
||||
|
||||
await next();
|
||||
},
|
||||
@ -68,9 +71,12 @@ export const uiSchemaActions = {
|
||||
|
||||
function insertPositionActionBuilder(position: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd') {
|
||||
return async function (ctx: Context, next) {
|
||||
const { resourceIndex, values } = ctx.action.params;
|
||||
const { resourceIndex, values, removeParentsIfNoChildren, breakRemoveOn } = ctx.action.params;
|
||||
const repository = getRepositoryFromCtx(ctx);
|
||||
ctx.body = await repository.insertAdjacent(position, resourceIndex, values);
|
||||
ctx.body = await repository.insertAdjacent(position, resourceIndex, values, {
|
||||
removeParentsIfNoChildren,
|
||||
breakRemoveOn,
|
||||
});
|
||||
await next();
|
||||
};
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export default {
|
||||
name: 'ui_schema_tree_path',
|
||||
name: 'uiSchemaTreePath',
|
||||
autoGenId: false,
|
||||
timestamps: false,
|
||||
fields: [
|
||||
|
@ -1,5 +1,11 @@
|
||||
import { MagicAttributeModel } from '@nocobase/database';
|
||||
import { HookType } from './server-hooks';
|
||||
|
||||
class UiSchemaModel extends MagicAttributeModel {}
|
||||
class UiSchemaModel extends MagicAttributeModel {
|
||||
getServerHooksByType(type: HookType) {
|
||||
const hooks = this.get('x-server-hooks') || [];
|
||||
return hooks.filter((hook) => hook.type === type);
|
||||
}
|
||||
}
|
||||
|
||||
export { UiSchemaModel };
|
||||
|
@ -9,10 +9,20 @@ interface GetJsonSchemaOptions {
|
||||
transaction?: Transaction;
|
||||
}
|
||||
|
||||
type BreakRemoveOnType = {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export interface removeParentOptions {
|
||||
removeParentsIfNoChildren?: boolean;
|
||||
breakRemoveOn?: BreakRemoveOnType;
|
||||
}
|
||||
|
||||
interface InsertAdjacentOptions extends removeParentOptions {}
|
||||
|
||||
const nodeKeys = ['properties', 'definitions', 'patternProperties', 'additionalProperties', 'items'];
|
||||
|
||||
export class UiSchemaRepository extends Repository {
|
||||
|
||||
get uiSchemasTableName() {
|
||||
if (this.database.sequelize.getDialect() === 'postgres') {
|
||||
return `"${this.model.tableName}"`;
|
||||
@ -21,7 +31,7 @@ export class UiSchemaRepository extends Repository {
|
||||
}
|
||||
|
||||
get uiSchemaTreePathTableName() {
|
||||
const model = this.database.getCollection('ui_schema_tree_path').model;
|
||||
const model = this.database.getCollection('uiSchemaTreePath').model;
|
||||
if (this.database.sequelize.getDialect() === 'postgres') {
|
||||
return `"${model.tableName}"`;
|
||||
}
|
||||
@ -94,12 +104,8 @@ export class UiSchemaRepository extends Repository {
|
||||
NodeInfo.type as type, NodeInfo.async as async, ParentPath.ancestor as parent, ParentPath.sort as sort
|
||||
FROM ${this.uiSchemaTreePathTableName} as TreePath
|
||||
LEFT JOIN ${this.uiSchemasTableName} as SchemaTable ON SchemaTable.uid = TreePath.descendant
|
||||
LEFT JOIN ${
|
||||
this.uiSchemaTreePathTableName
|
||||
} as NodeInfo ON NodeInfo.descendant = SchemaTable.uid and NodeInfo.descendant = NodeInfo.ancestor and NodeInfo.depth = 0
|
||||
LEFT JOIN ${
|
||||
this.uiSchemaTreePathTableName
|
||||
} as ParentPath ON (ParentPath.descendant = SchemaTable.uid AND ParentPath.depth = 1)
|
||||
LEFT JOIN ${this.uiSchemaTreePathTableName} as NodeInfo ON NodeInfo.descendant = SchemaTable.uid and NodeInfo.descendant = NodeInfo.ancestor and NodeInfo.depth = 0
|
||||
LEFT JOIN ${this.uiSchemaTreePathTableName} as ParentPath ON (ParentPath.descendant = SchemaTable.uid AND ParentPath.depth = 1)
|
||||
WHERE TreePath.ancestor = :ancestor AND (NodeInfo.async = false or TreePath.depth = 1)`;
|
||||
|
||||
const nodes = await db.sequelize.query(rawSql, {
|
||||
@ -194,7 +200,7 @@ export class UiSchemaRepository extends Repository {
|
||||
}
|
||||
|
||||
treeCollection() {
|
||||
return this.database.getCollection('ui_schema_tree_path');
|
||||
return this.database.getCollection('uiSchemaTreePath');
|
||||
}
|
||||
|
||||
async patch(newSchema: any, options?) {
|
||||
@ -256,10 +262,30 @@ export class UiSchemaRepository extends Repository {
|
||||
);
|
||||
}
|
||||
|
||||
protected async isSingleChild(uid, transaction) {
|
||||
protected async childrenCount(uid, transaction) {
|
||||
const db = this.database;
|
||||
|
||||
const parent = await db.getRepository('ui_schema_tree_path').findOne({
|
||||
const countResult = await db.sequelize.query(
|
||||
`SELECT COUNT(*) as count FROM ${this.uiSchemaTreePathTableName} where ancestor = :ancestor and depth = 1`,
|
||||
{
|
||||
replacements: {
|
||||
ancestor: uid,
|
||||
},
|
||||
type: 'SELECT',
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
return parseInt(countResult[0]['count']);
|
||||
}
|
||||
|
||||
protected async isLeafNode(uid, transaction) {
|
||||
const childrenCount = await this.childrenCount(uid, transaction);
|
||||
return childrenCount === 0;
|
||||
}
|
||||
|
||||
async findParentUid(uid, transaction?) {
|
||||
const parent = await this.database.getRepository('uiSchemaTreePath').findOne({
|
||||
filter: {
|
||||
descendant: uid,
|
||||
depth: 1,
|
||||
@ -267,29 +293,38 @@ export class UiSchemaRepository extends Repository {
|
||||
transaction,
|
||||
});
|
||||
|
||||
return parent ? (parent.get('ancestor') as string) : null;
|
||||
}
|
||||
|
||||
protected async findNodeSchemaWithParent(uid, transaction) {
|
||||
const schema = await this.database.getRepository('uiSchemas').findOne({
|
||||
filter: {
|
||||
uid,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
return {
|
||||
parentUid: await this.findParentUid(uid, transaction),
|
||||
schema,
|
||||
};
|
||||
}
|
||||
|
||||
protected async isSingleChild(uid, transaction) {
|
||||
const db = this.database;
|
||||
|
||||
const parent = await this.findParentUid(uid, transaction);
|
||||
|
||||
if (!parent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const countResult = await db.sequelize.query(
|
||||
`SELECT COUNT(*) as count FROM ${
|
||||
db.getCollection('ui_schema_tree_path').model.tableName
|
||||
} where ancestor = :ancestor and depth = 1`,
|
||||
{
|
||||
replacements: {
|
||||
ancestor: parent.get('ancestor'),
|
||||
},
|
||||
type: 'SELECT',
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
const parentChildrenCount = countResult[0]['count'];
|
||||
const parentChildrenCount = await this.childrenCount(parent, transaction);
|
||||
|
||||
if (parentChildrenCount == 1) {
|
||||
const schema = await db.getRepository('uiSchemas').findOne({
|
||||
filter: {
|
||||
uid: parent.get('ancestor') as string,
|
||||
uid: parent,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
@ -300,15 +335,13 @@ export class UiSchemaRepository extends Repository {
|
||||
return null;
|
||||
}
|
||||
|
||||
async removeEmptyParents(options: TransactionAble & { uid: string; breakComponent?: string }) {
|
||||
const { transaction, uid, breakComponent } = options;
|
||||
async removeEmptyParents(options: TransactionAble & { uid: string; breakRemoveOn?: BreakRemoveOnType }) {
|
||||
const { transaction, uid, breakRemoveOn } = options;
|
||||
|
||||
const removeParent = async (nodeUid: string) => {
|
||||
const parent = await this.isSingleChild(nodeUid, transaction);
|
||||
|
||||
const nodeComponentType = parent ? parent.get('x-component') : null;
|
||||
|
||||
if ((parent && !breakComponent) || (parent && breakComponent != nodeComponentType)) {
|
||||
if (parent && !this.breakOnMatched(parent, breakRemoveOn)) {
|
||||
await removeParent(parent.get('uid') as string);
|
||||
} else {
|
||||
await this.remove(nodeUid, { transaction });
|
||||
@ -318,7 +351,49 @@ export class UiSchemaRepository extends Repository {
|
||||
await removeParent(uid);
|
||||
}
|
||||
|
||||
async remove(uid: string, options?: TransactionAble) {
|
||||
private breakOnMatched(schemaInstance, breakRemoveOn: BreakRemoveOnType): boolean {
|
||||
if (!breakRemoveOn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const key of Object.keys(breakRemoveOn)) {
|
||||
const instanceValue = schemaInstance.get(key);
|
||||
const breakRemoveOnValue = breakRemoveOn[key];
|
||||
if (instanceValue !== breakRemoveOnValue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async recursivelyRemoveIfNoChildren(options: TransactionAble & { uid: string; breakRemoveOn?: BreakRemoveOnType }) {
|
||||
const { uid, transaction, breakRemoveOn } = options;
|
||||
|
||||
const removeLeafNode = async (nodeUid: string) => {
|
||||
const isLeafNode = await this.isLeafNode(nodeUid, transaction);
|
||||
|
||||
if (isLeafNode) {
|
||||
const { parentUid, schema } = await this.findNodeSchemaWithParent(nodeUid, transaction);
|
||||
|
||||
if (this.breakOnMatched(schema, breakRemoveOn)) {
|
||||
// break at here
|
||||
return;
|
||||
} else {
|
||||
// remove current node
|
||||
await this.remove(nodeUid, {
|
||||
transaction,
|
||||
});
|
||||
// continue remove
|
||||
await removeLeafNode(parentUid);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await removeLeafNode(uid);
|
||||
}
|
||||
|
||||
async remove(uid: string, options?: TransactionAble & removeParentOptions) {
|
||||
let handleTransaction: boolean = true;
|
||||
let transaction;
|
||||
|
||||
@ -332,6 +407,14 @@ export class UiSchemaRepository extends Repository {
|
||||
const treePathTable = this.uiSchemaTreePathTableName;
|
||||
|
||||
try {
|
||||
if (options?.removeParentsIfNoChildren) {
|
||||
await this.removeEmptyParents({ transaction, uid, breakRemoveOn: options.breakRemoveOn });
|
||||
if (handleTransaction) {
|
||||
await transaction.commit();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await this.database.sequelize.query(
|
||||
`DELETE FROM ${this.uiSchemasTableName} WHERE uid IN (
|
||||
SELECT descendant FROM ${treePathTable} WHERE ancestor = :uid
|
||||
@ -371,16 +454,13 @@ export class UiSchemaRepository extends Repository {
|
||||
}
|
||||
}
|
||||
|
||||
async insertBeside(targetUid: string, schema: any, side: 'before' | 'after') {
|
||||
const targetParent = await this.treeCollection().repository.findOne({
|
||||
filter: {
|
||||
descendant: targetUid,
|
||||
depth: 1,
|
||||
},
|
||||
});
|
||||
async insertBeside(targetUid: string, schema: any, side: 'before' | 'after', options?: InsertAdjacentOptions) {
|
||||
const targetParent = await this.findParentUid(targetUid);
|
||||
|
||||
const db = this.database;
|
||||
|
||||
const treeTable = this.uiSchemaTreePathTableName;
|
||||
|
||||
const typeQuery = await db.sequelize.query(`SELECT type from ${treeTable} WHERE ancestor = :uid AND depth = 0;`, {
|
||||
type: 'SELECT',
|
||||
replacements: {
|
||||
@ -393,7 +473,7 @@ export class UiSchemaRepository extends Repository {
|
||||
const rootNode = nodes[0];
|
||||
|
||||
rootNode.childOptions = {
|
||||
parentUid: targetParent.get('ancestor') as string,
|
||||
parentUid: targetParent,
|
||||
type: typeQuery[0]['type'],
|
||||
position: {
|
||||
type: side,
|
||||
@ -401,41 +481,47 @@ export class UiSchemaRepository extends Repository {
|
||||
},
|
||||
};
|
||||
|
||||
const insertedNodes = await this.insertNodes(nodes);
|
||||
const insertedNodes = await this.insertNodes(nodes, options);
|
||||
return await this.getJsonSchema(insertedNodes[0].get('uid'));
|
||||
}
|
||||
|
||||
async insertInner(targetUid: string, schema: any, position: 'first' | 'last') {
|
||||
async insertInner(targetUid: string, schema: any, position: 'first' | 'last', options?: InsertAdjacentOptions) {
|
||||
const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
|
||||
const rootNode = nodes[0];
|
||||
|
||||
rootNode.childOptions = {
|
||||
parentUid: targetUid,
|
||||
type: lodash.get(schema, 'x-node-type', 'properties'),
|
||||
position,
|
||||
};
|
||||
|
||||
const insertedNodes = await this.insertNodes(nodes);
|
||||
const insertedNodes = await this.insertNodes(nodes, options);
|
||||
return await this.getJsonSchema(insertedNodes[0].get('uid'));
|
||||
}
|
||||
|
||||
async insertAdjacent(position: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd', target: string, schema: any) {
|
||||
return await this[`insert${lodash.upperFirst(position)}`](target, schema);
|
||||
async insertAdjacent(
|
||||
position: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd',
|
||||
target: string,
|
||||
schema: any,
|
||||
options?: InsertAdjacentOptions,
|
||||
) {
|
||||
return await this[`insert${lodash.upperFirst(position)}`](target, schema, options);
|
||||
}
|
||||
|
||||
async insertAfterBegin(targetUid: string, schema: any) {
|
||||
return await this.insertInner(targetUid, schema, 'first');
|
||||
async insertAfterBegin(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||
return await this.insertInner(targetUid, schema, 'first', options);
|
||||
}
|
||||
|
||||
async insertBeforeEnd(targetUid: string, schema: any) {
|
||||
return await this.insertInner(targetUid, schema, 'last');
|
||||
async insertBeforeEnd(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||
return await this.insertInner(targetUid, schema, 'last', options);
|
||||
}
|
||||
|
||||
async insertBeforeBegin(targetUid: string, schema: any) {
|
||||
return await this.insertBeside(targetUid, schema, 'before');
|
||||
async insertBeforeBegin(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||
return await this.insertBeside(targetUid, schema, 'before', options);
|
||||
}
|
||||
|
||||
async insertAfterEnd(targetUid: string, schema: any) {
|
||||
return await this.insertBeside(targetUid, schema, 'after');
|
||||
async insertAfterEnd(targetUid: string, schema: any, options?: InsertAdjacentOptions) {
|
||||
return await this.insertBeside(targetUid, schema, 'after', options);
|
||||
}
|
||||
|
||||
async insertNodes(nodes: SchemaNode[], options?) {
|
||||
@ -453,7 +539,12 @@ export class UiSchemaRepository extends Repository {
|
||||
|
||||
try {
|
||||
for (const node of nodes) {
|
||||
insertedNodes.push(await this.insertSingleNode(node, transaction));
|
||||
insertedNodes.push(
|
||||
await this.insertSingleNode(node, {
|
||||
...options,
|
||||
transaction,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (handleTransaction) {
|
||||
@ -497,9 +588,11 @@ export class UiSchemaRepository extends Repository {
|
||||
return node;
|
||||
}
|
||||
|
||||
async insertSingleNode(schema: SchemaNode, transaction: Transaction) {
|
||||
async insertSingleNode(schema: SchemaNode, options: TransactionAble & removeParentOptions) {
|
||||
const { transaction } = options;
|
||||
|
||||
const db = this.database;
|
||||
const treeCollection = db.getCollection('ui_schema_tree_path');
|
||||
const treeCollection = db.getCollection('uiSchemaTreePath');
|
||||
|
||||
const uid = schema['x-uid'];
|
||||
const name = schema['name'];
|
||||
@ -530,6 +623,7 @@ export class UiSchemaRepository extends Repository {
|
||||
}
|
||||
|
||||
if (childOptions) {
|
||||
const oldParentUid = await this.findParentUid(uid, transaction);
|
||||
const parentUid = childOptions.parentUid;
|
||||
|
||||
const isTreeQuery = await db.sequelize.query(
|
||||
@ -547,6 +641,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 )
|
||||
@ -561,6 +656,7 @@ export class UiSchemaRepository extends Repository {
|
||||
},
|
||||
);
|
||||
|
||||
// insert new tree path
|
||||
await db.sequelize.query(
|
||||
`INSERT INTO ${treeTable} (ancestor, descendant, depth)
|
||||
SELECT supertree.ancestor, subtree.descendant, supertree.depth + subtree.depth + 1
|
||||
@ -738,6 +834,23 @@ WHERE TreeTable.depth = 1 AND TreeTable.ancestor = :ancestor and TreeTable.sort
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
// move node to new parent
|
||||
if (oldParentUid !== null && oldParentUid !== parentUid) {
|
||||
await this.database.emitAsync('uiSchemaMove', savedNode, {
|
||||
transaction,
|
||||
oldParentUid,
|
||||
parentUid,
|
||||
});
|
||||
|
||||
if (options.removeParentsIfNoChildren) {
|
||||
await this.recursivelyRemoveIfNoChildren({
|
||||
transaction,
|
||||
uid: oldParentUid,
|
||||
breakRemoveOn: options.breakRemoveOn,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// insert root node path
|
||||
await db.sequelize.query(
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { hookFactory } from './factory';
|
||||
import { removeSchema } from './remove-schema';
|
||||
import { bindMenuToRole } from './bind-menu-to-row';
|
||||
import { removeParentsIfNoChildren } from './remove-parents-if-no-children';
|
||||
|
||||
const hooks = [
|
||||
hookFactory('onCollectionDestroy', 'removeSchema', removeSchema),
|
||||
hookFactory('onCollectionFieldDestroy', 'removeSchema', removeSchema),
|
||||
hookFactory('onSelfCreate', 'bindMenuToRole', bindMenuToRole),
|
||||
hookFactory('onSelfMove', 'removeParentsIfNoChildren', removeParentsIfNoChildren),
|
||||
];
|
||||
|
||||
export { hooks };
|
||||
|
@ -0,0 +1,11 @@
|
||||
import { UiSchemaRepository } from '../../repository';
|
||||
|
||||
export async function removeParentsIfNoChildren({ schemaInstance, db, options, params }) {
|
||||
const { transaction, oldParentUid } = options;
|
||||
const uiSchemaRepository: UiSchemaRepository = db.getRepository('uiSchemas');
|
||||
await uiSchemaRepository.recursivelyRemoveIfNoChildren({
|
||||
transaction,
|
||||
uid: oldParentUid,
|
||||
breakRemoveOn: params?.breakRemoveOn,
|
||||
});
|
||||
}
|
@ -5,10 +5,10 @@ export async function removeSchema({ schemaInstance, options, db, params }) {
|
||||
const uiSchemaRepository: UiSchemaRepository = db.getRepository('uiSchemas');
|
||||
const uid = schemaInstance.get('uid') as string;
|
||||
|
||||
if (params?.removeEmptyParents) {
|
||||
if (params?.removeParentsIfNoChildren) {
|
||||
await uiSchemaRepository.removeEmptyParents({
|
||||
uid,
|
||||
breakComponent: params['breakComponent'],
|
||||
breakRemoveOn: params['breakRemoveOn'],
|
||||
transaction,
|
||||
});
|
||||
} else {
|
||||
|
@ -7,7 +7,8 @@ export type HookType =
|
||||
| 'onCollectionDestroy'
|
||||
| 'onCollectionFieldDestroy'
|
||||
| 'onAnyCollectionFieldDestroy'
|
||||
| 'onSelfCreate';
|
||||
| 'onSelfCreate'
|
||||
| 'onSelfMove';
|
||||
|
||||
export class ServerHooks {
|
||||
hooks = new Map<HookType, Map<string, any>>();
|
||||
@ -34,6 +35,30 @@ export class ServerHooks {
|
||||
this.db.on('uiSchemas.afterCreateWithAssociations', async (model, options) => {
|
||||
await this.onUiSchemaCreate(model, options);
|
||||
});
|
||||
|
||||
this.db.on('uiSchemaMove', async (model, options) => {
|
||||
await this.onUiSchemaMove(model, options);
|
||||
});
|
||||
}
|
||||
|
||||
protected async callSchemaInstanceHooksByType(schemaInstance, options, type: HookType) {
|
||||
const { transaction } = options;
|
||||
|
||||
const hooks = schemaInstance.getServerHooksByType(type);
|
||||
|
||||
for (const hook of hooks) {
|
||||
const hookFunc = this.hooks.get(type)?.get(hook['method']);
|
||||
await hookFunc({
|
||||
schemaInstance,
|
||||
options,
|
||||
db: this.db,
|
||||
params: hook['params'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected async onUiSchemaMove(schemaInstance, options) {
|
||||
await this.callSchemaInstanceHooksByType(schemaInstance, options, 'onSelfMove');
|
||||
}
|
||||
|
||||
protected async onCollectionDestroy(collectionModel, options) {
|
||||
@ -89,21 +114,7 @@ export class ServerHooks {
|
||||
}
|
||||
|
||||
protected async onUiSchemaCreate(schemaInstance, options) {
|
||||
const { transaction } = options;
|
||||
|
||||
const serverHooks = schemaInstance.get('serverHooks') || [];
|
||||
|
||||
const onSelfCreateHooks = serverHooks.filter((serverHook) => serverHook.get('type') === 'onSelfCreate');
|
||||
|
||||
for (const serverHook of onSelfCreateHooks) {
|
||||
const hookFunc = this.hooks.get('onSelfCreate')?.get(serverHook.get('method'));
|
||||
await hookFunc({
|
||||
schemaInstance,
|
||||
options,
|
||||
db: this.db,
|
||||
params: serverHook.get('params'),
|
||||
});
|
||||
}
|
||||
await this.callSchemaInstanceHooksByType(schemaInstance, options, 'onSelfCreate');
|
||||
}
|
||||
|
||||
protected async findHooksAndCall(hooksFilter, hooksArgs, transaction) {
|
||||
|
Loading…
Reference in New Issue
Block a user