|
import { URL } from 'node:url'; |
|
import get from 'lodash.get'; |
|
import Base from './base.js'; |
|
import App from './app.js'; |
|
import Flow from './flow.js'; |
|
import Connection from './connection.js'; |
|
import ExecutionStep from './execution-step.js'; |
|
import Telemetry from '../helpers/telemetry/index.js'; |
|
import appConfig from '../config/app.js'; |
|
import globalVariable from '../helpers/global-variable.js'; |
|
import computeParameters from '../helpers/compute-parameters.js'; |
|
|
|
class Step extends Base { |
|
static tableName = 'steps'; |
|
|
|
static jsonSchema = { |
|
type: 'object', |
|
required: ['type'], |
|
|
|
properties: { |
|
id: { type: 'string', format: 'uuid' }, |
|
flowId: { type: 'string', format: 'uuid' }, |
|
key: { type: ['string', 'null'] }, |
|
appKey: { type: ['string', 'null'], minLength: 1, maxLength: 255 }, |
|
type: { type: 'string', enum: ['action', 'trigger'] }, |
|
connectionId: { type: ['string', 'null'], format: 'uuid' }, |
|
status: { |
|
type: 'string', |
|
enum: ['incomplete', 'completed'], |
|
default: 'incomplete', |
|
}, |
|
position: { type: 'integer' }, |
|
parameters: { type: 'object' }, |
|
webhookPath: { type: ['string', 'null'] }, |
|
deletedAt: { type: 'string' }, |
|
createdAt: { type: 'string' }, |
|
updatedAt: { type: 'string' }, |
|
}, |
|
}; |
|
|
|
static get virtualAttributes() { |
|
return ['iconUrl', 'webhookUrl']; |
|
} |
|
|
|
static relationMappings = () => ({ |
|
flow: { |
|
relation: Base.BelongsToOneRelation, |
|
modelClass: Flow, |
|
join: { |
|
from: 'steps.flow_id', |
|
to: 'flows.id', |
|
}, |
|
}, |
|
connection: { |
|
relation: Base.HasOneRelation, |
|
modelClass: Connection, |
|
join: { |
|
from: 'steps.connection_id', |
|
to: 'connections.id', |
|
}, |
|
}, |
|
executionSteps: { |
|
relation: Base.HasManyRelation, |
|
modelClass: ExecutionStep, |
|
join: { |
|
from: 'steps.id', |
|
to: 'execution_steps.step_id', |
|
}, |
|
}, |
|
}); |
|
|
|
get webhookUrl() { |
|
return new URL(this.webhookPath, appConfig.webhookUrl).toString(); |
|
} |
|
|
|
get iconUrl() { |
|
if (!this.appKey) return null; |
|
|
|
return `${appConfig.baseUrl}/apps/${this.appKey}/assets/favicon.svg`; |
|
} |
|
|
|
async computeWebhookPath() { |
|
if (this.type === 'action') return null; |
|
|
|
const triggerCommand = await this.getTriggerCommand(); |
|
|
|
if (!triggerCommand) return null; |
|
|
|
const { useSingletonWebhook, singletonWebhookRefValueParameter, type } = |
|
triggerCommand; |
|
|
|
const isWebhook = type === 'webhook'; |
|
|
|
if (!isWebhook) return null; |
|
|
|
if (singletonWebhookRefValueParameter) { |
|
const parameterValue = get( |
|
this.parameters, |
|
singletonWebhookRefValueParameter |
|
); |
|
return `/webhooks/connections/${this.connectionId}/${parameterValue}`; |
|
} |
|
|
|
if (useSingletonWebhook) { |
|
return `/webhooks/connections/${this.connectionId}`; |
|
} |
|
|
|
if (this.parameters.workSynchronously) { |
|
return `/webhooks/flows/${this.flowId}/sync`; |
|
} |
|
|
|
return `/webhooks/flows/${this.flowId}`; |
|
} |
|
|
|
async getWebhookUrl() { |
|
if (this.type === 'action') return; |
|
|
|
const path = await this.computeWebhookPath(); |
|
const webhookUrl = new URL(path, appConfig.webhookUrl).toString(); |
|
|
|
return webhookUrl; |
|
} |
|
|
|
async $afterInsert(queryContext) { |
|
await super.$afterInsert(queryContext); |
|
Telemetry.stepCreated(this); |
|
} |
|
|
|
async $afterUpdate(opt, queryContext) { |
|
await super.$afterUpdate(opt, queryContext); |
|
Telemetry.stepUpdated(this); |
|
} |
|
|
|
get isTrigger() { |
|
return this.type === 'trigger'; |
|
} |
|
|
|
get isAction() { |
|
return this.type === 'action'; |
|
} |
|
|
|
async getApp() { |
|
if (!this.appKey) return null; |
|
|
|
return await App.findOneByKey(this.appKey); |
|
} |
|
|
|
async getLastExecutionStep() { |
|
const lastExecutionStep = await this.$relatedQuery('executionSteps') |
|
.orderBy('created_at', 'desc') |
|
.limit(1) |
|
.first(); |
|
|
|
return lastExecutionStep; |
|
} |
|
|
|
async getNextStep() { |
|
const flow = await this.$relatedQuery('flow'); |
|
|
|
return await flow |
|
.$relatedQuery('steps') |
|
.findOne({ position: this.position + 1 }); |
|
} |
|
|
|
async getTriggerCommand() { |
|
const { appKey, key, isTrigger } = this; |
|
if (!isTrigger || !appKey || !key) return null; |
|
|
|
const app = await App.findOneByKey(appKey); |
|
const command = app.triggers?.find((trigger) => trigger.key === key); |
|
|
|
return command; |
|
} |
|
|
|
async getActionCommand() { |
|
const { appKey, key, isAction } = this; |
|
if (!isAction || !appKey || !key) return null; |
|
|
|
const app = await App.findOneByKey(appKey); |
|
const command = app.actions?.find((action) => action.key === key); |
|
|
|
return command; |
|
} |
|
|
|
async getSetupFields() { |
|
let setupSupsteps; |
|
|
|
if (this.isTrigger) { |
|
setupSupsteps = (await this.getTriggerCommand()).substeps; |
|
} else { |
|
setupSupsteps = (await this.getActionCommand()).substeps; |
|
} |
|
|
|
const existingArguments = setupSupsteps.find( |
|
(substep) => substep.key === 'chooseTrigger' |
|
).arguments; |
|
|
|
return existingArguments; |
|
} |
|
|
|
async createDynamicFields(dynamicFieldsKey, parameters) { |
|
const connection = await this.$relatedQuery('connection'); |
|
const flow = await this.$relatedQuery('flow'); |
|
const app = await this.getApp(); |
|
const $ = await globalVariable({ connection, app, flow, step: this }); |
|
|
|
const command = app.dynamicFields.find( |
|
(data) => data.key === dynamicFieldsKey |
|
); |
|
|
|
for (const parameterKey in parameters) { |
|
const parameterValue = parameters[parameterKey]; |
|
$.step.parameters[parameterKey] = parameterValue; |
|
} |
|
|
|
const dynamicFields = (await command.run($)) || []; |
|
|
|
return dynamicFields; |
|
} |
|
|
|
async createDynamicData(dynamicDataKey, parameters) { |
|
const connection = await this.$relatedQuery('connection'); |
|
const flow = await this.$relatedQuery('flow'); |
|
const app = await this.getApp(); |
|
const $ = await globalVariable({ connection, app, flow, step: this }); |
|
|
|
const command = app.dynamicData.find((data) => data.key === dynamicDataKey); |
|
|
|
for (const parameterKey in parameters) { |
|
const parameterValue = parameters[parameterKey]; |
|
$.step.parameters[parameterKey] = parameterValue; |
|
} |
|
|
|
const lastExecution = await flow.$relatedQuery('lastExecution'); |
|
const lastExecutionId = lastExecution?.id; |
|
|
|
const priorExecutionSteps = lastExecutionId |
|
? await ExecutionStep.query().where({ |
|
execution_id: lastExecutionId, |
|
}) |
|
: []; |
|
|
|
const computedParameters = computeParameters( |
|
$.step.parameters, |
|
priorExecutionSteps |
|
); |
|
|
|
$.step.parameters = computedParameters; |
|
const dynamicData = (await command.run($)).data; |
|
|
|
return dynamicData; |
|
} |
|
|
|
async updateWebhookUrl() { |
|
if (this.isAction) return this; |
|
|
|
const payload = { |
|
webhookPath: await this.computeWebhookPath(), |
|
}; |
|
|
|
await this.$query().patchAndFetch(payload); |
|
|
|
return this; |
|
} |
|
} |
|
|
|
export default Step; |
|
|