Merge branch 'master' into on-call

This commit is contained in:
Simon Larsen 2023-06-19 11:29:39 +01:00
commit ad451fd9c9
No known key found for this signature in database
GPG Key ID: AB45983AA9C81CDE
8 changed files with 238 additions and 100 deletions

View File

@ -18,6 +18,7 @@ export enum ComponentInputType {
Email = 'Email',
CronTab = 'CronTab',
Query = 'Database Query',
Select = 'Database Select',
BaseModel = 'Database Record',
BaseModelArray = 'Database Records',
JSONArray = 'List of JSON',

View File

@ -33,7 +33,7 @@ export default class BaseModelComponent {
placeholder: 'Example: {"columnName": "value", ...}',
},
{
type: ComponentInputType.Query,
type: ComponentInputType.Select,
name: 'Select Fields',
description: `Select on ${model.singularName}`,
required: true,
@ -92,7 +92,7 @@ export default class BaseModelComponent {
placeholder: 'Example: {"columnName": "value", ...}',
},
{
type: ComponentInputType.Query,
type: ComponentInputType.Select,
name: 'Select Fields',
description: `Select on ${model.singularName}`,
required: true,
@ -288,7 +288,16 @@ export default class BaseModelComponent {
iconProp: IconProp.Bolt,
tableName: model.tableName!,
componentType: ComponentType.Trigger,
arguments: [],
arguments: [
{
type: ComponentInputType.Select,
name: 'Select Fields',
description: `Select on ${model.singularName}`,
required: true,
id: 'select',
placeholder: 'Example: {"columnName": true, ...}',
},
],
returnValues: [
{
id: 'data',
@ -422,7 +431,16 @@ export default class BaseModelComponent {
iconProp: IconProp.Bolt,
tableName: model.tableName!,
componentType: ComponentType.Trigger,
arguments: [],
arguments: [
{
type: ComponentInputType.Select,
name: 'Select Fields',
description: `Select on ${model.singularName}`,
required: true,
id: 'select',
placeholder: 'Example: {"columnName": true, ...}',
},
],
returnValues: [
{
id: 'data',

View File

@ -47,7 +47,6 @@ import API from 'Common/Utils/API';
import Protocol from 'Common/Types/API/Protocol';
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import JSONFunctions from 'Common/Types/JSONFunctions';
import ClusterKeyAuthorization from '../Middleware/ClusterKeyAuthorization';
import Text from 'Common/Types/Text';
@ -518,7 +517,7 @@ class DatabaseService<TBaseModel extends BaseModel> {
}
public async onTrigger(
model: TBaseModel,
id: ObjectID,
projectId: ObjectID,
triggerType: DatabaseTriggerType
): Promise<void> {
@ -533,7 +532,9 @@ class DatabaseService<TBaseModel extends BaseModel> {
)
),
{
data: JSONFunctions.toJSON(model, this.entityType),
data: {
_id: id.toString(),
},
},
{
...ClusterKeyAuthorization.getClusterKeyHeaders(),
@ -618,7 +619,7 @@ class DatabaseService<TBaseModel extends BaseModel> {
)))
) {
await this.onTrigger(
createBy.data,
createBy.data.id!,
createBy.props.tenantId ||
createBy.data.getValue<ObjectID>(
this.getModel().getTenantColumn()!
@ -954,7 +955,7 @@ class DatabaseService<TBaseModel extends BaseModel> {
deleteBy.props.tenantId
) {
await this.onTrigger(
item,
item.id!,
deleteBy.props.tenantId ||
item.getValue<ObjectID>(
this.getModel().getTenantColumn()!
@ -992,15 +993,23 @@ class DatabaseService<TBaseModel extends BaseModel> {
withDeleted?: boolean | undefined
): Promise<Array<TBaseModel>> {
try {
let automaticallyAddedCreatedAtInSelect: boolean = false;
if (!findBy.sort || Object.keys(findBy.sort).length === 0) {
findBy.sort = {
createdAt: SortOrder.Descending,
};
if (!(findBy.select as any)['createdAt']) {
(findBy.select as any)['createdAt'] = true;
automaticallyAddedCreatedAtInSelect = true;
}
}
const onFind: OnFind<TBaseModel> = findBy.props.ignoreHooks
? { findBy, carryForward: [] }
: await this.onBeforeFind(findBy);
const onBeforeFind: FindBy<TBaseModel> = onFind.findBy;
const onBeforeFind: FindBy<TBaseModel> = { ...onFind.findBy };
const carryForward: any = onFind.carryForward;
if (
@ -1014,10 +1023,6 @@ class DatabaseService<TBaseModel extends BaseModel> {
(onBeforeFind.select as any)['_id'] = true;
}
if (!(onBeforeFind.select as any)['createdAt']) {
(onBeforeFind.select as any)['createdAt'] = true;
}
const result: {
query: Query<TBaseModel>;
select: Select<TBaseModel> | null;
@ -1060,6 +1065,13 @@ class DatabaseService<TBaseModel extends BaseModel> {
decryptedItems,
onBeforeFind
);
for (const item of decryptedItems) {
if (automaticallyAddedCreatedAtInSelect) {
delete (item as any).createdAt;
}
}
if (!findBy.props.ignoreHooks) {
decryptedItems = await (
await this.onFindSuccess(
@ -1230,7 +1242,7 @@ class DatabaseService<TBaseModel extends BaseModel> {
))
) {
await this.onTrigger(
item,
item.id!,
updateBy.props.tenantId ||
item.getValue<ObjectID>(
this.getModel().getTenantColumn()!

View File

@ -1,7 +1,7 @@
import BaseModel from 'Common/Models/BaseModel';
import BadDataException from 'Common/Types/Exception/BadDataException';
import ObjectID from 'Common/Types/ObjectID';
import ComponentMetadata from 'Common/Types/Workflow/Component';
import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component';
import DatabaseService from '../../../../Services/DatabaseService';
import { ExpressRequest, ExpressResponse } from '../../../../Utils/Express';
import Response from '../../../../Utils/Response';
@ -12,6 +12,10 @@ import WorkflowService from '../../../../Services/WorkflowService';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import Workflow from 'Model/Models/Workflow';
import ClusterKeyAuthorization from '../../../../Middleware/ClusterKeyAuthorization';
import { JSONObject } from 'Common/Types/JSON';
import { RunOptions, RunReturnType } from '../../ComponentCode';
import JSONFunctions from 'Common/Types/JSONFunctions';
import Select from '../../../Database/Select';
export default class OnTriggerBaseModel<
TBaseModel extends BaseModel
@ -19,12 +23,14 @@ export default class OnTriggerBaseModel<
public modelId: string = '';
public type: string = '';
public service: DatabaseService<TBaseModel> | null = null;
public constructor(
modelService: DatabaseService<TBaseModel>,
type: string
) {
super();
this.service = modelService;
this.modelId = `${Text.pascalCaseToDashes(
modelService.getModel().tableName!
)}`;
@ -66,6 +72,80 @@ export default class OnTriggerBaseModel<
);
}
public override async run(
args: JSONObject,
options: RunOptions
): Promise<RunReturnType> {
const data: JSONObject = args['data'] as JSONObject;
const successPort: Port | undefined = this.getMetadata().outPorts.find(
(p: Port) => {
return p.id === 'success';
}
);
if (!successPort) {
throw options.onError(
new BadDataException('Success port not found')
);
}
if (
!data['_id'] ||
!args['select'] ||
Object.keys(args['select']).length === 0
) {
return {
returnValues: {
model: data
? JSONFunctions.toJSON(
data as any,
this.service!.entityType
)
: null,
},
executePort: successPort,
};
}
let select: Select<TBaseModel> = args['select'] as Select<TBaseModel>;
if (select) {
select = JSONFunctions.deserialize(
args['select'] as JSONObject
) as Select<TBaseModel>;
}
const model: TBaseModel | null = await this.service!.findOneById({
id: new ObjectID(args['_id'] as string),
props: {
isRoot: true,
},
select: {
_id: true,
...select,
},
});
if (!model) {
throw new BadDataException(
('Model not found with id ' + args['_id']) as string
);
}
return {
returnValues: {
model: data
? JSONFunctions.toJSON(
model as any,
this.service!.entityType
)
: null,
},
executePort: successPort,
};
}
public async initTrigger(
req: ExpressRequest,
res: ExpressResponse,
@ -95,6 +175,8 @@ export default class OnTriggerBaseModel<
for (const workflow of workflows) {
/// Run Graph.
/// Find the object and send data.
const executeWorkflow: ExecuteWorkflowType = {
workflowId: workflow.id!,
returnValues: {

View File

@ -4,7 +4,9 @@
import { JSONObject } from 'Common/Types/JSON';
import ObjectID from 'Common/Types/ObjectID';
import { ExpressRouter } from '../../Utils/Express';
import ComponentCode from './ComponentCode';
import ComponentCode, { RunOptions, RunReturnType } from './ComponentCode';
import { Port } from 'Common/Types/Workflow/Component';
import BadDataException from 'Common/Types/Exception/BadDataException';
export interface ExecuteWorkflowType {
workflowId: ObjectID;
@ -44,6 +46,30 @@ export default class TrigegrCode extends ComponentCode {
super();
}
public override async run(
args: JSONObject,
options: RunOptions
): Promise<RunReturnType> {
const successPort: Port | undefined = this.getMetadata().outPorts.find(
(p: Port) => {
return p.id === 'success';
}
);
if (!successPort) {
throw options.onError(
new BadDataException('Success port not found')
);
}
return {
returnValues: {
...args,
},
executePort: successPort,
};
}
public async setupComponent(props: InitProps): Promise<void> {
this.executeWorkflow = props.executeWorkflow;
this.scheduleWorkflow = props.scheduleWorkflow;

View File

@ -74,6 +74,8 @@ const RunModal: FunctionComponent<ComponentProps> = (
ComponentInputType.BaseModelArray ||
args.type ===
ComponentInputType.Query ||
args.type ===
ComponentInputType.Select ||
args.type ===
ComponentInputType.StringDictionary) &&
component.returnValues[args.id] &&

View File

@ -80,6 +80,12 @@ export const componentInputTypeToFormFieldType: Function = (
};
}
if (componentInputType === ComponentInputType.Select) {
return {
fieldType: FormFieldSchemaType.JSON,
};
}
if (componentInputType === ComponentInputType.StringDictionary) {
return {
fieldType: FormFieldSchemaType.JSON,

View File

@ -196,99 +196,70 @@ export default class RunWorkflow {
);
}
// execute this stack.
// now actually run this component.
let args: JSONObject = this.getComponentArguments(
storageMap,
stackItem.node
);
if (stackItem.node.componentType === ComponentType.Trigger) {
// this is already executed. So, place its arguments inside of storage map.
storageMap.local.components[stackItem.node.id] = {
returnValues: runProps.arguments,
// If this is the trigger. Then pass workflow argument to this component as args to execute.
args = {
...args,
...runProps.arguments,
};
}
this.log('Trigger args:');
this.log(runProps.arguments);
this.log('Component Args:');
this.log(args);
this.log('Component Logs: ' + executeComponentId);
// need port to be executed.
const nodesToBeExecuted: Array<string> | undefined =
Object.keys(stackItem.outPorts)
.map((outport: string) => {
return stackItem.outPorts[outport] || [];
})
.flat();
const result: RunReturnType = await this.runComponent(
args,
stackItem.node,
setDidErrorOut
);
if (nodesToBeExecuted && nodesToBeExecuted.length > 0) {
nodesToBeExecuted.forEach((item: string) => {
// if its not in the stack, then add it to execution stack.
if (
!fifoStackOfComponentsPendingExecution.includes(
item
)
) {
fifoStackOfComponentsPendingExecution.push(
item
);
}
});
}
} else {
// now actually run this component.
const args: JSONObject = this.getComponentArguments(
storageMap,
stackItem.node
if (didWorkflowErrorOut) {
throw new BadDataException(
'Workflow stopped because of an error'
);
}
this.log('Component Args:');
this.log(args);
this.log('Component Logs: ' + executeComponentId);
const result: RunReturnType = await this.runComponent(
args,
stackItem.node,
setDidErrorOut
);
this.log(
'Completed Execution Component: ' + executeComponentId
);
this.log('Data Returned');
this.log(result.returnValues);
this.log(
'Executing Port: ' + result.executePort?.title || '<None>'
);
if (didWorkflowErrorOut) {
throw new BadDataException(
'Workflow stopped because of an error'
);
}
storageMap.local.components[stackItem.node.id] = {
returnValues: result.returnValues,
};
this.log(
'Completed Execution Component: ' + executeComponentId
);
this.log('Data Returned');
this.log(result.returnValues);
this.log(
'Executing Port: ' + result.executePort?.title ||
'<None>'
);
const portToBeExecuted: Port | undefined = result.executePort;
storageMap.local.components[stackItem.node.id] = {
returnValues: result.returnValues,
};
if (!portToBeExecuted) {
break; // stop the workflow, the process has ended.
}
const portToBeExecuted: Port | undefined =
result.executePort;
const nodesToBeExecuted: Array<string> | undefined =
stackItem.outPorts[portToBeExecuted.id];
if (!portToBeExecuted) {
break; // stop the workflow, the process has ended.
}
const nodesToBeExecuted: Array<string> | undefined =
stackItem.outPorts[portToBeExecuted.id];
if (nodesToBeExecuted && nodesToBeExecuted.length > 0) {
nodesToBeExecuted.forEach((item: string) => {
// if its not in the stack, then add it to execution stack.
if (
!fifoStackOfComponentsPendingExecution.includes(
item
)
) {
fifoStackOfComponentsPendingExecution.push(
item
);
}
});
}
if (nodesToBeExecuted && nodesToBeExecuted.length > 0) {
nodesToBeExecuted.forEach((item: string) => {
// if its not in the stack, then add it to execution stack.
if (
!fifoStackOfComponentsPendingExecution.includes(
item
)
) {
fifoStackOfComponentsPendingExecution.push(item);
}
});
}
}
@ -308,7 +279,7 @@ export default class RunWorkflow {
});
} catch (err: any) {
logger.error(err);
this.log(err.toString());
this.log(err.message || err.toString());
if (!runProps.workflowLogId) {
return;
@ -473,6 +444,26 @@ export default class RunWorkflow {
argumentContent = argumentContentCopy;
}
if (
typeof argumentContent === 'string' &&
(argument.type === ComponentInputType.JSON ||
argument.type === ComponentInputType.Query ||
argument.type === ComponentInputType.Select)
) {
try {
argumentContent = JSON.parse(argumentContent);
} catch (err: any) {
throw new BadDataException(
'Invalid JSON provided for argument ' +
argument.id +
'. JSON parse error: ' +
err.message +
'. JSON: ' +
argumentContent
);
}
}
argumentObj[argument.id] = argumentContent;
}