From bdf27f6218e8bea5839b73d49b4fc363e3e0a7ea Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Fri, 7 Jun 2024 10:21:24 +0300 Subject: [PATCH] feat: some mp analytics related to automate actions (#2299) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- .../automate/automation/CreateDialog.vue | 13 ++ .../automate/function/CreateDialog.vue | 7 ++ .../automation/FunctionSettingsDialog.vue | 13 +- .../project/page/automation/Header.vue | 12 +- .../project/page/automation/Runs.vue | 16 ++- .../lib/common/generated/gql/gql.ts | 4 +- .../lib/common/generated/gql/graphql.ts | 9 +- .../frontend-2/lib/core/composables/mp.ts | 11 +- .../composables/automationManagement.ts | 2 +- .../{mixpanel.global.ts => mp.global.ts} | 0 packages/frontend-2/plugins/mp.ts | 64 ++++++++-- .../assets/automate/typedefs/automate.graphql | 3 +- .../server/modules/automate/events/runs.ts | 8 +- .../automate/graph/resolvers/automate.ts | 5 +- .../automate/helpers/executionEngine.ts | 11 +- .../server/modules/automate/helpers/types.ts | 12 +- packages/server/modules/automate/index.ts | 12 +- .../automate/services/automationManagement.ts | 3 +- .../automate/services/runsManagement.ts | 18 +-- .../automate/services/subscriptions.ts | 4 +- .../modules/automate/services/tracking.ts | 116 ++++++++++++++++++ .../modules/automate/services/trigger.ts | 23 +++- .../modules/automate/tests/trigger.spec.ts | 10 +- .../modules/core/graph/generated/graphql.ts | 5 +- .../graph/generated/graphql.ts | 3 +- packages/server/modules/mocks.ts | 2 +- .../server/test/graphql/generated/graphql.ts | 3 +- packages/shared/src/core/helpers/error.ts | 9 ++ workspace.code-workspace | 2 +- 29 files changed, 318 insertions(+), 82 deletions(-) rename packages/frontend-2/middleware/{mixpanel.global.ts => mp.global.ts} (100%) create mode 100644 packages/server/modules/automate/services/tracking.ts diff --git a/packages/frontend-2/components/automate/automation/CreateDialog.vue b/packages/frontend-2/components/automate/automation/CreateDialog.vue index 91f30508a..ca4239bb9 100644 --- a/packages/frontend-2/components/automate/automation/CreateDialog.vue +++ b/packages/frontend-2/components/automate/automation/CreateDialog.vue @@ -109,6 +109,7 @@ import { useAutomationInputEncryptor, type AutomationInputEncryptor } from '~/lib/automate/composables/automations' +import { useMixpanel } from '~/lib/core/composables/mp' enum AutomationCreateSteps { SelectFunction, @@ -136,6 +137,8 @@ const props = defineProps<{ }>() const open = defineModel('open', { required: true }) +const mixpanel = useMixpanel() + const { handleSubmit: handleDetailsSubmit } = useForm() const stepsOrder = computed(() => [ @@ -443,6 +446,16 @@ const onDetailsSubmit = handleDetailsSubmit(async () => { return } + mixpanel.track('Automation Created', { + automationId: aId, + name, + projectId: project.id, + functionName: fn.name, + functionId: fn.id, + functionReleaseId: fnRelease.id, + modelId: model.id + }) + // Enable await updateAutomation( { diff --git a/packages/frontend-2/components/automate/function/CreateDialog.vue b/packages/frontend-2/components/automate/function/CreateDialog.vue index 5409ee087..8b967966c 100644 --- a/packages/frontend-2/components/automate/function/CreateDialog.vue +++ b/packages/frontend-2/components/automate/function/CreateDialog.vue @@ -57,6 +57,7 @@ import { useCreateAutomateFunction } from '~/lib/automate/composables/management import { useMutationLoading } from '@vue/apollo-composable' import type { AutomateFunctionCreateDialogDoneStep_AutomateFunctionFragment } from '~~/lib/common/generated/gql/graphql' import { automationFunctionRoute } from '~/lib/common/helpers/route' +import { useMixpanel } from '~/lib/core/composables/mp' enum FunctionCreateSteps { Authorize, @@ -74,6 +75,7 @@ const props = defineProps<{ }>() const open = defineModel('open', { required: true }) +const mixpanel = useMixpanel() const logger = useLogger() const mutationLoading = useMutationLoading() const createFunction = useCreateAutomateFunction() @@ -96,6 +98,11 @@ const onDetailsSubmit = handleDetailsSubmit(async (values) => { }) if (res?.id) { + mixpanel.track('Automate Function Created', { + functionId: res.id, + templateId: selectedTemplate.value.id, + name: values.name + }) createdFunction.value = res step.value++ } diff --git a/packages/frontend-2/components/project/page/automation/FunctionSettingsDialog.vue b/packages/frontend-2/components/project/page/automation/FunctionSettingsDialog.vue index fb7234584..2842ced30 100644 --- a/packages/frontend-2/components/project/page/automation/FunctionSettingsDialog.vue +++ b/packages/frontend-2/components/project/page/automation/FunctionSettingsDialog.vue @@ -93,6 +93,7 @@ import { useAutomationInputEncryptor, type AutomationInputEncryptor } from '~/lib/automate/composables/automations' +import { useMixpanel } from '~/lib/core/composables/mp' type AutomationRevisionFunction = ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunctionFragment @@ -145,6 +146,7 @@ const createNewAutomationRevision = useCreateAutomationRevision() const inputEncryption = useAutomationInputEncryptor({ ensureWhen: open }) const { triggerNotification } = useGlobalToast() const logger = useLogger() +const mixpanel = useMixpanel() const selectedModel = ref() const selectedRelease = ref() @@ -215,7 +217,7 @@ const onSave = async () => { }) // TODO: Apollo cache mutation afterwards - await createNewAutomationRevision({ + const res = await createNewAutomationRevision({ projectId: props.projectId, input: { automationId: props.automationId, @@ -237,6 +239,15 @@ const onSave = async () => { } } }) + if (res?.id) { + mixpanel.track('Automation Revision Created', { + automationId: props.automationId, + projectId: props.projectId, + functionId: fId, + functionReleaseId: rId, + modelId: model.id + }) + } } finally { automationEncrypt?.dispose() loading.value = false diff --git a/packages/frontend-2/components/project/page/automation/Header.vue b/packages/frontend-2/components/project/page/automation/Header.vue index a89df9d54..5fbc1db02 100644 --- a/packages/frontend-2/components/project/page/automation/Header.vue +++ b/packages/frontend-2/components/project/page/automation/Header.vue @@ -44,6 +44,7 @@ import type { ProjectPageAutomationHeader_ProjectFragment } from '~/lib/common/generated/gql/graphql' import { projectRoute } from '~/lib/common/helpers/route' +import { useMixpanel } from '~/lib/core/composables/mp' import { useUpdateAutomation } from '~/lib/projects/composables/automationManagement' graphql(` @@ -80,6 +81,7 @@ const props = defineProps<{ const switchId = useId() const loading = useMutationLoading() const updateAutomation = useUpdateAutomation() +const mixpanel = useMixpanel() const automationsLink = computed(() => projectRoute(props.project.id, 'automations')) const name = computed({ @@ -122,7 +124,7 @@ const enabled = computed({ enabled: newVal } } - await updateAutomation(args, { + const res = await updateAutomation(args, { optimisticResponse: { projectMutations: { automationMutations: { @@ -139,6 +141,14 @@ const enabled = computed({ failure: `Failed to ${args.input.enabled ? 'enable' : 'disable'} automation` } }) + if (res?.id) { + mixpanel.track('Automation Enabled/Disabled', { + automationId: res.id, + automationName: res.name, + projectId: props.project.id, + enabled: res.enabled + }) + } } }) diff --git a/packages/frontend-2/components/project/page/automation/Runs.vue b/packages/frontend-2/components/project/page/automation/Runs.vue index 8237680c6..f4630eb79 100644 --- a/packages/frontend-2/components/project/page/automation/Runs.vue +++ b/packages/frontend-2/components/project/page/automation/Runs.vue @@ -24,6 +24,7 @@ import { ArrowPathIcon } from '@heroicons/vue/24/outline' import { usePaginatedQuery } from '~/lib/common/composables/graphql' import { graphql } from '~/lib/common/generated/gql' import type { ProjectPageAutomationRuns_AutomationFragment } from '~/lib/common/generated/gql/graphql' +import { useMixpanel } from '~/lib/core/composables/mp' import { useTriggerAutomation } from '~/lib/projects/composables/automationManagement' import { projectAutomationPagePaginatedRunsQuery } from '~/lib/projects/graphql/queries' @@ -32,6 +33,7 @@ import { projectAutomationPagePaginatedRunsQuery } from '~/lib/projects/graphql/ graphql(` fragment ProjectPageAutomationRuns_Automation on Automation { id + name enabled isTestAutomation runs(limit: 10) { @@ -65,8 +67,18 @@ const { identifier, onInfiniteLoad } = usePaginatedQuery({ resolveCursorFromVariables: (vars) => vars.cursor }) const triggerAutomation = useTriggerAutomation() +const mixpanel = useMixpanel() -const onTrigger = () => { - triggerAutomation(props.projectId, props.automation.id) +const onTrigger = async () => { + const res = await triggerAutomation(props.projectId, props.automation.id) + if (res) { + mixpanel.track('Automation Run Triggered', { + automationId: props.automation.id, + automationName: props.automation.name, + automationRunId: res, + projectId: props.projectId, + source: 'manual' + }) + } } diff --git a/packages/frontend-2/lib/common/generated/gql/gql.ts b/packages/frontend-2/lib/common/generated/gql/gql.ts index d905fc63e..9ce537f9f 100644 --- a/packages/frontend-2/lib/common/generated/gql/gql.ts +++ b/packages/frontend-2/lib/common/generated/gql/gql.ts @@ -56,7 +56,7 @@ const documents = { "\n fragment ProjectPageAutomationFunctions_Automation on Automation {\n id\n currentRevision {\n id\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevision\n functions {\n release {\n id\n function {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n releases(limit: 1) {\n items {\n id\n }\n }\n }\n }\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction\n }\n }\n }\n": types.ProjectPageAutomationFunctions_AutomationFragmentDoc, "\n fragment ProjectPageAutomationHeader_Automation on Automation {\n id\n name\n enabled\n isTestAutomation\n currentRevision {\n id\n triggerDefinitions {\n ... on VersionCreatedTriggerDefinition {\n model {\n ...ProjectPageLatestItemsModelItem\n }\n }\n }\n }\n }\n": types.ProjectPageAutomationHeader_AutomationFragmentDoc, "\n fragment ProjectPageAutomationHeader_Project on Project {\n id\n ...ProjectPageModelsCardProject\n }\n": types.ProjectPageAutomationHeader_ProjectFragmentDoc, - "\n fragment ProjectPageAutomationRuns_Automation on Automation {\n id\n enabled\n isTestAutomation\n runs(limit: 10) {\n items {\n ...AutomationRunDetails\n }\n totalCount\n cursor\n }\n }\n": types.ProjectPageAutomationRuns_AutomationFragmentDoc, + "\n fragment ProjectPageAutomationRuns_Automation on Automation {\n id\n name\n enabled\n isTestAutomation\n runs(limit: 10) {\n items {\n ...AutomationRunDetails\n }\n totalCount\n cursor\n }\n }\n": types.ProjectPageAutomationRuns_AutomationFragmentDoc, "\n fragment ProjectPageAutomationsEmptyState_Query on Query {\n automateFunctions(limit: 9, filter: { featuredFunctionsOnly: true }) {\n items {\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n": types.ProjectPageAutomationsEmptyState_QueryFragmentDoc, "\n fragment ProjectPageAutomationsRow_Automation on Automation {\n id\n name\n enabled\n isTestAutomation\n currentRevision {\n id\n triggerDefinitions {\n ... on VersionCreatedTriggerDefinition {\n model {\n id\n name\n }\n }\n }\n }\n runs(limit: 10) {\n totalCount\n items {\n ...AutomationRunDetails\n }\n cursor\n }\n }\n": types.ProjectPageAutomationsRow_AutomationFragmentDoc, "\n fragment ProjectDiscussionsPageHeader_Project on Project {\n id\n name\n }\n": types.ProjectDiscussionsPageHeader_ProjectFragmentDoc, @@ -429,7 +429,7 @@ export function graphql(source: "\n fragment ProjectPageAutomationHeader_Projec /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment ProjectPageAutomationRuns_Automation on Automation {\n id\n enabled\n isTestAutomation\n runs(limit: 10) {\n items {\n ...AutomationRunDetails\n }\n totalCount\n cursor\n }\n }\n"): (typeof documents)["\n fragment ProjectPageAutomationRuns_Automation on Automation {\n id\n enabled\n isTestAutomation\n runs(limit: 10) {\n items {\n ...AutomationRunDetails\n }\n totalCount\n cursor\n }\n }\n"]; +export function graphql(source: "\n fragment ProjectPageAutomationRuns_Automation on Automation {\n id\n name\n enabled\n isTestAutomation\n runs(limit: 10) {\n items {\n ...AutomationRunDetails\n }\n totalCount\n cursor\n }\n }\n"): (typeof documents)["\n fragment ProjectPageAutomationRuns_Automation on Automation {\n id\n name\n enabled\n isTestAutomation\n runs(limit: 10) {\n items {\n ...AutomationRunDetails\n }\n totalCount\n cursor\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/packages/frontend-2/lib/common/generated/gql/graphql.ts b/packages/frontend-2/lib/common/generated/gql/graphql.ts index 66e015c3f..b69430275 100644 --- a/packages/frontend-2/lib/common/generated/gql/graphql.ts +++ b/packages/frontend-2/lib/common/generated/gql/graphql.ts @@ -335,7 +335,6 @@ export enum AutomateRunStatus { } export enum AutomateRunTriggerType { - TestType = 'TEST_TYPE', VersionCreated = 'VERSION_CREATED' } @@ -1831,7 +1830,7 @@ export type ProjectAutomationMutations = { * Trigger an automation with a fake "version created" trigger. The "version created" will * just refer to the last version of the model. */ - trigger: Scalars['Boolean']; + trigger: Scalars['String']; update: Automation; }; @@ -3595,7 +3594,7 @@ export type ProjectPageAutomationHeader_AutomationFragment = { __typename?: 'Aut export type ProjectPageAutomationHeader_ProjectFragment = { __typename?: 'Project', id: string, role?: string | null, visibility: ProjectVisibility }; -export type ProjectPageAutomationRuns_AutomationFragment = { __typename?: 'Automation', id: string, enabled: boolean, isTestAutomation: boolean, runs: { __typename?: 'AutomateRunCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'AutomateRun', id: string, status: AutomateRunStatus, createdAt: string, updatedAt: string, functionRuns: Array<{ __typename?: 'AutomateFunctionRun', statusMessage?: string | null, id: string, status: AutomateRunStatus }>, trigger: { __typename?: 'VersionCreatedTrigger', version?: { __typename?: 'Version', id: string } | null, model?: { __typename?: 'Model', id: string } | null } }> } }; +export type ProjectPageAutomationRuns_AutomationFragment = { __typename?: 'Automation', id: string, name: string, enabled: boolean, isTestAutomation: boolean, runs: { __typename?: 'AutomateRunCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'AutomateRun', id: string, status: AutomateRunStatus, createdAt: string, updatedAt: string, functionRuns: Array<{ __typename?: 'AutomateFunctionRun', statusMessage?: string | null, id: string, status: AutomateRunStatus }>, trigger: { __typename?: 'VersionCreatedTrigger', version?: { __typename?: 'Version', id: string } | null, model?: { __typename?: 'Model', id: string } | null } }> } }; export type ProjectPageAutomationsEmptyState_QueryFragment = { __typename?: 'Query', automateFunctions: { __typename?: 'AutomateFunctionCollection', items: Array<{ __typename?: 'AutomateFunction', id: string, name: string, isFeatured: boolean, description: string, logo?: string | null, repo: { __typename?: 'BasicGitRepositoryMetadata', id: string, url: string, owner: string, name: string }, releases: { __typename?: 'AutomateFunctionReleaseCollection', items: Array<{ __typename?: 'AutomateFunctionRelease', id: string, inputSchema?: {} | null }> } }> } }; @@ -4092,7 +4091,7 @@ export type TriggerAutomationMutationVariables = Exact<{ }>; -export type TriggerAutomationMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', automationMutations: { __typename?: 'ProjectAutomationMutations', trigger: boolean } } }; +export type TriggerAutomationMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', automationMutations: { __typename?: 'ProjectAutomationMutations', trigger: string } } }; export type CreateTestAutomationMutationVariables = Exact<{ projectId: Scalars['ID']; @@ -4718,7 +4717,7 @@ export const CommonModelSelectorModelFragmentDoc = {"kind":"Document","definitio export const ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageAutomationFunctionSettingsDialog_AutomationRevision"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomationRevision"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"triggerDefinitions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"VersionCreatedTriggerDefinition"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"model"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CommonModelSelectorModel"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunctionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomationRevisionFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"parameters"}},{"kind":"Field","name":{"kind":"Name","value":"release"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"inputSchema"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; export const ProjectPageAutomationFunctions_AutomationFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageAutomationFunctions_Automation"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Automation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"currentRevision"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageAutomationFunctionSettingsDialog_AutomationRevision"}},{"kind":"Field","name":{"kind":"Name","value":"functions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"release"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsFunctionsCard_AutomateFunction"}},{"kind":"Field","name":{"kind":"Name","value":"releases"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction"}}]}}]}}]}}]} as unknown as DocumentNode; -export const ProjectPageAutomationRuns_AutomationFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageAutomationRuns_Automation"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Automation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"isTestAutomation"}},{"kind":"Field","name":{"kind":"Name","value":"runs"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationRunDetails"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}}]}}]} as unknown as DocumentNode; +export const ProjectPageAutomationRuns_AutomationFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageAutomationRuns_Automation"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Automation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"isTestAutomation"}},{"kind":"Field","name":{"kind":"Name","value":"runs"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationRunDetails"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}}]}}]} as unknown as DocumentNode; export const ProjectPageAutomationPage_AutomationFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageAutomationPage_Automation"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Automation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageAutomationHeader_Automation"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageAutomationFunctions_Automation"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageAutomationRuns_Automation"}}]}}]} as unknown as DocumentNode; export const ProjectPageAutomationHeader_ProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageAutomationHeader_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardProject"}}]}}]} as unknown as DocumentNode; export const ProjectPageAutomationPage_ProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageAutomationPage_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageAutomationHeader_Project"}}]}}]} as unknown as DocumentNode; diff --git a/packages/frontend-2/lib/core/composables/mp.ts b/packages/frontend-2/lib/core/composables/mp.ts index 8f18a2ab0..6b71caa88 100644 --- a/packages/frontend-2/lib/core/composables/mp.ts +++ b/packages/frontend-2/lib/core/composables/mp.ts @@ -1,4 +1,3 @@ -import type { OverridedMixpanel } from 'mixpanel-browser' import { useOnAuthStateChange } from '~/lib/auth/composables/auth' import { useActiveUser } from '~~/lib/auth/composables/activeUser' import { md5 } from '~/lib/common/helpers/encodeDecode' @@ -17,16 +16,10 @@ function getMixpanelServerId(): string { /** * Get Mixpanel instance * Note: Mixpanel is not available during SSR because mixpanel-browser only works in the browser! - * If this composable is invoked during SSR it will return undefined! */ -export function useMixpanel(): OverridedMixpanel { - // we're making TS lie here cause we don't want to constantly check if the return of this - // is undefined - if (process.server) return undefined as unknown as OverridedMixpanel - +export function useMixpanel() { const nuxt = useNuxtApp() - const $mixpanel = nuxt.$mixpanel as () => OverridedMixpanel - + const $mixpanel = nuxt.$mixpanel return $mixpanel() } diff --git a/packages/frontend-2/lib/projects/composables/automationManagement.ts b/packages/frontend-2/lib/projects/composables/automationManagement.ts index 98392bbdb..050581640 100644 --- a/packages/frontend-2/lib/projects/composables/automationManagement.ts +++ b/packages/frontend-2/lib/projects/composables/automationManagement.ts @@ -193,7 +193,7 @@ export const useTriggerAutomation = () => { }) } - return !!res?.data?.projectMutations?.automationMutations?.trigger + return res?.data?.projectMutations?.automationMutations?.trigger } } diff --git a/packages/frontend-2/middleware/mixpanel.global.ts b/packages/frontend-2/middleware/mp.global.ts similarity index 100% rename from packages/frontend-2/middleware/mixpanel.global.ts rename to packages/frontend-2/middleware/mp.global.ts diff --git a/packages/frontend-2/plugins/mp.ts b/packages/frontend-2/plugins/mp.ts index b30a91ccb..0a153173b 100644 --- a/packages/frontend-2/plugins/mp.ts +++ b/packages/frontend-2/plugins/mp.ts @@ -1,24 +1,67 @@ +/* eslint-disable camelcase */ +import { LogicError } from '@speckle/ui-components' +import type { OverridedMixpanel } from 'mixpanel-browser' +import type { Merge } from 'type-fest' + /** * mixpanel-browser only supports being ran on the client-side (hence the name)! So it's only going to be accessible * in client-side execution branches */ +type LimitedMixpanel = Merge< + Pick< + OverridedMixpanel, + 'track' | 'init' | 'reset' | 'register' | 'identify' | 'people' | 'add_group' + >, + { + people: Pick + } +> + +const fakeLimitedMixpanel = (): LimitedMixpanel => ({ + init: noop as LimitedMixpanel['init'], + track: noop, + reset: noop, + register: noop, + identify: noop, + people: { + set: noop, + set_once: noop + }, + add_group: noop +}) + export default defineNuxtPlugin(async () => { const { public: { mixpanelApiHost, mixpanelTokenId, logCsrEmitProps } } = useRuntimeConfig() + const logger = useLogger() - const mixpanel = process.client - ? (await import('mixpanel-browser')).default - : undefined - if (!mixpanel) { - return { - provide: { - mixpanel: () => { - throw new Error('Mixpanel is only available on the client-side!') + let mixpanel: LimitedMixpanel | undefined = undefined + + try { + mixpanel = process.client ? (await import('mixpanel-browser')).default : undefined + if (process.server) { + mixpanel = { + ...fakeLimitedMixpanel(), + track: () => { + throw new Error('mixpanel is not available on the server-side') + }, + identify: () => { + throw new Error('mixpanel is not available on the server-side') + }, + register: () => { + throw new Error('mixpanel is not available on the server-side') } } } + } catch (e) { + logger.warn(e, 'Failed to load mixpanel') + } + + if (!mixpanel) { + // Implement mocked version + mixpanel = fakeLimitedMixpanel() } // Init @@ -30,7 +73,10 @@ export default defineNuxtPlugin(async () => { return { provide: { - mixpanel: () => mixpanel + mixpanel: () => { + if (!mixpanel) throw new LogicError('Mixpanel unexpectedly not defined') + return mixpanel + } } } }) diff --git a/packages/server/assets/automate/typedefs/automate.graphql b/packages/server/assets/automate/typedefs/automate.graphql index ee225a4fe..7309ffa2c 100644 --- a/packages/server/assets/automate/typedefs/automate.graphql +++ b/packages/server/assets/automate/typedefs/automate.graphql @@ -11,7 +11,6 @@ enum AutomateRunStatus { enum AutomateRunTriggerType { VERSION_CREATED - TEST_TYPE } type AutomationRevisionFunction { @@ -296,7 +295,7 @@ type ProjectAutomationMutations { Trigger an automation with a fake "version created" trigger. The "version created" will just refer to the last version of the model. """ - trigger(automationId: ID!): Boolean! + trigger(automationId: ID!): String! createTestAutomation(input: ProjectTestAutomationCreateInput!): Automation! createTestAutomationRun(automationId: ID!): TestAutomationRun! } diff --git a/packages/server/modules/automate/events/runs.ts b/packages/server/modules/automate/events/runs.ts index 78e5e66df..43ce08e2f 100644 --- a/packages/server/modules/automate/events/runs.ts +++ b/packages/server/modules/automate/events/runs.ts @@ -1,8 +1,10 @@ import { AutomationFunctionRunRecord, AutomationRunRecord, + AutomationTriggerType, AutomationWithRevision, - BaseTriggerManifest + BaseTriggerManifest, + RunTriggerSource } from '@/modules/automate/helpers/types' import { InsertableAutomationRun } from '@/modules/automate/repositories/automations' import { initializeModuleEventEmitter } from '@/modules/shared/services/moduleEventEmitterSetup' @@ -17,10 +19,12 @@ export type AutomateEventsPayloads = { automation: AutomationWithRevision run: InsertableAutomationRun manifests: BaseTriggerManifest[] + source: RunTriggerSource + triggerType: AutomationTriggerType } [AutomateRunsEvents.StatusUpdated]: { run: AutomationRunRecord - functionRuns: AutomationFunctionRunRecord[] + functionRun: AutomationFunctionRunRecord automationId: string } } diff --git a/packages/server/modules/automate/graph/resolvers/automate.ts b/packages/server/modules/automate/graph/resolvers/automate.ts index 777b33916..b9c7a6c3c 100644 --- a/packages/server/modules/automate/graph/resolvers/automate.ts +++ b/packages/server/modules/automate/graph/resolvers/automate.ts @@ -506,14 +506,14 @@ export = (FF_AUTOMATE_MODULE_ENABLED }) }) - await trigger({ + const { automationRunId } = await trigger({ automationId, userId: ctx.userId!, userResourceAccessRules: ctx.resourceAccessRules, projectId: parent.projectId }) - return true + return automationRunId }, async createTestAutomation(parent, { input }, ctx) { const create = createTestAutomation({ @@ -596,6 +596,7 @@ export = (FF_AUTOMATE_MODULE_ENABLED items: [] } } + throw e } } diff --git a/packages/server/modules/automate/helpers/executionEngine.ts b/packages/server/modules/automate/helpers/executionEngine.ts index 0d9f21869..edaa13e40 100644 --- a/packages/server/modules/automate/helpers/executionEngine.ts +++ b/packages/server/modules/automate/helpers/executionEngine.ts @@ -1,7 +1,4 @@ -import { - TestTriggerType, - VersionCreationTriggerType -} from '@/modules/automate/helpers/types' +import { VersionCreationTriggerType } from '@/modules/automate/helpers/types' import { AutomateFunctionTemplateLanguage, AutomateRunTriggerType @@ -50,11 +47,9 @@ export const functionTemplateRepos = [ ] export const dbToGraphqlTriggerTypeMap = { - [VersionCreationTriggerType]: AutomateRunTriggerType.VersionCreated, - [TestTriggerType]: AutomateRunTriggerType.TestType + [VersionCreationTriggerType]: AutomateRunTriggerType.VersionCreated } export const graphqlToDbTriggerTypeMap = { - [AutomateRunTriggerType.VersionCreated]: VersionCreationTriggerType, - [AutomateRunTriggerType.TestType]: TestTriggerType + [AutomateRunTriggerType.VersionCreated]: VersionCreationTriggerType } diff --git a/packages/server/modules/automate/helpers/types.ts b/packages/server/modules/automate/helpers/types.ts index c165df55a..3c8b82d12 100644 --- a/packages/server/modules/automate/helpers/types.ts +++ b/packages/server/modules/automate/helpers/types.ts @@ -46,7 +46,7 @@ export type AutomationRunStatus = | 'timeout' | 'canceled' -export const AutomationRunStatuses: Record = { +export const AutomationRunStatuses: { [key in AutomationRunStatus]: key } = { pending: 'pending', initializing: 'initializing', running: 'running', @@ -73,11 +73,13 @@ export type AutomateRevisionFunctionRecord = { automationRevisionId: string } +export enum RunTriggerSource { + Automatic = 'automatic', + Manual = 'manual' +} + export const VersionCreationTriggerType = 'versionCreation' -export const TestTriggerType = 'testtttt' -export type AutomationTriggerType = - | typeof VersionCreationTriggerType - | typeof TestTriggerType +export type AutomationTriggerType = typeof VersionCreationTriggerType export type AutomationTriggerRecordBase< T extends AutomationTriggerType = AutomationTriggerType diff --git a/packages/server/modules/automate/index.ts b/packages/server/modules/automate/index.ts index aa37a0222..2e3399f14 100644 --- a/packages/server/modules/automate/index.ts +++ b/packages/server/modules/automate/index.ts @@ -8,9 +8,10 @@ import { import { Environment } from '@speckle/shared' import { getActiveTriggerDefinitions, + getAutomationRunFullTriggers, + getFullAutomationRevisionMetadata, getAutomation, - getAutomationRevision, - getAutomationRunFullTriggers + getAutomationRevision } from '@/modules/automate/repositories/automations' import { ScopeRecord } from '@/modules/auth/helpers/types' import { Scopes } from '@speckle/shared' @@ -26,6 +27,7 @@ import { setupAutomationUpdateSubscriptions, setupStatusUpdateSubscriptions } from '@/modules/automate/services/subscriptions' +import { setupRunFinishedTracking } from '@/modules/automate/services/tracking' import authGithubAppRest from '@/modules/automate/rest/authGithubApp' const { FF_AUTOMATE_MODULE_ENABLED } = Environment.getFeatureFlags() @@ -67,6 +69,9 @@ const initializeEventListeners = () => { getAutomationRunFullTriggers }) const setupAutomationUpdateSubscriptionsInvoke = setupAutomationUpdateSubscriptions() + const setupRunFinishedTrackingInvoke = setupRunFinishedTracking({ + getFullAutomationRevisionMetadata + }) const quitters = [ VersionsEmitter.listen( @@ -81,7 +86,8 @@ const initializeEventListeners = () => { } ), setupStatusUpdateSubscriptionsInvoke(), - setupAutomationUpdateSubscriptionsInvoke() + setupAutomationUpdateSubscriptionsInvoke(), + setupRunFinishedTrackingInvoke() ] return () => { diff --git a/packages/server/modules/automate/services/automationManagement.ts b/packages/server/modules/automate/services/automationManagement.ts index 887587797..a2277d3f0 100644 --- a/packages/server/modules/automate/services/automationManagement.ts +++ b/packages/server/modules/automate/services/automationManagement.ts @@ -35,6 +35,7 @@ import { JsonSchemaInputValidationError } from '@/modules/automate/errors/management' import { + AutomationRunStatus, AutomationRunStatuses, VersionCreationTriggerType } from '@/modules/automate/helpers/types' @@ -561,7 +562,7 @@ export const getAutomationsStatus = (a) => a.status === AutomationRunStatuses.pending ) - let status = AutomationRunStatuses.succeeded + let status: AutomationRunStatus = AutomationRunStatuses.succeeded let statusMessage = 'All automations have succeeded' if (failedAutomations.length) { diff --git a/packages/server/modules/automate/services/runsManagement.ts b/packages/server/modules/automate/services/runsManagement.ts index 611683e3e..9a7e80273 100644 --- a/packages/server/modules/automate/services/runsManagement.ts +++ b/packages/server/modules/automate/services/runsManagement.ts @@ -16,14 +16,14 @@ import { import { Automate } from '@speckle/shared' const AutomationRunStatusOrder: { [key in AutomationRunStatus]: number } = { - pending: 0, - initializing: 1, - running: 2, - succeeded: 3, - failed: 3, - canceled: 4, - exception: 5, - timeout: 5 + [AutomationRunStatuses.pending]: 0, + [AutomationRunStatuses.initializing]: 1, + [AutomationRunStatuses.running]: 2, + [AutomationRunStatuses.succeeded]: 3, + [AutomationRunStatuses.failed]: 3, + [AutomationRunStatuses.exception]: 5, + [AutomationRunStatuses.timeout]: 5, + [AutomationRunStatuses.canceled]: 4 } /** @@ -148,7 +148,7 @@ export const reportFunctionRunStatus = await AutomateRunsEmitter.emit(AutomateRunsEmitter.events.StatusUpdated, { run: updatedRun, - functionRuns: [nextFunctionRunRecord], + functionRun: nextFunctionRunRecord, automationId }) diff --git a/packages/server/modules/automate/services/subscriptions.ts b/packages/server/modules/automate/services/subscriptions.ts index 7adaf4867..7da819788 100644 --- a/packages/server/modules/automate/services/subscriptions.ts +++ b/packages/server/modules/automate/services/subscriptions.ts @@ -121,7 +121,7 @@ export const setupStatusUpdateSubscriptions = AutomateRunsEmitter.listen( AutomateRunsEmitter.events.StatusUpdated, - async ({ run, functionRuns, automationId }) => { + async ({ run, functionRun, automationId }) => { const triggers = await getAutomationRunFullTriggers({ automationRunId: run.id }) @@ -141,7 +141,7 @@ export const setupStatusUpdateSubscriptions = versionId: trigger.version.id, run: { ...run, - functionRuns, + functionRuns: [functionRun], automationId, triggers: undefined }, diff --git a/packages/server/modules/automate/services/tracking.ts b/packages/server/modules/automate/services/tracking.ts new file mode 100644 index 000000000..cca251e47 --- /dev/null +++ b/packages/server/modules/automate/services/tracking.ts @@ -0,0 +1,116 @@ +import { automateLogger } from '@/logging/logging' +import { AutomateRunsEmitter } from '@/modules/automate/events/runs' +import { + AutomationFunctionRunRecord, + AutomationRunRecord, + AutomationRunStatus, + AutomationRunStatuses, + AutomationWithRevision, + RunTriggerSource +} from '@/modules/automate/helpers/types' +import { + InsertableAutomationRun, + getFullAutomationRevisionMetadata +} from '@/modules/automate/repositories/automations' +import { mixpanel } from '@/modules/shared/utils/mixpanel' +import { throwUncoveredError } from '@speckle/shared' +import dayjs from 'dayjs' + +const isFinished = (runStatus: AutomationRunStatus) => { + const finishedStatuses: AutomationRunStatus[] = [ + AutomationRunStatuses.succeeded, + AutomationRunStatuses.failed, + AutomationRunStatuses.exception, + AutomationRunStatuses.timeout, + AutomationRunStatuses.canceled + ] + + return finishedStatuses.includes(runStatus) +} + +export type SetupRunFinishedTrackingDeps = { + getFullAutomationRevisionMetadata: typeof getFullAutomationRevisionMetadata +} + +const onAutomationRunStatusUpdated = + ({ getFullAutomationRevisionMetadata }: SetupRunFinishedTrackingDeps) => + async ({ + run, + functionRun, + automationId + }: { + run: AutomationRunRecord + functionRun: AutomationFunctionRunRecord + automationId: string + }) => { + if (!isFinished(run.status)) return + + const automationWithRevision = await getFullAutomationRevisionMetadata( + run.automationRevisionId + ) + if (!automationWithRevision) { + automateLogger.error( + { + run + }, + 'Run revision not found unexpectedly' + ) + return + } + + const mp = mixpanel({ userEmail: undefined }) + await mp.track('Automate Function Run Finished', { + automationId, + automationRevisionId: automationWithRevision.id, + automationName: automationWithRevision.name, + runId: run.id, + functionRunId: functionRun.id, + status: functionRun.status, + durationInSeconds: dayjs(functionRun.updatedAt).diff( + functionRun.createdAt, + 'second' + ) + }) + } + +const onRunCreated = async ({ + automation, + run: automationRun, + source +}: { + automation: AutomationWithRevision + run: InsertableAutomationRun + source: RunTriggerSource +}) => { + // all triggers, that are automatic result of an action are in a need to be tracked + switch (source) { + case RunTriggerSource.Automatic: { + const mp = mixpanel({ userEmail: undefined }) + await mp.track('Automation Run Triggered', { + automationId: automation.id, + automationName: automation.name, + automationRunId: automationRun.id, + projectId: automation.projectId, + source + }) + break + } + // runs created from a user interaction are tracked in the frontend + case RunTriggerSource.Manual: + return + default: + throwUncoveredError(source) + } +} + +export const setupRunFinishedTracking = (deps: SetupRunFinishedTrackingDeps) => () => { + const quitters = [ + AutomateRunsEmitter.listen( + AutomateRunsEmitter.events.StatusUpdated, + onAutomationRunStatusUpdated(deps) + ), + AutomateRunsEmitter.listen(AutomateRunsEmitter.events.Created, onRunCreated) + ] + + return () => quitters.forEach((quitter) => quitter()) +} diff --git a/packages/server/modules/automate/services/trigger.ts b/packages/server/modules/automate/services/trigger.ts index ab935169d..b8a574fca 100644 --- a/packages/server/modules/automate/services/trigger.ts +++ b/packages/server/modules/automate/services/trigger.ts @@ -17,7 +17,8 @@ import { VersionCreationTriggerType, BaseTriggerManifest, isVersionCreatedTriggerManifest, - LiveAutomation + LiveAutomation, + RunTriggerSource } from '@/modules/automate/helpers/types' import { getBranchLatestCommits } from '@/modules/core/repositories/branches' import { getCommit } from '@/modules/core/repositories/commits' @@ -219,9 +220,10 @@ export const triggerAutomationRevisionRun = async (params: { revisionId: string manifest: M + source?: RunTriggerSource }): Promise<{ automationRunId: string }> => { const { automateRunTrigger } = deps - const { revisionId, manifest } = params + const { revisionId, manifest, source = RunTriggerSource.Automatic } = params if (!isVersionCreatedTriggerManifest(manifest)) { throw new AutomateInvalidTriggerError( @@ -302,7 +304,9 @@ export const triggerAutomationRevisionRun = await AutomateRunsEmitter.emit(AutomateRunsEmitter.events.Created, { run: automationRun, manifests: triggerManifests, - automation: automationWithRevision + automation: automationWithRevision, + source, + triggerType: manifest.triggerType }) return { automationRunId: automationRun.id } @@ -392,13 +396,18 @@ export const ensureRunConditions = async function composeTriggerData(params: { projectId: string manifest: BaseTriggerManifest - // TODO: Q Gergo: What's going on here? Why do we pass in extra unrelated triggers? triggerDefinitions: AutomationTriggerDefinitionRecord[] }): Promise { const { projectId, manifest, triggerDefinitions } = params const manifests: BaseTriggerManifest[] = [{ ...manifest }] + /** + * The reason why we collect multiple triggers, even tho there's only one: + * - We want to collect the current context (all active versions of all triggers) at the time when the run is triggered, + * cause once the function already runs, there may be new versions already + */ + if (triggerDefinitions.length > 1) { const associatedTriggers = triggerDefinitions.filter((t) => { if (t.triggerType !== manifest.triggerType) return false @@ -492,15 +501,17 @@ export const manuallyTriggerAutomation = } // Trigger "model version created" - return await triggerFunction({ + const { automationRunId } = await triggerFunction({ revisionId: triggerDefs[0].automationRevisionId, manifest: { projectId, modelId: latestCommit.branchId, versionId: latestCommit.id, triggerType: VersionCreationTriggerType - } + }, + source: RunTriggerSource.Manual }) + return { automationRunId } } export type CreateTestAutomationRunDeps = { diff --git a/packages/server/modules/automate/tests/trigger.spec.ts b/packages/server/modules/automate/tests/trigger.spec.ts index 5316d249a..ffbd4cb9e 100644 --- a/packages/server/modules/automate/tests/trigger.spec.ts +++ b/packages/server/modules/automate/tests/trigger.spec.ts @@ -17,6 +17,7 @@ import { AutomationTriggerType, BaseTriggerManifest, LiveAutomation, + RunTriggerSource, VersionCreatedTriggerManifest, VersionCreationTriggerType, isVersionCreatedTriggerManifest @@ -294,7 +295,8 @@ const { FF_AUTOMATE_MODULE_ENABLED } = Environment.getFeatureFlags() versionId: cryptoRandomString({ length: 10 }), triggerType: VersionCreationTriggerType, modelId: cryptoRandomString({ length: 10 }) - } + }, + source: RunTriggerSource.Manual }) throw 'this should have thrown' } catch (error) { @@ -378,7 +380,8 @@ const { FF_AUTOMATE_MODULE_ENABLED } = Environment.getFeatureFlags() versionId: version.id, modelId: trigger.triggeringId, triggerType: trigger.triggerType - } + }, + source: RunTriggerSource.Manual }) const storedRun = await getFullAutomationRunById(automationRunId) @@ -468,7 +471,8 @@ const { FF_AUTOMATE_MODULE_ENABLED } = Environment.getFeatureFlags() versionId: version.id, modelId: trigger.triggeringId, triggerType: trigger.triggerType - } + }, + source: RunTriggerSource.Manual }) const storedRun = await getFullAutomationRunById(automationRunId) diff --git a/packages/server/modules/core/graph/generated/graphql.ts b/packages/server/modules/core/graph/generated/graphql.ts index db4dedfdd..f1b3e51a6 100644 --- a/packages/server/modules/core/graph/generated/graphql.ts +++ b/packages/server/modules/core/graph/generated/graphql.ts @@ -344,7 +344,6 @@ export enum AutomateRunStatus { } export enum AutomateRunTriggerType { - TestType = 'TEST_TYPE', VersionCreated = 'VERSION_CREATED' } @@ -1845,7 +1844,7 @@ export type ProjectAutomationMutations = { * Trigger an automation with a fake "version created" trigger. The "version created" will * just refer to the last version of the model. */ - trigger: Scalars['Boolean']; + trigger: Scalars['String']; update: Automation; }; @@ -4736,7 +4735,7 @@ export type ProjectAutomationMutationsResolvers>; createTestAutomation?: Resolver>; createTestAutomationRun?: Resolver>; - trigger?: 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 aea05eae0..50f3676dc 100644 --- a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts +++ b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts @@ -333,7 +333,6 @@ export enum AutomateRunStatus { } export enum AutomateRunTriggerType { - TestType = 'TEST_TYPE', VersionCreated = 'VERSION_CREATED' } @@ -1834,7 +1833,7 @@ export type ProjectAutomationMutations = { * Trigger an automation with a fake "version created" trigger. The "version created" will * just refer to the last version of the model. */ - trigger: Scalars['Boolean']; + trigger: Scalars['String']; update: Automation; }; diff --git a/packages/server/modules/mocks.ts b/packages/server/modules/mocks.ts index 120a90e2d..07dde277b 100644 --- a/packages/server/modules/mocks.ts +++ b/packages/server/modules/mocks.ts @@ -230,7 +230,7 @@ export async function buildMocksConfig(): Promise<{ ...(isNullOrUndefined(enabled) ? {} : { enabled }) } }, - trigger: () => true, + trigger: () => faker.datatype.string(10), createRevision: () => store.get('AutomationRevision') as any }, UserAutomateInfo: { diff --git a/packages/server/test/graphql/generated/graphql.ts b/packages/server/test/graphql/generated/graphql.ts index c50b1b4e3..04c27cb08 100644 --- a/packages/server/test/graphql/generated/graphql.ts +++ b/packages/server/test/graphql/generated/graphql.ts @@ -334,7 +334,6 @@ export enum AutomateRunStatus { } export enum AutomateRunTriggerType { - TestType = 'TEST_TYPE', VersionCreated = 'VERSION_CREATED' } @@ -1835,7 +1834,7 @@ export type ProjectAutomationMutations = { * Trigger an automation with a fake "version created" trigger. The "version created" will * just refer to the last version of the model. */ - trigger: Scalars['Boolean']; + trigger: Scalars['String']; update: Automation; }; diff --git a/packages/shared/src/core/helpers/error.ts b/packages/shared/src/core/helpers/error.ts index 0b801c151..6b916b4bd 100644 --- a/packages/shared/src/core/helpers/error.ts +++ b/packages/shared/src/core/helpers/error.ts @@ -11,3 +11,12 @@ export function ensureError( if (e instanceof Error) return e return new UnexpectedErrorStructureError(fallbackMessage) } + +// this makes sure that a case is breaking in typing and in runtime too +export function throwUncoveredError(e: never): never { + throw createUncoveredError(e) +} + +export function createUncoveredError(e: never) { + return new Error(`Uncovered error case ${e}.`) +} diff --git a/workspace.code-workspace b/workspace.code-workspace index ae9f22361..18c5e59fb 100644 --- a/workspace.code-workspace +++ b/workspace.code-workspace @@ -86,7 +86,7 @@ }, "files.eol": "\n", "volar.vueserver.maxOldSpaceSize": 4000, - "cSpell.words": ["Automations", "Bursty", "mjml"], + "cSpell.words": ["Automations", "Bursty", "Insertable", "mjml"], "tailwindCSS.experimental.configFile": { "packages/frontend-2/tailwind.config.mjs": "packages/frontend-2/**" },