From 16dfefd0bd6aea844bfeb217f59cd6073d1a86eb Mon Sep 17 00:00:00 2001 From: Charles Driesler Date: Tue, 28 May 2024 13:20:10 +0100 Subject: [PATCH] add `createTestAutomation` --- .../assets/automate/typedefs/automate.graphql | 10 +- .../automate/graph/resolvers/automate.ts | 15 +++ .../automate/services/automationManagement.ts | 105 ++++++++++++++++-- .../utils/automationConfigurationValidator.ts | 10 ++ .../modules/core/graph/generated/graphql.ts | 15 +++ .../graph/generated/graphql.ts | 12 ++ .../server/test/graphql/generated/graphql.ts | 12 ++ 7 files changed, 170 insertions(+), 9 deletions(-) create mode 100644 packages/server/modules/automate/utils/automationConfigurationValidator.ts diff --git a/packages/server/assets/automate/typedefs/automate.graphql b/packages/server/assets/automate/typedefs/automate.graphql index d68aa872e..5725a5d1b 100644 --- a/packages/server/assets/automate/typedefs/automate.graphql +++ b/packages/server/assets/automate/typedefs/automate.graphql @@ -194,6 +194,12 @@ input ProjectAutomationCreateInput { enabled: Boolean! } +input ProjectTestAutomationCreateInput { + name: String! + modelId: String! + functionId: String! +} + input AutomateFunctionsFilter { search: String """ @@ -287,8 +293,8 @@ type ProjectAutomationMutations { just refer to the last version of the model. """ trigger(automationId: ID!): Boolean! - createTestAutomation(input: ProjectAutomationCreateInput!): Automation! - createTestAutomationRun(automationId: ID!): string! + createTestAutomation(input: ProjectTestAutomationCreateInput!): Automation! + # createTestAutomationRun(automationId: ID!): String! } extend type ProjectMutations { diff --git a/packages/server/modules/automate/graph/resolvers/automate.ts b/packages/server/modules/automate/graph/resolvers/automate.ts index ada14f6cc..d929f1ad5 100644 --- a/packages/server/modules/automate/graph/resolvers/automate.ts +++ b/packages/server/modules/automate/graph/resolvers/automate.ts @@ -27,6 +27,7 @@ import { import { createAutomation, createAutomationRevision, + createTestAutomation, getAutomationsStatus, updateAutomation } from '@/modules/automate/services/automationManagement' @@ -472,6 +473,20 @@ export = { }) return true + }, + async createTestAutomation(parent, { input }, ctx) { + const create = createTestAutomation({ + getFunction, + storeAutomation, + storeAutomationRevision + }) + + return await create({ + input, + projectId: parent.projectId, + userId: ctx.userId!, + userResourceAccessRules: ctx.resourceAccessRules + }) } }, Query: { diff --git a/packages/server/modules/automate/services/automationManagement.ts b/packages/server/modules/automate/services/automationManagement.ts index 29d108fb1..729195f41 100644 --- a/packages/server/modules/automate/services/automationManagement.ts +++ b/packages/server/modules/automate/services/automationManagement.ts @@ -13,6 +13,7 @@ import { getServerOrigin } from '@/modules/shared/helpers/envHelper' import cryptoRandomString from 'crypto-random-string' import { createAutomation as clientCreateAutomation, + getFunction, getFunctionRelease, getFunctionReleases } from '@/modules/automate/clients/executionEngine' @@ -22,7 +23,8 @@ import { createStoredAuthCode } from '@/modules/automate/services/executionEngin import { ProjectAutomationCreateInput, ProjectAutomationRevisionCreateInput, - ProjectAutomationUpdateInput + ProjectAutomationUpdateInput, + ProjectTestAutomationCreateInput } from '@/modules/core/graph/generated/graphql' import { ContextResourceAccessRules } from '@/modules/core/helpers/token' import { @@ -47,6 +49,7 @@ import { import { LibsodiumEncryptionError } from '@/modules/shared/errors/encryption' import { validateInputAgainstFunctionSchema } from '@/modules/automate/utils/inputSchemaValidator' import { AutomationsEmitter } from '@/modules/automate/events/automations' +import { validateAutomationName } from '@/modules/automate/utils/automationConfigurationValidator' export type CreateAutomationDeps = { createAuthCode: ReturnType @@ -76,12 +79,7 @@ export const createAutomation = storeAutomationToken } = deps - const nameLength = name?.length || 0 - if (nameLength < 1 || nameLength > 255) { - throw new AutomationCreationError( - 'Automation name should be a string between the length of 1 and 255 characters.' - ) - } + validateAutomationName(name) await validateStreamAccess( userId, @@ -125,6 +123,99 @@ export const createAutomation = return { automation: automationRecord, token: automationTokenRecord } } +export type CreateTestAutomationDeps = { + getFunction: typeof getFunction + storeAutomation: typeof storeAutomation + storeAutomationRevision: typeof storeAutomationRevision +} + +/** Create a test automation and its first revision in one request. */ +export const createTestAutomation = + (deps: CreateTestAutomationDeps) => + async (params: { + input: ProjectTestAutomationCreateInput + projectId: string + userId: string + userResourceAccessRules?: ContextResourceAccessRules + }) => { + const { + input: { name, functionId, modelId }, + projectId, + userId, + userResourceAccessRules + } = params + const { getFunction, storeAutomation, storeAutomationRevision } = deps + + validateAutomationName(name) + + await validateStreamAccess( + userId, + projectId, + Roles.Stream.Owner, + userResourceAccessRules + ) + + // Get latest release for specified function + const { functionVersions: functionReleases } = await getFunction({ functionId }) + + if (!functionReleases || functionReleases.length === 0) { + // TODO: This should probably be okay for test automations + throw new AutomationCreationError( + 'The specified function does not have any releases' + ) + } + + const latestFunctionRelease = functionReleases[0] + + // Create and store the automation record + const automationId = cryptoRandomString({ length: 10 }) + + const automationRecord = await storeAutomation({ + id: automationId, + name, + userId, + createdAt: new Date(), + updatedAt: new Date(), + enabled: true, + projectId, + executionEngineAutomationId: null, + isTestAutomation: true + }) + + await AutomationsEmitter.emit(AutomationsEmitter.events.Created, { + automation: automationRecord + }) + + // Create and store the automation revision + const automationRevisionRecord = await storeAutomationRevision({ + functions: [ + { + functionId, + functionReleaseId: latestFunctionRelease.functionVersionId, + functionInputs: null + } + ], + triggers: [ + { + triggerType: VersionCreationTriggerType, + triggeringId: modelId + } + ], + automationId, + userId, + active: true, + // TODO: Should this be formally nullable? + publicKey: '' + }) + + await AutomationsEmitter.emit(AutomationsEmitter.events.CreatedRevision, { + automation: automationRecord, + revision: automationRevisionRecord + }) + + return automationRecord + } + export type UpdateAutomationDeps = { getAutomation: typeof getAutomation updateAutomation: typeof updateDbAutomation diff --git a/packages/server/modules/automate/utils/automationConfigurationValidator.ts b/packages/server/modules/automate/utils/automationConfigurationValidator.ts new file mode 100644 index 000000000..7936e133a --- /dev/null +++ b/packages/server/modules/automate/utils/automationConfigurationValidator.ts @@ -0,0 +1,10 @@ +import { AutomationCreationError } from '@/modules/automate/errors/management' + +export const validateAutomationName = (automationName: string): void => { + const nameLength = automationName?.length || 0 + if (nameLength < 1 || nameLength > 255) { + throw new AutomationCreationError( + 'Automation name should be a string between the length of 1 and 255 characters.' + ) + } +} diff --git a/packages/server/modules/core/graph/generated/graphql.ts b/packages/server/modules/core/graph/generated/graphql.ts index 4f737bb48..774e46acd 100644 --- a/packages/server/modules/core/graph/generated/graphql.ts +++ b/packages/server/modules/core/graph/generated/graphql.ts @@ -1795,6 +1795,7 @@ export type ProjectAutomationMutations = { __typename?: 'ProjectAutomationMutations'; create: Automation; createRevision: AutomationRevision; + createTestAutomation: Automation; /** * Trigger an automation with a fake "version created" trigger. The "version created" will * just refer to the last version of the model. @@ -1814,6 +1815,11 @@ export type ProjectAutomationMutationsCreateRevisionArgs = { }; +export type ProjectAutomationMutationsCreateTestAutomationArgs = { + input: ProjectTestAutomationCreateInput; +}; + + export type ProjectAutomationMutationsTriggerArgs = { automationId: Scalars['ID']; }; @@ -2100,6 +2106,12 @@ export enum ProjectPendingVersionsUpdatedMessageType { Updated = 'UPDATED' } +export type ProjectTestAutomationCreateInput = { + functionId: Scalars['String']; + modelId: Scalars['String']; + name: Scalars['String']; +}; + export type ProjectTriggeredAutomationsStatusUpdatedMessage = { __typename?: 'ProjectTriggeredAutomationsStatusUpdatedMessage'; model: Model; @@ -3604,6 +3616,7 @@ export type ResolversTypes = { ProjectPendingModelsUpdatedMessageType: ProjectPendingModelsUpdatedMessageType; ProjectPendingVersionsUpdatedMessage: ResolverTypeWrapper & { version: ResolversTypes['FileUpload'] }>; ProjectPendingVersionsUpdatedMessageType: ProjectPendingVersionsUpdatedMessageType; + ProjectTestAutomationCreateInput: ProjectTestAutomationCreateInput; ProjectTriggeredAutomationsStatusUpdatedMessage: ResolverTypeWrapper; ProjectTriggeredAutomationsStatusUpdatedMessageType: ProjectTriggeredAutomationsStatusUpdatedMessageType; ProjectUpdateInput: ProjectUpdateInput; @@ -3811,6 +3824,7 @@ export type ResolversParentTypes = { ProjectMutations: MutationsObjectGraphQLReturn; ProjectPendingModelsUpdatedMessage: Omit & { model: ResolversParentTypes['FileUpload'] }; ProjectPendingVersionsUpdatedMessage: Omit & { version: ResolversParentTypes['FileUpload'] }; + ProjectTestAutomationCreateInput: ProjectTestAutomationCreateInput; ProjectTriggeredAutomationsStatusUpdatedMessage: ProjectTriggeredAutomationsStatusUpdatedMessageGraphQLReturn; ProjectUpdateInput: ProjectUpdateInput; ProjectUpdateRoleInput: ProjectUpdateRoleInput; @@ -4579,6 +4593,7 @@ export type ProjectResolvers = { create?: Resolver>; createRevision?: Resolver>; + createTestAutomation?: Resolver>; trigger?: Resolver>; update?: Resolver>; __isTypeOf?: IsTypeOfResolverFn; diff --git a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts index 46cdd0eec..d007576e8 100644 --- a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts +++ b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts @@ -1784,6 +1784,7 @@ export type ProjectAutomationMutations = { __typename?: 'ProjectAutomationMutations'; create: Automation; createRevision: AutomationRevision; + createTestAutomation: Automation; /** * Trigger an automation with a fake "version created" trigger. The "version created" will * just refer to the last version of the model. @@ -1803,6 +1804,11 @@ export type ProjectAutomationMutationsCreateRevisionArgs = { }; +export type ProjectAutomationMutationsCreateTestAutomationArgs = { + input: ProjectTestAutomationCreateInput; +}; + + export type ProjectAutomationMutationsTriggerArgs = { automationId: Scalars['ID']; }; @@ -2089,6 +2095,12 @@ export enum ProjectPendingVersionsUpdatedMessageType { Updated = 'UPDATED' } +export type ProjectTestAutomationCreateInput = { + functionId: Scalars['String']; + modelId: Scalars['String']; + name: Scalars['String']; +}; + export type ProjectTriggeredAutomationsStatusUpdatedMessage = { __typename?: 'ProjectTriggeredAutomationsStatusUpdatedMessage'; model: Model; diff --git a/packages/server/test/graphql/generated/graphql.ts b/packages/server/test/graphql/generated/graphql.ts index 262530ce4..0945efc9d 100644 --- a/packages/server/test/graphql/generated/graphql.ts +++ b/packages/server/test/graphql/generated/graphql.ts @@ -1785,6 +1785,7 @@ export type ProjectAutomationMutations = { __typename?: 'ProjectAutomationMutations'; create: Automation; createRevision: AutomationRevision; + createTestAutomation: Automation; /** * Trigger an automation with a fake "version created" trigger. The "version created" will * just refer to the last version of the model. @@ -1804,6 +1805,11 @@ export type ProjectAutomationMutationsCreateRevisionArgs = { }; +export type ProjectAutomationMutationsCreateTestAutomationArgs = { + input: ProjectTestAutomationCreateInput; +}; + + export type ProjectAutomationMutationsTriggerArgs = { automationId: Scalars['ID']; }; @@ -2090,6 +2096,12 @@ export enum ProjectPendingVersionsUpdatedMessageType { Updated = 'UPDATED' } +export type ProjectTestAutomationCreateInput = { + functionId: Scalars['String']; + modelId: Scalars['String']; + name: Scalars['String']; +}; + export type ProjectTriggeredAutomationsStatusUpdatedMessage = { __typename?: 'ProjectTriggeredAutomationsStatusUpdatedMessage'; model: Model;