Files
Automata/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/setupTemplate.store.ts

249 lines
6.2 KiB
TypeScript

import { defineStore } from 'pinia';
import { computed, ref } from 'vue';
import type { Router } from 'vue-router';
import { useCredentialsStore } from '@/stores/credentials.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useRootStore } from '@/stores/n8nRoot.store';
import { useTemplatesStore } from '@/stores/templates.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import type { INodeTypeDescription } from 'n8n-workflow';
import type { INodeUi } from '@/Interface';
import { VIEWS } from '@/constants';
import { createWorkflowFromTemplate } from '@/utils/templates/templateActions';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { useTelemetry } from '@/composables/useTelemetry';
import { useCredentialSetupState } from '@/views/SetupWorkflowFromTemplateView/useCredentialSetupState';
import { tryToParseNumber } from '@/utils/typesUtils';
export type NodeAndType = {
node: INodeUi;
nodeType: INodeTypeDescription;
};
export type RequiredCredentials = {
node: INodeUi;
credentialName: string;
credentialType: string;
};
export type AppCredentialCount = {
appName: string;
count: number;
};
/**
* Store for managing the state of the SetupWorkflowFromTemplateView
*/
export const useSetupTemplateStore = defineStore('setupTemplate', () => {
//#region State
const templateId = ref<string>('');
const isLoading = ref(true);
const isSaving = ref(false);
//#endregion State
const templatesStore = useTemplatesStore();
const nodeTypesStore = useNodeTypesStore();
const credentialsStore = useCredentialsStore();
const rootStore = useRootStore();
const workflowsStore = useWorkflowsStore();
//#region Getters
const template = computed(() => {
return templateId.value ? templatesStore.getFullTemplateById(templateId.value) : null;
});
const templateNodes = computed(() => {
return template.value?.workflow.nodes ?? [];
});
const {
appCredentials,
credentialOverrides,
credentialUsages,
credentialsByKey,
nodesRequiringCredentialsSorted,
numFilledCredentials,
selectedCredentialIdByKey,
setSelectedCredentialId,
unsetSelectedCredential,
} = useCredentialSetupState(templateNodes);
//#endregion Getters
//#region Actions
const setTemplateId = (id: string) => {
templateId.value = id;
};
const ignoredAutoFillCredentialTypes = new Set([
'httpBasicAuth',
'httpCustomAuth',
'httpDigestAuth',
'httpHeaderAuth',
'oAuth1Api',
'oAuth2Api',
'httpQueryAuth',
]);
/**
* Selects initial credentials for the template. Credentials
* need to be loaded before this.
*/
const setInitialCredentialSelection = () => {
for (const credUsage of credentialUsages.value) {
if (ignoredAutoFillCredentialTypes.has(credUsage.credentialType)) {
continue;
}
const availableCreds = credentialsStore.getCredentialsByType(credUsage.credentialType);
if (availableCreds.length === 1) {
selectedCredentialIdByKey.value[credUsage.key] = availableCreds[0].id;
}
}
};
/**
* Loads the template if it hasn't been loaded yet.
*/
const loadTemplateIfNeeded = async () => {
if (!!template.value || !templateId.value) {
return;
}
await templatesStore.fetchTemplateById(templateId.value);
setInitialCredentialSelection();
};
/**
* Initializes the store for a specific template.
*/
const init = async () => {
isLoading.value = true;
try {
selectedCredentialIdByKey.value = {};
await Promise.all([
credentialsStore.fetchAllCredentials(),
credentialsStore.fetchCredentialTypes(false),
nodeTypesStore.loadNodeTypesIfNotLoaded(),
loadTemplateIfNeeded(),
]);
setInitialCredentialSelection();
} finally {
isLoading.value = false;
}
};
/**
* Skips the setup and goes directly to the workflow view.
*/
const skipSetup = async ({ router }: { router: Router }) => {
const externalHooks = useExternalHooks();
const telemetry = useTelemetry();
await externalHooks.run('templatesWorkflowView.openWorkflow', {
source: 'workflow',
template_id: templateId.value,
wf_template_repo_session_id: templatesStore.currentSessionId,
});
telemetry.track('User closed cred setup', {
completed: false,
creds_filled: 0,
creds_needed: credentialUsages.value.length,
workflow_id: null,
});
// Replace the URL so back button doesn't come back to this setup view
await router.replace({
name: VIEWS.TEMPLATE_IMPORT,
params: { id: templateId.value },
});
};
/**
* Creates a workflow from the template and navigates to the workflow view.
*/
const createWorkflow = async (opts: { router: Router }) => {
const { router } = opts;
const telemetry = useTelemetry();
if (!template.value) {
return;
}
try {
isSaving.value = true;
const createdWorkflow = await createWorkflowFromTemplate({
template: template.value,
credentialOverrides: credentialOverrides.value,
rootStore,
workflowsStore,
nodeTypeProvider: nodeTypesStore,
});
telemetry.track('User closed cred setup', {
completed: true,
creds_filled: numFilledCredentials.value,
creds_needed: credentialUsages.value.length,
workflow_id: createdWorkflow.id,
});
telemetry.track(
'User inserted workflow template',
{
source: 'workflow',
template_id: tryToParseNumber(templateId.value),
wf_template_repo_session_id: templatesStore.currentSessionId,
},
{ withPostHog: true },
);
telemetry.track('User saved new workflow from template', {
template_id: tryToParseNumber(templateId.value),
workflow_id: createdWorkflow.id,
wf_template_repo_session_id: templatesStore.currentSessionId,
});
// Replace the URL so back button doesn't come back to this setup view
await router.replace({
name: VIEWS.WORKFLOW,
params: { name: createdWorkflow.id },
});
} finally {
isSaving.value = false;
}
};
//#endregion Actions
return {
credentialsByKey,
isLoading,
isSaving,
appCredentials,
nodesRequiringCredentialsSorted,
template,
credentialUsages,
selectedCredentialIdByKey,
credentialOverrides,
numFilledCredentials,
createWorkflow,
skipSetup,
init,
loadTemplateIfNeeded,
setInitialCredentialSelection,
setTemplateId,
setSelectedCredentialId,
unsetSelectedCredential,
};
});