Files
speckle-server/packages/server/modules/automate/repositories/automations.ts
T
Kristaps Fabians Geikins bde148f286 chore(server): migrating fully to ESM (#5042)
* wip

* some extra fixes

* stuff kinda works?

* need to figure out mocks

* need to figure out mocks

* fix db listener

* gqlgen fix

* minor gqlgen watch adjustment

* lint fixes

* delete old codegen file

* converting migrations to ESM

* getModuleDIrectory

* vitest sort of works

* added back ts-vitest

* resolve gql double load

* fixing test timeout configs

* TSC lint fix

* fix automate tests

* moar debugging

* debugging

* more debugging

* codegen update

* server works

* yargs migrated

* chore(server): getting rid of global mocks for Server ESM (#5046)

* got rid of email mock

* got rid of comment mocks

* got rid of multi region mocks

* got rid of stripe mock

* admin override mock updated

* removed final mock

* fixing import.meta.resolve calls

* another import.meta.resolve fix

* added requested test

* nyc ESM fix

* removed unneeded deps + linting

* yarn lock forgot to commit

* tryna fix flakyness

* email capture util fix

* sendEmail fix

* fix TSX check

* sender transporter fix + CR comments

* merge main fix

* test fixx

* circleci fix

* gqlgen bigint fix

* error formatter fix

* more error formatting improvements

* esmloader added to Dockerfile

* more dockerfile fixes

* bg jobs fix
2025-07-14 10:26:19 +03:00

1052 lines
32 KiB
TypeScript

import {
MarkAutomationDeleted,
GetActiveTriggerDefinitions,
GetAutomation,
GetAutomationProject,
GetAutomationProjects,
GetAutomationRevision,
GetAutomationRevisions,
GetAutomationRunFullTriggers,
GetAutomationRunWithToken,
GetAutomations,
GetAutomationToken,
GetAutomationTriggerDefinitions,
GetFullAutomationRevisionMetadata,
GetFullAutomationRunById,
GetFunctionRun,
GetLatestAutomationRevision,
GetLatestAutomationRevisions,
GetLatestVersionAutomationRuns,
GetProjectAutomationCount,
GetRevisionsFunctions,
GetRevisionsTriggerDefinitions,
QueryAllAutomationFunctionRuns,
StoreAutomation,
StoreAutomationRevision,
StoreAutomationToken,
UpdateAutomation,
UpdateAutomationRun,
UpsertAutomationFunctionRun,
UpsertAutomationRun
} from '@/modules/automate/domain/operations'
import {
AutomationRunFullTrigger,
InsertableAutomationFunctionRun
} from '@/modules/automate/domain/types'
import {
AutomationRecord,
AutomationRevisionRecord,
AutomationTriggerDefinitionRecord,
AutomationRunRecord,
AutomationTokenRecord,
AutomationTriggerRecordBase,
AutomationRevisionFunctionRecord,
AutomationRunWithTriggersFunctionRuns,
AutomationRunTriggerRecord,
AutomationFunctionRunRecord,
AutomationRevisionWithTriggersFunctions,
AutomationTriggerType,
VersionCreationTriggerType,
isVersionCreatedTrigger
} from '@/modules/automate/helpers/types'
import {
AutomationFunctionRuns,
AutomationRevisionFunctions,
AutomationRevisions,
AutomationRunTriggers,
AutomationRuns,
AutomationTokens,
AutomationTriggers,
Automations,
BranchCommits,
Branches,
Commits,
StreamAcl,
Streams,
knex
} from '@/modules/core/dbSchema'
import {
AutomationRunsArgs,
ProjectAutomationsArgs
} from '@/modules/core/graph/generated/graphql'
import { StreamRecord } from '@/modules/core/helpers/types'
import {
executeBatchedSelect,
formatJsonArrayRecords
} from '@/modules/shared/helpers/dbHelper'
import {
decodeCursor,
decodeIsoDateCursor,
encodeIsoDateCursor
} from '@/modules/shared/helpers/dbHelper'
import { Nullable, StreamRoles, isNullOrUndefined } from '@speckle/shared'
import cryptoRandomString from 'crypto-random-string'
import { Knex } from 'knex'
import { clamp, groupBy, keyBy, pick } from 'lodash-es'
import { SetOptional, SetRequired } from 'type-fest'
const tables = {
automations: (db: Knex) => db<AutomationRecord>(Automations.name),
automationTokens: (db: Knex) => db<AutomationTokenRecord>(AutomationTokens.name),
automationRevisions: (db: Knex) =>
db<AutomationRevisionRecord>(AutomationRevisions.name),
automationRevisionFunctions: (db: Knex) =>
db<AutomationRevisionFunctionRecord>(AutomationRevisionFunctions.name),
automationTriggers: (db: Knex) =>
db<AutomationTriggerDefinitionRecord>(AutomationTriggers.name),
automationRuns: (db: Knex) => db<AutomationRunRecord>(AutomationRuns.name),
automationRunTriggers: (db: Knex) =>
db<AutomationRunTriggerRecord>(AutomationRunTriggers.name),
automationFunctionRuns: (db: Knex) =>
db<AutomationFunctionRunRecord>(AutomationFunctionRuns.name)
}
export const generateRevisionId = () => cryptoRandomString({ length: 10 })
export const getActiveTriggerDefinitionsFactory =
(deps: { db: Knex }): GetActiveTriggerDefinitions =>
async <T extends AutomationTriggerType = AutomationTriggerType>(
params: AutomationTriggerRecordBase<T>
) => {
const { triggeringId, triggerType } = params
const q = tables
.automationTriggers(deps.db)
.select<AutomationTriggerDefinitionRecord<T>[]>('*')
.where(AutomationTriggers.col.triggeringId, triggeringId)
.andWhere(AutomationTriggers.col.triggerType, triggerType)
return await q
}
export const getFullAutomationRevisionMetadataFactory =
(deps: { db: Knex }): GetFullAutomationRevisionMetadata =>
async (revisionId: string) => {
const query = tables
.automationRevisions(deps.db)
.where(AutomationRevisions.col.id, revisionId)
.first()
const automationRevision = await query
if (!automationRevision) return null
const [functions, triggers, automation] = await Promise.all([
tables
.automationRevisionFunctions(deps.db)
.select('*')
.where(AutomationRevisionFunctions.col.automationRevisionId, revisionId),
tables
.automationTriggers(deps.db)
.select('*')
.where(AutomationTriggers.col.automationRevisionId, revisionId),
tables
.automations(deps.db)
.where(Automations.col.id, automationRevision.automationId)
.first()
])
if (!automation) return null
return {
...automation,
revision: {
...automationRevision,
functions,
triggers
}
}
}
export const upsertAutomationFunctionRunFactory =
(deps: { db: Knex }): UpsertAutomationFunctionRun =>
async (automationFunctionRun: InsertableAutomationFunctionRun) => {
await tables
.automationFunctionRuns(deps.db)
.insert(
pick(automationFunctionRun, AutomationFunctionRuns.withoutTablePrefix.cols)
)
.onConflict(AutomationFunctionRuns.withoutTablePrefix.col.id)
.merge([
AutomationFunctionRuns.withoutTablePrefix.col.contextView,
AutomationFunctionRuns.withoutTablePrefix.col.elapsed,
AutomationFunctionRuns.withoutTablePrefix.col.results,
AutomationFunctionRuns.withoutTablePrefix.col.status,
AutomationFunctionRuns.withoutTablePrefix.col.statusMessage
] as Array<keyof AutomationFunctionRunRecord>)
}
export type InsertableAutomationRun = AutomationRunRecord & {
triggers: Omit<AutomationRunTriggerRecord, 'automationRunId'>[]
functionRuns: Omit<AutomationFunctionRunRecord, 'runId'>[]
}
export const upsertAutomationRunFactory =
(deps: { db: Knex }): UpsertAutomationRun =>
async (automationRun: InsertableAutomationRun) => {
await tables
.automationRuns(deps.db)
.insert(pick(automationRun, AutomationRuns.withoutTablePrefix.cols))
.onConflict(AutomationRuns.withoutTablePrefix.col.id)
.merge([
AutomationRuns.withoutTablePrefix.col.status,
AutomationRuns.withoutTablePrefix.col.updatedAt,
AutomationRuns.withoutTablePrefix.col.executionEngineRunId
] as Array<keyof AutomationRunRecord>)
await Promise.all([
tables
.automationRunTriggers(deps.db)
.insert(
automationRun.triggers.map((t) => ({
automationRunId: automationRun.id,
...pick(t, AutomationRunTriggers.withoutTablePrefix.cols)
}))
)
.onConflict()
.ignore(),
tables
.automationFunctionRuns(deps.db)
.insert(
automationRun.functionRuns.map((f) => ({
...pick(f, AutomationFunctionRuns.withoutTablePrefix.cols),
runId: automationRun.id
}))
)
.onConflict(AutomationFunctionRuns.withoutTablePrefix.col.id)
.merge(
AutomationFunctionRuns.withoutTablePrefix.cols as Array<
keyof AutomationFunctionRunRecord
>
)
])
return
}
export const getFunctionRunFactory =
(deps: { db: Knex }): GetFunctionRun =>
async (functionRunId: string) => {
const q = tables
.automationFunctionRuns(deps.db)
.select<
Array<
AutomationFunctionRunRecord & {
automationId: string
automationRevisionId: string
}
>
>([
...AutomationFunctionRuns.cols,
AutomationRuns.col.automationRevisionId,
AutomationRevisions.col.automationId
])
.where(AutomationFunctionRuns.col.id, functionRunId)
.innerJoin(
AutomationRuns.name,
AutomationRuns.col.id,
AutomationFunctionRuns.col.runId
)
.innerJoin(
AutomationRevisions.name,
AutomationRevisions.col.id,
AutomationRuns.col.automationRevisionId
)
const runs = await q
return (runs[0] ?? null) as (typeof runs)[0] | null
}
export const getFullAutomationRunByIdFactory =
(deps: { db: Knex }): GetFullAutomationRunById =>
async (automationRunId: string) => {
const run = await tables
.automationRuns(deps.db)
.select<
Array<{
runs: AutomationRunRecord[]
triggers: AutomationRunTriggerRecord[]
functionRuns: AutomationFunctionRunRecord[]
automationId: string
}>
>([
AutomationRuns.groupArray('runs'),
AutomationRunTriggers.groupArray('triggers'),
AutomationFunctionRuns.groupArray('functionRuns'),
knex.raw(`(array_agg(??))[1] as "automationId"`, [
AutomationRevisions.col.automationId
])
])
.where(AutomationRuns.col.id, automationRunId)
.innerJoin(
AutomationRevisions.name,
AutomationRevisions.col.id,
AutomationRuns.col.automationRevisionId
)
.innerJoin(
AutomationRunTriggers.name,
AutomationRunTriggers.col.automationRunId,
AutomationRuns.col.id
)
.innerJoin(
AutomationFunctionRuns.name,
AutomationFunctionRuns.col.runId,
AutomationRuns.col.id
)
.groupBy(AutomationRuns.col.id)
.first()
return run
? {
...formatJsonArrayRecords(run.runs)[0],
triggers: formatJsonArrayRecords(run.triggers),
functionRuns: formatJsonArrayRecords(run.functionRuns),
automationId: run.automationId
}
: null
}
export const storeAutomationFactory =
(deps: { db: Knex }): StoreAutomation =>
async (automation: AutomationRecord) => {
const [newAutomation] = await tables
.automations(deps.db)
.insert(pick(automation, Automations.withoutTablePrefix.cols))
.returning('*')
return newAutomation
}
export const markAutomationDeletedFactory =
(deps: { db: Knex }): MarkAutomationDeleted =>
async ({ automationId }) => {
await tables.automations(deps.db).where({ id: automationId }).update({
isDeleted: true
})
return true
}
export const storeAutomationTokenFactory =
(deps: { db: Knex }): StoreAutomationToken =>
async (automationToken: AutomationTokenRecord) => {
const [newToken] = await tables
.automationTokens(deps.db)
.insert(pick(automationToken, AutomationTokens.withoutTablePrefix.cols))
.returning('*')
return newToken
}
export type InsertableAutomationRevisionFunction = Omit<
AutomationRevisionFunctionRecord,
'automationRevisionId'
>
export type InsertableAutomationRevisionTrigger = Omit<
AutomationTriggerDefinitionRecord,
'automationRevisionId'
>
export type InsertableAutomationRevision = SetOptional<
AutomationRevisionRecord,
'createdAt' | 'id'
> & {
functions: InsertableAutomationRevisionFunction[]
triggers: InsertableAutomationRevisionTrigger[]
}
export const updateAutomationRevisionFactory =
(deps: { db: Knex }) =>
async (revision: SetRequired<Partial<AutomationRevisionRecord>, 'id'>) => {
const [ret] = await tables
.automationRevisions(deps.db)
.where(AutomationRevisions.col.id, revision.id)
.update(pick(revision, AutomationRevisions.withoutTablePrefix.cols))
.returning<AutomationRevisionRecord[]>('*')
return ret
}
export type StoredInsertableAutomationRevision = AutomationRevisionWithTriggersFunctions
export const storeAutomationRevisionFactory =
(deps: { db: Knex }): StoreAutomationRevision =>
async (revision: InsertableAutomationRevision) => {
const id = revision.id || generateRevisionId()
const rev = pick(revision, AutomationRevisions.withoutTablePrefix.cols)
const [newRev] = await tables
.automationRevisions(deps.db)
.insert({
...rev,
id
})
.returning('*')
const functions =
revision.functions.length > 0
? await tables
.automationRevisionFunctions(deps.db)
.insert(
revision.functions.map(
(f): AutomationRevisionFunctionRecord => ({
...f,
automationRevisionId: id
})
)
)
.returning('*')
: []
const triggers = await tables
.automationTriggers(deps.db)
.insert(
revision.triggers.map(
(t): AutomationTriggerDefinitionRecord => ({
...t,
automationRevisionId: id
})
)
)
.returning('*')
// Unset 'active in revision' for all other revisions
if (revision.active) {
await tables
.automationRevisions(deps.db)
.where(AutomationRevisions.col.automationId, newRev.automationId)
.andWhereNot(AutomationRevisions.col.id, newRev.id)
.update(AutomationRevisions.withoutTablePrefix.col.active, false)
}
return {
...newRev,
functions,
triggers
}
}
export const getAutomationTokenFactory =
(deps: { db: Knex }): GetAutomationToken =>
async (automationId: string): Promise<AutomationTokenRecord | null> => {
const token = await tables
.automationTokens(deps.db)
.where(AutomationTokens.col.automationId, automationId)
.first()
return token || null
}
export const getAutomationsFactory =
(deps: { db: Knex }): GetAutomations =>
async (params: { automationIds: string[]; projectId?: string }) => {
const { automationIds, projectId } = params
if (!automationIds.length) return []
const q = tables
.automations(deps.db)
.select()
.whereIn(Automations.col.id, automationIds)
.andWhere(Automations.col.isDeleted, false)
if (projectId?.length) {
q.andWhere(Automations.col.projectId, projectId)
}
return await q
}
export const getAutomationFactory =
(deps: { db: Knex }): GetAutomation =>
async (params: { automationId: string; projectId?: string }) => {
const { automationId, projectId } = params
return (
(
await getAutomationsFactory(deps)({ automationIds: [automationId], projectId })
)?.[0] || null
)
}
export const updateAutomationFactory =
(deps: { db: Knex }): UpdateAutomation =>
async (automation: SetRequired<Partial<AutomationRecord>, 'id'>) => {
const [ret] = await tables
.automations(deps.db)
.where(Automations.col.id, automation.id)
.update({
...pick(automation, Automations.withoutTablePrefix.cols),
[Automations.withoutTablePrefix.col.updatedAt]: new Date()
})
.returning('*')
return ret
}
export const getAutomationTriggerDefinitionsFactory =
(deps: { db: Knex }): GetAutomationTriggerDefinitions =>
async <T extends AutomationTriggerType = AutomationTriggerType>(params: {
automationId: string
projectId?: string
triggerType?: T
}) => {
const { automationId, projectId, triggerType } = params
const revisionQuery = tables
.automationRevisions(deps.db)
.select([AutomationRevisions.col.id])
.where(AutomationRevisions.col.automationId, automationId)
.andWhere(AutomationRevisions.col.active, true)
.innerJoin(
AutomationTriggers.name,
AutomationTriggers.col.automationRevisionId,
AutomationRevisions.col.id
)
.limit(1)
if (projectId) {
revisionQuery
.innerJoin(
Automations.name,
Automations.col.id,
AutomationRevisions.col.automationId
)
.andWhere(Automations.col.projectId, projectId)
}
const mainQ = tables
.automationTriggers(deps.db)
.select<AutomationTriggerDefinitionRecord<T>[]>('*')
.where(AutomationTriggers.col.automationRevisionId, revisionQuery)
if (triggerType) {
mainQ.andWhere(AutomationTriggers.col.triggerType, triggerType)
}
return (await mainQ).map((r) => ({
...r,
automationId
}))
}
export const updateAutomationRunFactory =
(deps: { db: Knex }): UpdateAutomationRun =>
async (run: SetRequired<Partial<AutomationRunRecord>, 'id'>) => {
const [ret] = await tables
.automationRuns(deps.db)
.where(AutomationRuns.col.id, run.id)
.update({
...pick(run, AutomationRuns.withoutTablePrefix.cols),
[AutomationRuns.withoutTablePrefix.col.updatedAt]: new Date()
})
.returning<AutomationRunRecord[]>('*')
return ret
}
export const getAutomationRevisionsFactory =
(deps: { db: Knex }): GetAutomationRevisions =>
async (params: { automationRevisionIds: string[] }) => {
const { automationRevisionIds } = params
if (!automationRevisionIds.length) return []
const q = tables
.automationRevisions(deps.db)
.whereIn(AutomationRevisions.col.id, automationRevisionIds)
.andWhere(AutomationRevisions.col.active, true)
return await q
}
export const getAutomationRevisionFactory =
(deps: { db: Knex }): GetAutomationRevision =>
async (params: { automationRevisionId: string }) => {
const { automationRevisionId } = params
const revisions = await getAutomationRevisionsFactory(deps)({
automationRevisionIds: [automationRevisionId]
})
return (revisions[0] || null) as Nullable<(typeof revisions)[0]>
}
export const getLatestAutomationRevisionsFactory =
(deps: { db: Knex }): GetLatestAutomationRevisions =>
async (params: { automationIds: string[] }) => {
const { automationIds } = params
if (!automationIds.length) return {}
const innerQ = tables
.automationRevisions(deps.db)
.select([
AutomationRevisions.col.automationId,
knex.raw('max(??) as ??', [AutomationRevisions.col.createdAt, 'maxCreatedAt'])
])
.whereIn(AutomationRevisions.col.automationId, automationIds)
.andWhere(AutomationRevisions.col.active, true)
.groupBy(AutomationRevisions.col.automationId)
const outerQ = tables
.automationRevisions(deps.db)
.select<AutomationRevisionRecord[]>('*')
.innerJoin(innerQ.as('q1'), function () {
this.on(AutomationRevisions.col.automationId, '=', 'q1.automationId')
this.andOn(AutomationRevisions.col.createdAt, '=', 'q1.maxCreatedAt')
})
const res = await outerQ
return keyBy(res, (r) => r.automationId)
}
export const getLatestAutomationRevisionFactory =
(deps: { db: Knex }): GetLatestAutomationRevision =>
async (params: { automationId: string }) => {
const { automationId } = params
const revisions = await getLatestAutomationRevisionsFactory(deps)({
automationIds: [automationId]
})
return (revisions[automationId] ?? null) as Nullable<(typeof revisions)[0]>
}
export const getRevisionsTriggerDefinitionsFactory =
(deps: { db: Knex }): GetRevisionsTriggerDefinitions =>
async (params: { automationRevisionIds: string[] }) => {
const { automationRevisionIds } = params
if (!automationRevisionIds.length) return {}
const q = tables
.automationTriggers(deps.db)
.whereIn(AutomationTriggers.col.automationRevisionId, automationRevisionIds)
return groupBy(await q, (r) => r.automationRevisionId)
}
export const getRevisionsFunctionsFactory =
(deps: { db: Knex }): GetRevisionsFunctions =>
async (params: { automationRevisionIds: string[] }) => {
const { automationRevisionIds } = params
if (!automationRevisionIds.length) return {}
const q = tables
.automationRevisionFunctions(deps.db)
.whereIn(
AutomationRevisionFunctions.col.automationRevisionId,
automationRevisionIds
)
return groupBy(await q, (r) => r.automationRevisionId)
}
type GetAutomationRunsArgs = AutomationRunsArgs & {
automationId: string
revisionId?: string
}
const getAutomationRunsTotalCountBaseQueryFactory =
(deps: { db: Knex }) =>
(params: { args: Pick<GetAutomationRunsArgs, 'automationId' | 'revisionId'> }) => {
const { args } = params
const q = tables
.automationRuns(deps.db)
.innerJoin(
AutomationRevisions.name,
AutomationRevisions.col.id,
AutomationRuns.col.automationRevisionId
)
.innerJoin(
Automations.name,
Automations.col.id,
AutomationRevisions.col.automationId
)
.where(AutomationRevisions.col.automationId, args.automationId)
.where(Automations.col.isDeleted, false)
if (args.revisionId?.length) {
q.andWhere(AutomationRuns.col.automationRevisionId, args.revisionId)
}
return q
}
export const getAutomationRunsTotalCountFactory =
(deps: { db: Knex }) => async (params: { args: GetAutomationRunsArgs }) => {
const q = getAutomationRunsTotalCountBaseQueryFactory(deps)(params).count<
[{ count: string }]
>(AutomationRuns.col.id)
const [ret] = await q
return parseInt(ret.count)
}
export const getAutomationRunsItemsFactory =
(deps: { db: Knex }) => async (params: { args: GetAutomationRunsArgs }) => {
const { args } = params
if (args.limit === 0) return { items: [], cursor: null }
const q = getAutomationRunsTotalCountBaseQueryFactory(deps)(params)
const limit = clamp(isNullOrUndefined(args.limit) ? 10 : args.limit, 0, 25)
const cursor = args.cursor ? decodeIsoDateCursor(args.cursor) : null
// Attach trigger & function runs
q.select([
knex.raw(`(array_agg(??))[1] as "projectId"`, [Automations.col.projectId]),
AutomationRuns.groupArray('runs'),
AutomationRunTriggers.groupArray('triggers'),
AutomationFunctionRuns.groupArray('functionRuns'),
knex.raw(`(array_agg(??))[1] as "automationId"`, [
AutomationRevisions.col.automationId
])
])
.innerJoin(
AutomationRunTriggers.name,
AutomationRunTriggers.col.automationRunId,
AutomationRuns.col.id
)
.innerJoin(
AutomationFunctionRuns.name,
AutomationFunctionRuns.col.runId,
AutomationRuns.col.id
)
.groupBy(AutomationRuns.col.id)
.orderBy([
{ column: AutomationRuns.col.updatedAt, order: 'desc' },
{ column: AutomationRuns.col.updatedAt, order: 'desc' }
])
.limit(limit)
if (cursor?.length) {
q.andWhere(AutomationRuns.col.updatedAt, '<', cursor)
}
const res = (await q) as Array<{
runs: AutomationRunRecord[]
triggers: AutomationRunTriggerRecord[]
functionRuns: AutomationFunctionRunRecord[]
automationId: string
projectId: string
}>
const items = res.map(
(r): AutomationRunWithTriggersFunctionRuns & { projectId: string } => ({
...formatJsonArrayRecords(r.runs)[0],
triggers: formatJsonArrayRecords(r.triggers),
functionRuns: formatJsonArrayRecords(r.functionRuns),
automationId: r.automationId,
projectId: r.projectId
})
)
return {
items,
cursor: items.length
? encodeIsoDateCursor(items[items.length - 1].updatedAt)
: null
}
}
export const queryAllAutomationFunctionRunsFactory =
(deps: { db: Knex }): QueryAllAutomationFunctionRuns =>
({ automationId }) => {
const automationFunctionRunsQuery = tables
.automationRevisions(deps.db)
.select<AutomationFunctionRunRecord[]>(...AutomationFunctionRuns.cols)
.where({ automationId })
.join<AutomationRunRecord>(
AutomationRuns.name,
AutomationRuns.col.automationRevisionId,
AutomationRevisions.col.id
)
.join<AutomationFunctionRunRecord>(
AutomationFunctionRuns.name,
AutomationFunctionRuns.col.runId,
AutomationRuns.col.id
)
return executeBatchedSelect(automationFunctionRunsQuery)
}
export type GetProjectAutomationsParams = {
projectId: string
args: ProjectAutomationsArgs
}
const getProjectAutomationsBaseQueryFactory =
(deps: { db: Knex }) => (params: GetProjectAutomationsParams) => {
const { projectId, args } = params
const q = tables
.automations(deps.db)
.where(Automations.col.projectId, projectId)
.andWhere({ isDeleted: false })
if (args.filter?.length) {
q.andWhere(Automations.col.name, 'ilike', `%${args.filter}%`)
}
return q
}
export const getProjectAutomationsTotalCountFactory =
(deps: { db: Knex }): GetProjectAutomationCount =>
async ({ projectId }) => {
const q = getProjectAutomationsBaseQueryFactory(deps)({
projectId,
args: {}
}).count<[{ count: string }]>(Automations.col.id)
const [ret] = await q
return parseInt(ret.count)
}
export const getProjectAutomationsItemsFactory =
(deps: { db: Knex }) => async (params: GetProjectAutomationsParams) => {
const { args } = params
if (args.limit === 0) return { items: [], cursor: null }
const cursor = args.cursor ? decodeCursor(args.cursor) : null
const q = getProjectAutomationsBaseQueryFactory(deps)(params)
.limit(clamp(isNullOrUndefined(args.limit) ? 10 : args.limit, 0, 25))
.orderBy(Automations.col.updatedAt, 'desc')
if (cursor?.length) {
q.andWhere(Automations.col.updatedAt, '<', cursor)
}
const res = await q
return {
items: res,
cursor: res.length ? encodeIsoDateCursor(res[res.length - 1].updatedAt) : null
}
}
export const getLatestVersionAutomationRunsFactory =
(deps: { db: Knex }): GetLatestVersionAutomationRuns =>
async (params, options) => {
const { projectId, modelId, versionId } = params
const { limit = 20 } = options || {}
const runsQ = tables
.automationRuns(deps.db)
.select<Array<AutomationRunRecord & { automationId: string }>>([
...AutomationRuns.cols,
AutomationRevisions.col.automationId
])
.innerJoin(
AutomationRevisions.name,
AutomationRevisions.col.id,
AutomationRuns.col.automationRevisionId
)
.innerJoin(
Automations.name,
Automations.col.id,
AutomationRevisions.col.automationId
)
.innerJoin(
AutomationRunTriggers.name,
AutomationRunTriggers.col.automationRunId,
AutomationRuns.col.id
)
.innerJoin(
BranchCommits.name,
BranchCommits.col.commitId,
AutomationRunTriggers.col.triggeringId
)
.where(AutomationRunTriggers.col.triggerType, VersionCreationTriggerType)
.andWhere(AutomationRunTriggers.col.triggeringId, versionId)
.andWhere(Automations.col.projectId, projectId)
.andWhere(BranchCommits.col.branchId, modelId)
.andWhere(Automations.col.isDeleted, false)
.distinctOn(AutomationRevisions.col.automationId)
.orderBy([
{ column: AutomationRevisions.col.automationId },
{ column: AutomationRuns.col.createdAt, order: 'desc' }
])
.limit(limit)
const mainQ = deps.db
.select<
Array<{
runs: Array<AutomationRunRecord & { automationId: string }>
functionRuns: AutomationFunctionRunRecord[]
triggers: AutomationRunTriggerRecord[]
}>
>([
// We will only have 1 run here, but we have to use an aggregation because of the grouping,
// so we just take the 1st array item later on
AutomationRuns.with({ withCustomTablePrefix: 'rq' }).groupArray('runs'),
AutomationFunctionRuns.groupArray('functionRuns'),
AutomationRunTriggers.groupArray('triggers')
])
.from(runsQ.as('rq'))
.innerJoin(AutomationFunctionRuns.name, AutomationFunctionRuns.col.runId, 'rq.id')
.innerJoin(
AutomationRunTriggers.name,
AutomationRunTriggers.col.automationRunId,
'rq.id'
)
.orderBy([{ column: 'rq.updatedAt', order: 'desc' }])
.groupBy('rq.id', 'rq.updatedAt')
const res = await mainQ
const formattedItems: AutomationRunWithTriggersFunctionRuns[] = res.map(
(r): AutomationRunWithTriggersFunctionRuns => ({
...formatJsonArrayRecords(r.runs)[0],
triggers: formatJsonArrayRecords(r.triggers),
functionRuns: formatJsonArrayRecords(r.functionRuns)
})
)
return formattedItems
}
export const getAutomationProjectsFactory =
(deps: { db: Knex }): GetAutomationProjects =>
async (params: { automationIds: string[]; userId?: string }) => {
const { automationIds, userId } = params
if (!automationIds.length) return {}
const q = tables
.automations(deps.db)
.select<Array<StreamRecord & { automationId: string; role?: StreamRoles }>>([
...Streams.cols,
Automations.colAs('id', 'automationId'),
...(userId
? [
// Getting first role from grouped results
knex.raw(`(array_agg("stream_acl"."role"))[1] as role`)
]
: [])
])
.whereIn(Automations.col.id, automationIds)
.innerJoin(Streams.name, Streams.col.id, Automations.col.projectId)
if (userId) {
q.leftJoin(StreamAcl.name, function () {
this.on(StreamAcl.col.resourceId, Streams.col.id).andOnVal(
StreamAcl.col.userId,
userId
)
}).groupBy(Automations.col.id, Streams.col.id)
}
const res = await q
return keyBy(res, (r) => r.automationId)
}
export const getAutomationProjectFactory =
(deps: { db: Knex }): GetAutomationProject =>
async (params: { automationId: string; userId?: string }) => {
const { automationId, userId } = params
const projects = await getAutomationProjectsFactory(deps)({
automationIds: [automationId],
userId
})
return (projects[automationId] || null) as Nullable<(typeof projects)[0]>
}
export const getAutomationRunWithTokenFactory =
(deps: { db: Knex }): GetAutomationRunWithToken =>
async (params: { automationRunId: string; automationId: string }) => {
const { automationRunId, automationId } = params
const q = tables
.automationRuns(deps.db)
.select<
Array<
AutomationRunRecord & {
automationId: string
token: string
executionEngineAutomationId: string
}
>
>([
...AutomationRuns.cols,
Automations.colAs('id', 'automationId'),
Automations.col.executionEngineAutomationId,
AutomationTokens.colAs('automateToken', 'token')
])
.where(AutomationRuns.col.id, automationRunId)
.andWhere(Automations.col.id, automationId)
.innerJoin(
AutomationRevisions.name,
AutomationRevisions.col.id,
AutomationRuns.col.automationRevisionId
)
.innerJoin(
Automations.name,
Automations.col.id,
AutomationRevisions.col.automationId
)
.innerJoin(
AutomationTokens.name,
AutomationTokens.col.automationId,
Automations.col.id
)
.first()
return await q
}
export const getAutomationRunsTriggersFactory =
(deps: { db: Knex }) => async (params: { automationRunIds: string[] }) => {
const { automationRunIds } = params
if (!automationRunIds.length) return {}
const q = tables
.automationRunTriggers(deps.db)
.whereIn(AutomationRunTriggers.col.automationRunId, automationRunIds)
const items = await q
return groupBy(items, (i) => i.automationRunId)
}
export const getAutomationRunFullTriggersFactory =
(deps: { db: Knex }): GetAutomationRunFullTriggers =>
async (params: { automationRunId: string }) => {
const { automationRunId } = params
const q = tables
.automationRunTriggers(deps.db)
.where(AutomationRunTriggers.col.automationRunId, automationRunId)
// Join on relevant entities
.leftJoin(Commits.name, function () {
this.on(Commits.col.id, AutomationRunTriggers.col.triggeringId).andOnVal(
AutomationRunTriggers.col.triggerType,
VersionCreationTriggerType
)
})
.innerJoin(BranchCommits.name, BranchCommits.col.commitId, Commits.col.id)
.innerJoin(Branches.name, Branches.col.id, BranchCommits.col.branchId)
.groupBy(
AutomationRunTriggers.col.automationRunId,
AutomationRunTriggers.col.triggerType,
AutomationRunTriggers.col.triggeringId
)
.select<AutomationRunFullTrigger[]>([
...AutomationRunTriggers.cols,
Commits.groupArray('versions'),
Branches.groupArray('models')
])
const res = await q
const formattedRes = res.map((r) => ({
...r,
versions: formatJsonArrayRecords(r.versions),
models: formatJsonArrayRecords(r.models)
}))
return {
[VersionCreationTriggerType]: formattedRes
.filter((r): r is AutomationRunFullTrigger<typeof VersionCreationTriggerType> =>
isVersionCreatedTrigger(r)
)
.map((r) => ({
triggerType: r.triggerType,
triggeringId: r.triggeringId,
version: r.versions[0],
model: r.models[0]
}))
}
}