tachybase_todo/packages/plugins/workflow/src/server/triggers/collection.ts
Junyi 9f8460ca22
feat(plugin-workflow): change to unlimited depth preloading associations in workflow (#2142)
* refactor(plugin-snapshot): move AppendsTreeSelect component into client package

* refactor(plugin-workflow): change all appends fields select to AppendsTreeSelect

* refactor(plugin-workflow): change appends and toJSON logic on server side

* fix(plugin-workflow): fix toJSON logic and build error

* fix(plugin-workflow): fix missing component injection

* fix(plugin-workflow): fix cycle association in variables

* refactor(client): change AppendsTreeSelect to lazy load

* fix(client): fix lazy load in option
2023-07-17 20:36:17 -07:00

147 lines
4.0 KiB
TypeScript

import { Collection, Model } from '@nocobase/database';
import { Trigger } from '..';
import { toJSON } from '../utils';
import type { WorkflowModel } from '../types';
export interface CollectionChangeTriggerConfig {
collection: string;
mode: number;
// TODO: ICondition
condition: any;
}
const MODE_BITMAP = {
CREATE: 1,
UPDATE: 2,
DESTROY: 4,
};
const MODE_BITMAP_EVENTS = new Map();
MODE_BITMAP_EVENTS.set(MODE_BITMAP.CREATE, 'afterCreateWithAssociations');
MODE_BITMAP_EVENTS.set(MODE_BITMAP.UPDATE, 'afterUpdateWithAssociations');
MODE_BITMAP_EVENTS.set(MODE_BITMAP.DESTROY, 'afterDestroy');
function getHookId(workflow, type) {
return `${type}#${workflow.id}`;
}
function getFieldRawName(collection: Collection, name: string) {
const field = collection.getField(name);
if (field && field.type === 'belongsTo') {
return field.foreignKey;
}
return name;
}
// async function, should return promise
async function handler(this: CollectionTrigger, workflow: WorkflowModel, data: Model, options) {
const { collection: collectionName, condition, changed, mode, appends } = workflow.config;
const collection = (<typeof Model>data.constructor).database.getCollection(collectionName);
const { transaction, context } = options;
const { repository, model } = collection;
// NOTE: if no configured fields changed, do not trigger
if (
changed &&
changed.length &&
changed
.filter((name) => !['linkTo', 'hasOne', 'hasMany', 'belongsToMany'].includes(collection.getField(name).type))
.every((name) => !data.changedWithAssociations(getFieldRawName(collection, name)))
) {
return;
}
// NOTE: if no configured condition match, do not trigger
if (condition && condition.$and?.length) {
// TODO: change to map filter format to calculation format
// const calculation = toCalculation(condition);
const count = await repository.count({
filter: {
$and: [condition, { [model.primaryKeyAttribute]: data[model.primaryKeyAttribute] }],
},
context,
transaction,
});
if (!count) {
return;
}
}
let result = data;
if (appends?.length && !(mode & MODE_BITMAP.DESTROY)) {
const includeFields = appends.reduce((set, field) => {
set.add(field.split('.')[0]);
set.add(field);
return set;
}, new Set());
result = await repository.findOne({
filterByTk: data[model.primaryKeyAttribute],
appends: Array.from(includeFields),
transaction,
});
}
// TODO: `result.toJSON()` throws error
const json = toJSON(result);
this.plugin.trigger(
workflow,
{ data: json },
{
context,
},
);
}
export default class CollectionTrigger extends Trigger {
events = new Map();
on(workflow: WorkflowModel) {
const { db } = this.plugin.app;
const { collection, mode } = workflow.config;
const Collection = db.getCollection(collection);
if (!Collection) {
return;
}
for (const [key, type] of MODE_BITMAP_EVENTS.entries()) {
const event = `${collection}.${type}`;
const name = getHookId(workflow, event);
if (mode & key) {
if (!this.events.has(name)) {
const listener = handler.bind(this, workflow);
this.events.set(name, listener);
db.on(event, listener);
}
} else {
const listener = this.events.get(name);
if (listener) {
db.off(event, listener);
this.events.delete(name);
}
}
}
}
off(workflow: WorkflowModel) {
const { db } = this.plugin.app;
const { collection, mode } = workflow.config;
const Collection = db.getCollection(collection);
if (!Collection) {
return;
}
for (const [key, type] of MODE_BITMAP_EVENTS.entries()) {
const event = `${collection}.${type}`;
const name = getHookId(workflow, event);
if (mode & key) {
const listener = this.events.get(name);
if (listener) {
db.off(event, listener);
this.events.delete(name);
}
}
}
}
}