bdf27f6218
* fix(fe2): better resiliency for when mp cant be loaded * WIP mixpanel track calls * more resiliency improvements * added all clientside tracking calls * run finished event * minor adjustment * feat(automate): revert automationRunTriggerinAssociation * feat(automate): track manual run triggers * feat(automate): backend track automation run created events * fix(automate): manual trigger type gql schema fix * feat(automate): add source based filter to run trigger tracking * fix(automate): fix trigger mock * various minor adjustments * remove comment --------- Co-authored-by: Gergő Jedlicska <gergo@jedlicska.com>
425 lines
14 KiB
TypeScript
425 lines
14 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
import {
|
|
AutomateRunStatus,
|
|
LimitedUser,
|
|
Resolvers
|
|
} from '@/modules/core/graph/generated/graphql'
|
|
import { isTestEnv } from '@/modules/shared/helpers/envHelper'
|
|
import { Automate, Roles, SourceAppNames, isNullOrUndefined } from '@speckle/shared'
|
|
import { times } from 'lodash'
|
|
import { IMockStore, IMocks } from '@graphql-tools/mock'
|
|
import dayjs from 'dayjs'
|
|
import { BranchCommits, Branches, Commits } from '@/modules/core/dbSchema'
|
|
import {
|
|
AutomationNotFoundError,
|
|
FunctionNotFoundError
|
|
} from '@/modules/automate/errors/management'
|
|
import { functionTemplateRepos } from '@/modules/automate/helpers/executionEngine'
|
|
import {
|
|
AutomationRevisionTriggerDefinitionGraphQLReturn,
|
|
AutomationRunTriggerGraphQLReturn
|
|
} from '@/modules/automate/helpers/graphTypes'
|
|
import { VersionCreationTriggerType } from '@/modules/automate/helpers/types'
|
|
|
|
const getRandomModelVersion = async (offset?: number) => {
|
|
const versionQ = Commits.knex()
|
|
.join(BranchCommits.name, BranchCommits.col.commitId, Commits.col.id)
|
|
.first()
|
|
if (offset) versionQ.offset(offset)
|
|
const version = await versionQ
|
|
|
|
if (!version) {
|
|
throw new Error("Couldn't find even one commit in the DB, please create some")
|
|
}
|
|
|
|
const model = await Branches.knex()
|
|
.join(BranchCommits.name, BranchCommits.col.branchId, Branches.col.id)
|
|
.where(BranchCommits.col.commitId, version.id)
|
|
.first()
|
|
|
|
if (!model) {
|
|
throw new Error(
|
|
`Couldn't find branch for first commit #${version.id}, please create one `
|
|
)
|
|
}
|
|
|
|
return {
|
|
model,
|
|
version
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Define mocking config in dev env
|
|
* https://www.apollographql.com/docs/apollo-server/v3/testing/mocking
|
|
*/
|
|
export async function buildMocksConfig(): Promise<{
|
|
mocks: boolean | IMocks
|
|
mockEntireSchema: boolean
|
|
resolvers?: Resolvers | ((store: IMockStore) => Resolvers)
|
|
}> {
|
|
return { mocks: false, mockEntireSchema: false }
|
|
|
|
// TODO: Disable before merging!
|
|
if (isTestEnv()) return { mocks: false, mockEntireSchema: false }
|
|
|
|
// const isDebugEnv = isDevEnv()
|
|
// if (!isDebugEnv) return { mocks: false, mockEntireSchema: false } // we def don't want this on in prod
|
|
|
|
// feel free to define mocks for your dev env below
|
|
const { faker } = await import('@faker-js/faker')
|
|
|
|
return {
|
|
resolvers: (store) => ({
|
|
AutomationRevisionTriggerDefinition: {
|
|
__resolveType: () => 'VersionCreatedTriggerDefinition'
|
|
},
|
|
AutomationRunTrigger: {
|
|
__resolveType: () => 'VersionCreatedTrigger'
|
|
},
|
|
VersionCreatedTriggerDefinition: {
|
|
model: store.get('Model') as any
|
|
},
|
|
VersionCreatedTrigger: {
|
|
model: store.get('Model') as any,
|
|
version: store.get('Version') as any
|
|
},
|
|
Query: {
|
|
automateFunctions: (_parent, args) => {
|
|
const forceZero = false
|
|
const count = forceZero ? 0 : faker.datatype.number({ min: 0, max: 20 })
|
|
|
|
const isFeatured = args.filter?.featuredFunctionsOnly
|
|
|
|
return {
|
|
cursor: null,
|
|
totalCount: count,
|
|
items: times(count, () => store.get('AutomateFunction', { isFeatured }))
|
|
} as any
|
|
},
|
|
automateFunction: (_parent, args) => {
|
|
const id = args.id
|
|
if (id === '404') {
|
|
throw new FunctionNotFoundError()
|
|
}
|
|
|
|
return store.get('AutomateFunction', { id }) as any
|
|
}
|
|
},
|
|
Project: {
|
|
automations: () => {
|
|
const forceAutomations = false
|
|
const forceNoAutomations = false
|
|
|
|
const limit = faker.datatype.number({ min: 0, max: 20 })
|
|
let count
|
|
if (forceNoAutomations) {
|
|
count = 0
|
|
} else {
|
|
count = forceAutomations ? limit : faker.datatype.boolean() ? limit : 0
|
|
}
|
|
|
|
return {
|
|
cursor: null,
|
|
totalCount: count,
|
|
items: times(count, () => store.get('Automation'))
|
|
} as any
|
|
},
|
|
automation: (_parent, args) => {
|
|
if (args.id === '404') {
|
|
throw new AutomationNotFoundError()
|
|
}
|
|
|
|
return store.get('Automation', { id: args.id }) as any
|
|
},
|
|
blob: () => {
|
|
return store.get('BlobMetadata') as any
|
|
}
|
|
},
|
|
Model: {
|
|
automationsStatus: async () => {
|
|
const random = faker.datatype.boolean()
|
|
return (random ? store.get('TriggeredAutomationsStatus') : null) as any
|
|
}
|
|
},
|
|
Version: {
|
|
automationsStatus: async () => {
|
|
const random = faker.datatype.boolean()
|
|
return (random ? store.get('TriggeredAutomationsStatus') : null) as any
|
|
}
|
|
},
|
|
Automation: {
|
|
creationPublicKeys: () => {
|
|
// Random sized array of string keys
|
|
return [...new Array(faker.datatype.number({ min: 0, max: 5 }))].map(() =>
|
|
faker.datatype.uuid()
|
|
)
|
|
},
|
|
runs: () => {
|
|
const forceZero = false
|
|
const count = forceZero ? 0 : faker.datatype.number({ min: 0, max: 20 })
|
|
|
|
return {
|
|
cursor: null,
|
|
totalCount: count,
|
|
items: times(count, () => store.get('AutomateRun'))
|
|
} as any
|
|
},
|
|
currentRevision: () => store.get('AutomationRevision') as any
|
|
},
|
|
AutomationRevision: {
|
|
triggerDefinitions: async (parent) => {
|
|
const rand = faker.datatype.number({ min: 0, max: 2 })
|
|
const res = (
|
|
await Promise.all([getRandomModelVersion(), getRandomModelVersion(1)])
|
|
).slice(0, rand)
|
|
|
|
return res.map(
|
|
(i): AutomationRevisionTriggerDefinitionGraphQLReturn => ({
|
|
triggerType: VersionCreationTriggerType,
|
|
triggeringId: i.model.id,
|
|
automationRevisionId: parent.id
|
|
})
|
|
)
|
|
},
|
|
functions: () => [store.get('AutomateFunction') as any]
|
|
},
|
|
AutomationRevisionFunction: {
|
|
parameters: () => ({}),
|
|
release: () => store.get('AutomateFunctionRelease') as any
|
|
},
|
|
AutomateRun: {
|
|
trigger: async (parent) => {
|
|
const { version } = await getRandomModelVersion()
|
|
|
|
return <AutomationRunTriggerGraphQLReturn>{
|
|
triggerType: VersionCreationTriggerType,
|
|
triggeringId: version.id,
|
|
automationRunId: parent.id
|
|
}
|
|
},
|
|
automation: () => store.get('Automation') as any,
|
|
status: () => faker.helpers.arrayElement(Object.values(AutomateRunStatus))
|
|
},
|
|
AutomateFunctionRun: {
|
|
function: () => store.get('AutomateFunction') as any,
|
|
status: () => faker.helpers.arrayElement(Object.values(AutomateRunStatus))
|
|
},
|
|
ProjectAutomationMutations: {
|
|
create: (_parent, args) => {
|
|
const {
|
|
input: { name, enabled }
|
|
} = args
|
|
const automation = store.get('Automation') as any
|
|
return {
|
|
...automation,
|
|
name,
|
|
enabled
|
|
}
|
|
},
|
|
update: (_parent, args) => {
|
|
const {
|
|
input: { id, name, enabled }
|
|
} = args
|
|
const automation = store.get('Automation') as any
|
|
return {
|
|
...automation,
|
|
id,
|
|
...(name?.length ? { name } : {}),
|
|
...(isNullOrUndefined(enabled) ? {} : { enabled })
|
|
}
|
|
},
|
|
trigger: () => faker.datatype.string(10),
|
|
createRevision: () => store.get('AutomationRevision') as any
|
|
},
|
|
UserAutomateInfo: {
|
|
hasAutomateGithubApp: () => {
|
|
return faker.datatype.boolean()
|
|
},
|
|
availableGithubOrgs: () => {
|
|
// Random string array
|
|
return [...new Array(faker.datatype.number({ min: 0, max: 5 }))].map(() =>
|
|
faker.company.companyName()
|
|
)
|
|
}
|
|
},
|
|
AutomateFunction: {
|
|
// creator: async (_parent, args, ctx) => {
|
|
// const rand = faker.datatype.boolean()
|
|
// const activeUser = ctx.userId
|
|
// ? await ctx.loaders.users.getUser.load(ctx.userId)
|
|
// : null
|
|
|
|
// return rand ? (store.get('LimitedUser') as any) : activeUser
|
|
// }
|
|
releases: () => store.get('AutomateFunctionReleaseCollection') as any,
|
|
automationCount: () => faker.datatype.number({ min: 0, max: 99 })
|
|
},
|
|
AutomateFunctionRelease: {
|
|
function: () => store.get('AutomateFunction') as any
|
|
},
|
|
TriggeredAutomationsStatus: {
|
|
status: () => faker.helpers.arrayElement(Object.values(AutomateRunStatus))
|
|
},
|
|
AutomateMutations: {
|
|
createFunction: () => store.get('AutomateFunction') as any,
|
|
updateFunction: (_parent, args) => {
|
|
const {
|
|
input: { id, name, description, supportedSourceApps, tags }
|
|
} = args
|
|
const func = store.get('AutomateFunction', { id }) as any
|
|
return {
|
|
...func,
|
|
id,
|
|
...(name?.length ? { name } : {}),
|
|
...(description?.length ? { description } : {}),
|
|
...(supportedSourceApps?.length ? { supportedSourceApps } : {}),
|
|
...(tags?.length ? { tags } : {})
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
mocks: {
|
|
BlobMetadata: () => ({
|
|
fileName: () => faker.system.fileName(),
|
|
fileType: () => faker.system.mimeType(),
|
|
fileSize: () => faker.datatype.number({ min: 1, max: 1000 })
|
|
}),
|
|
TriggeredAutomationsStatus: () => ({
|
|
automationRuns: () => [...new Array(faker.datatype.number({ min: 1, max: 5 }))]
|
|
}),
|
|
AutomationRevision: () => ({
|
|
functions: () => [undefined] // array of 1 always,
|
|
}),
|
|
Automation: () => ({
|
|
name: () => faker.company.companyName(),
|
|
enabled: () => faker.datatype.boolean()
|
|
}),
|
|
AutomateFunction: () => ({
|
|
name: () => faker.commerce.productName(),
|
|
isFeatured: () => faker.datatype.boolean(),
|
|
logo: () => {
|
|
const random = faker.datatype.boolean()
|
|
return random
|
|
? faker.image.imageUrl(undefined, undefined, undefined, true)
|
|
: null
|
|
},
|
|
repoUrl: () =>
|
|
'https://github.com/specklesystems/speckle-automate-code-compliance-window-safety',
|
|
automationCount: () => faker.datatype.number({ min: 0, max: 99 }),
|
|
description: () => {
|
|
// Example markdown description
|
|
return `# ${faker.commerce.productName()}\n${faker.lorem.paragraphs(
|
|
1,
|
|
'\n\n'
|
|
)}\n## Features \n- ${faker.lorem.sentence()}\n - ${faker.lorem.sentence()}\n - ${faker.lorem.sentence()}`
|
|
},
|
|
supportedSourceApps: () => {
|
|
const base = SourceAppNames
|
|
|
|
// Random assortment from base
|
|
return base.filter(faker.datatype.boolean)
|
|
},
|
|
tags: () => {
|
|
// Random string array
|
|
return [...new Array(faker.datatype.number({ min: 0, max: 5 }))].map(() =>
|
|
faker.lorem.word()
|
|
)
|
|
}
|
|
}),
|
|
AutomateFunctionRelease: () => ({
|
|
versionTag: () => {
|
|
// Fake semantic version
|
|
return `${faker.datatype.number({ min: 0, max: 9 })}.${faker.datatype.number({
|
|
min: 0,
|
|
max: 9
|
|
})}.${faker.datatype.number({ min: 0, max: 9 })}`
|
|
},
|
|
commitId: () => '0c259d384a4df3cce3f24667560e5124e68f202f',
|
|
inputSchema: () => {
|
|
// random fro 1 to 3
|
|
const rand = faker.datatype.number({ min: 1, max: 3 })
|
|
switch (rand) {
|
|
case 1:
|
|
return {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'https://example.com/product.schema.json',
|
|
title: 'Product',
|
|
description: "A product from Acme's catalog",
|
|
type: 'object',
|
|
properties: {
|
|
name: {
|
|
desciption: 'Random name',
|
|
type: 'string'
|
|
},
|
|
productId: {
|
|
description: 'The unique identifier for a product',
|
|
type: 'integer'
|
|
}
|
|
},
|
|
required: ['productId']
|
|
}
|
|
default:
|
|
return null
|
|
}
|
|
}
|
|
}),
|
|
AutomateRun: () => ({
|
|
reason: () => faker.lorem.sentence(),
|
|
id: () => faker.random.alphaNumeric(20),
|
|
createdAt: () =>
|
|
faker.date
|
|
.recent(undefined, dayjs().subtract(1, 'day').toDate())
|
|
.toISOString(),
|
|
updatedAt: () => faker.date.recent().toISOString(),
|
|
functionRuns: () => [...new Array(faker.datatype.number({ min: 1, max: 5 }))],
|
|
statusMessage: () => faker.lorem.sentence()
|
|
}),
|
|
AutomateFunctionRun: () => ({
|
|
contextView: () => `/`,
|
|
elapsed: () => faker.datatype.number({ min: 0, max: 600 }),
|
|
statusMessage: () => faker.lorem.sentence(),
|
|
results: (): Automate.AutomateTypes.ResultsSchema => {
|
|
return {
|
|
version: Automate.AutomateTypes.RESULTS_SCHEMA_VERSION,
|
|
values: {
|
|
objectResults: [],
|
|
blobIds: [...new Array(faker.datatype.number({ min: 0, max: 5 }))].map(
|
|
() => faker.datatype.uuid()
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
LimitedUser: () =>
|
|
({
|
|
id: faker.datatype.uuid(),
|
|
name: faker.name.findName(),
|
|
avatar: faker.image.avatar(),
|
|
bio: faker.lorem.sentence(),
|
|
company: faker.company.companyName(),
|
|
verified: faker.datatype.boolean(),
|
|
role: Roles.Server.User
|
|
} as LimitedUser),
|
|
JSONObject: () => ({}),
|
|
ID: () => faker.datatype.uuid(),
|
|
DateTime: () => faker.date.recent().toISOString(),
|
|
Model: () => ({
|
|
id: () => faker.datatype.uuid(),
|
|
name: () => faker.commerce.productName(),
|
|
previewUrl: () => faker.image.imageUrl()
|
|
}),
|
|
Version: () => ({
|
|
id: () => faker.random.alphaNumeric(10)
|
|
}),
|
|
ServerAutomateInfo: () => ({
|
|
availableFunctionTemplates: () => functionTemplateRepos
|
|
})
|
|
},
|
|
mockEntireSchema: false
|
|
}
|
|
}
|
|
|
|
export type AppMocksConfig = Awaited<ReturnType<typeof buildMocksConfig>>
|