refactor: Migrate externalHooks mixin to composables (no-changelog) (#7930)
## Summary Provide details about your pull request and what it adds, fixes, or changes. Photos and videos are recommended. As part of NodeView refactor, this PR migrates all externalHooks calls to `useExternalHooks` composable. #### How to test the change: 1. Run using env `export N8N_DEPLOYMENT_TYPE=cloud` 2. Hooks should still run as expected ## Issues fixed Include links to Github issue or Community forum post or **Linear ticket**: > Important in order to close automatically and provide context to reviewers https://linear.app/n8n/issue/N8N-6349/externalhooks ## Review / Merge checklist - [x] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) - [x] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created. - [x] Tests included. > A bug is not considered fixed, unless a test is added to prevent it from happening again. A feature is not complete without tests. > > *(internal)* You can use Slack commands to trigger [e2e tests](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#a39f9e5ba64a48b58a71d81c837e8227) or [deploy test instance](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#f6a177d32bde4b57ae2da0b8e454bfce) or [deploy early access version on Cloud](https://www.notion.so/n8n/Cloudbot-3dbe779836004972b7057bc989526998?pvs=4#fef2d36ab02247e1a0f65a74f6fb534e).
This commit is contained in:
@@ -235,7 +235,6 @@ import {
|
||||
TIME,
|
||||
} from '@/constants';
|
||||
import { copyPaste } from '@/mixins/copyPaste';
|
||||
import { externalHooks } from '@/mixins/externalHooks';
|
||||
import { genericHelpers } from '@/mixins/genericHelpers';
|
||||
import { moveNodeWorkflow } from '@/mixins/moveNodeWorkflow';
|
||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
||||
@@ -366,6 +365,7 @@ import {
|
||||
import { sourceControlEventBus } from '@/event-bus/source-control';
|
||||
import { getConnectorPaintStyleData, OVERLAY_ENDPOINT_ARROW_ID } from '@/utils/nodeViewUtils';
|
||||
import { useViewStacks } from '@/components/Node/NodeCreator/composables/useViewStacks';
|
||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
|
||||
interface AddNodeOptions {
|
||||
position?: XYPosition;
|
||||
@@ -380,7 +380,6 @@ export default defineComponent({
|
||||
name: 'NodeView',
|
||||
mixins: [
|
||||
copyPaste,
|
||||
externalHooks,
|
||||
genericHelpers,
|
||||
moveNodeWorkflow,
|
||||
workflowHelpers,
|
||||
@@ -399,7 +398,8 @@ export default defineComponent({
|
||||
CanvasControls,
|
||||
ContextMenu,
|
||||
},
|
||||
setup(props) {
|
||||
setup(props, ctx) {
|
||||
const externalHooks = useExternalHooks();
|
||||
const locale = useI18n();
|
||||
const contextMenu = useContextMenu();
|
||||
const dataSchema = useDataSchema();
|
||||
@@ -408,6 +408,7 @@ export default defineComponent({
|
||||
locale,
|
||||
contextMenu,
|
||||
dataSchema,
|
||||
externalHooks,
|
||||
...useCanvasMouseSelect(),
|
||||
...useGlobalLinkActions(),
|
||||
...useTitleChange(),
|
||||
@@ -416,7 +417,7 @@ export default defineComponent({
|
||||
...useUniqueNodeName(),
|
||||
...useExecutionDebugging(),
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
...workflowRun.setup?.(props),
|
||||
...workflowRun.setup?.(props, ctx),
|
||||
};
|
||||
},
|
||||
errorCaptured: (err, vm, info) => {
|
||||
@@ -775,7 +776,7 @@ export default defineComponent({
|
||||
session_id: this.ndvStore.sessionId,
|
||||
};
|
||||
this.$telemetry.track('User clicked execute node button', telemetryPayload);
|
||||
void this.$externalHooks().run('nodeView.onRunNode', telemetryPayload);
|
||||
void this.externalHooks.run('nodeView.onRunNode', telemetryPayload);
|
||||
void this.runWorkflow({ destinationNode: nodeName, source });
|
||||
},
|
||||
async onOpenChat() {
|
||||
@@ -783,7 +784,7 @@ export default defineComponent({
|
||||
workflow_id: this.workflowsStore.workflowId,
|
||||
};
|
||||
this.$telemetry.track('User clicked chat open button', telemetryPayload);
|
||||
void this.$externalHooks().run('nodeView.onOpenChat', telemetryPayload);
|
||||
void this.externalHooks.run('nodeView.onOpenChat', telemetryPayload);
|
||||
this.uiStore.openModal(WORKFLOW_LM_CHAT_MODAL_KEY);
|
||||
},
|
||||
async onRunWorkflow() {
|
||||
@@ -796,7 +797,7 @@ export default defineComponent({
|
||||
),
|
||||
};
|
||||
this.$telemetry.track('User clicked execute workflow button', telemetryPayload);
|
||||
void this.$externalHooks().run('nodeView.onRunWorkflow', telemetryPayload);
|
||||
void this.externalHooks.run('nodeView.onRunWorkflow', telemetryPayload);
|
||||
});
|
||||
|
||||
await this.runWorkflow({});
|
||||
@@ -950,7 +951,7 @@ export default defineComponent({
|
||||
await this.$nextTick();
|
||||
this.canvasStore.zoomToFit();
|
||||
this.uiStore.stateIsDirty = false;
|
||||
void this.$externalHooks().run('execution.open', {
|
||||
void this.externalHooks.run('execution.open', {
|
||||
workflowId: data.workflowData.id,
|
||||
workflowName: data.workflowData.name,
|
||||
executionId,
|
||||
@@ -1030,7 +1031,7 @@ export default defineComponent({
|
||||
|
||||
let data: IWorkflowTemplate | undefined;
|
||||
try {
|
||||
void this.$externalHooks().run('template.requested', { templateId });
|
||||
void this.externalHooks.run('template.requested', { templateId });
|
||||
data = await this.templatesStore.getFixedWorkflowTemplate(templateId);
|
||||
|
||||
if (!data) {
|
||||
@@ -1055,7 +1056,7 @@ export default defineComponent({
|
||||
this.canvasStore.zoomToFit();
|
||||
this.uiStore.stateIsDirty = true;
|
||||
|
||||
void this.$externalHooks().run('template.open', {
|
||||
void this.externalHooks.run('template.open', {
|
||||
templateId,
|
||||
templateName: data.name,
|
||||
workflow: data.workflow,
|
||||
@@ -1106,7 +1107,7 @@ export default defineComponent({
|
||||
this.uiStore.stateIsDirty = false;
|
||||
}
|
||||
this.canvasStore.zoomToFit();
|
||||
void this.$externalHooks().run('workflow.open', {
|
||||
void this.externalHooks.run('workflow.open', {
|
||||
workflowId: workflow.id,
|
||||
workflowName: workflow.name,
|
||||
});
|
||||
@@ -1191,7 +1192,7 @@ export default defineComponent({
|
||||
if (e.key === 'Escape') {
|
||||
this.createNodeActive = false;
|
||||
if (this.activeNode) {
|
||||
void this.$externalHooks().run('dataDisplay.nodeEditingFinished');
|
||||
void this.externalHooks.run('dataDisplay.nodeEditingFinished');
|
||||
this.ndvStore.activeNodeName = null;
|
||||
}
|
||||
|
||||
@@ -2256,7 +2257,7 @@ export default defineComponent({
|
||||
workflow_id: this.workflowsStore.workflowId,
|
||||
});
|
||||
} else {
|
||||
void this.$externalHooks().run('nodeView.addNodeButton', { nodeTypeName });
|
||||
void this.externalHooks.run('nodeView.addNodeButton', { nodeTypeName });
|
||||
useSegment().trackAddedTrigger(nodeTypeName);
|
||||
const trackProperties: ITelemetryTrackProperties = {
|
||||
node_type: nodeTypeName,
|
||||
@@ -3595,7 +3596,7 @@ export default defineComponent({
|
||||
is_welcome_note: node.name === QUICKSTART_NOTE_NAME,
|
||||
});
|
||||
} else {
|
||||
void this.$externalHooks().run('node.deleteNode', { node });
|
||||
void this.externalHooks.run('node.deleteNode', { node });
|
||||
this.$telemetry.track('User deleted node', {
|
||||
node_type: node.type,
|
||||
workflow_id: this.workflowsStore.workflowId,
|
||||
@@ -4395,7 +4396,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
if (createNodeActive) this.nodeCreatorStore.setOpenSource(source);
|
||||
void this.$externalHooks().run('nodeView.createNodeActiveChanged', {
|
||||
void this.externalHooks.run('nodeView.createNodeActiveChanged', {
|
||||
source,
|
||||
mode,
|
||||
createNodeActive,
|
||||
@@ -4644,9 +4645,7 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
// TODO: This currently breaks since front-end hooks are still not updated to work with pinia store
|
||||
void this.$externalHooks()
|
||||
.run('nodeView.mount')
|
||||
.catch((e) => {});
|
||||
void this.externalHooks.run('nodeView.mount').catch((e) => {});
|
||||
|
||||
if (
|
||||
this.currentUser?.personalizationAnswers !== null &&
|
||||
|
||||
@@ -68,6 +68,7 @@ import { useUIStore } from '@/stores/ui.store';
|
||||
import { mapStores } from 'pinia';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { defineComponent } from 'vue';
|
||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
|
||||
const PACKAGE_COUNT_THRESHOLD = 31;
|
||||
|
||||
@@ -77,11 +78,14 @@ export default defineComponent({
|
||||
components: {
|
||||
CommunityPackageCard,
|
||||
},
|
||||
setup(props) {
|
||||
setup(props, ctx) {
|
||||
const externalHooks = useExternalHooks();
|
||||
|
||||
return {
|
||||
externalHooks,
|
||||
...useToast(),
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
...pushConnection.setup?.(props),
|
||||
...pushConnection.setup?.(props, ctx),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
@@ -222,10 +226,7 @@ export default defineComponent({
|
||||
};
|
||||
this.$telemetry.track('user clicked cnr install button', telemetryPayload);
|
||||
|
||||
void this.$externalHooks().run(
|
||||
'settingsCommunityNodesView.openInstallModal',
|
||||
telemetryPayload,
|
||||
);
|
||||
void this.externalHooks.run('settingsCommunityNodesView.openInstallModal', telemetryPayload);
|
||||
this.uiStore.openModal(COMMUNITY_PACKAGE_INSTALL_MODAL_KEY);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -8,19 +8,15 @@ import AppsRequiringCredsNotice from './AppsRequiringCredsNotice.vue';
|
||||
import SetupTemplateFormStep from './SetupTemplateFormStep.vue';
|
||||
import TemplatesView from '../TemplatesView.vue';
|
||||
import { VIEWS } from '@/constants';
|
||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
|
||||
// Store
|
||||
const setupTemplateStore = useSetupTemplateStore();
|
||||
const i18n = useI18n();
|
||||
const $telemetry = useTelemetry();
|
||||
const $externalHooks = useExternalHooks();
|
||||
|
||||
// Router
|
||||
const route = useRoute();
|
||||
const $router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
//#region Computed
|
||||
|
||||
@@ -31,7 +27,7 @@ const title = computed(() => setupTemplateStore.template?.name ?? 'unknown');
|
||||
const isReady = computed(() => !setupTemplateStore.isLoading);
|
||||
|
||||
const skipSetupUrl = computed(() => {
|
||||
const resolvedRoute = $router.resolve({
|
||||
const resolvedRoute = router.resolve({
|
||||
name: VIEWS.TEMPLATE_IMPORT,
|
||||
params: { id: templateId.value },
|
||||
});
|
||||
@@ -55,9 +51,7 @@ const onSkipSetup = async (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
await setupTemplateStore.skipSetup({
|
||||
$externalHooks,
|
||||
$telemetry,
|
||||
$router,
|
||||
router,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -69,9 +63,7 @@ const skipIfTemplateHasNoCreds = async () => {
|
||||
|
||||
if (setupTemplateStore.credentialUsages.length === 0) {
|
||||
await setupTemplateStore.skipSetup({
|
||||
$externalHooks,
|
||||
$telemetry,
|
||||
$router,
|
||||
router,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -94,7 +86,7 @@ onMounted(async () => {
|
||||
<TemplatesView :goBackEnabled="true">
|
||||
<template #header>
|
||||
<n8n-heading v-if="isReady" tag="h1" size="2xlarge"
|
||||
>{{ $locale.baseText('templateSetup.title', { interpolate: { name: title } }) }}
|
||||
>{{ i18n.baseText('templateSetup.title', { interpolate: { name: title } }) }}
|
||||
</n8n-heading>
|
||||
<n8n-loading v-else variant="h1" />
|
||||
</template>
|
||||
@@ -124,15 +116,15 @@ onMounted(async () => {
|
||||
|
||||
<div :class="$style.actions">
|
||||
<n8n-link :href="skipSetupUrl" :newWindow="false" @click="onSkipSetup($event)">{{
|
||||
$locale.baseText('templateSetup.skip')
|
||||
i18n.baseText('templateSetup.skip')
|
||||
}}</n8n-link>
|
||||
|
||||
<n8n-button
|
||||
v-if="isReady"
|
||||
size="large"
|
||||
:label="$locale.baseText('templateSetup.continue.button')"
|
||||
:label="i18n.baseText('templateSetup.continue.button')"
|
||||
:disabled="setupTemplateStore.isSaving"
|
||||
@click="setupTemplateStore.createWorkflow($router)"
|
||||
@click="setupTemplateStore.createWorkflow(router)"
|
||||
data-test-id="continue-button"
|
||||
/>
|
||||
<div v-else>
|
||||
|
||||
@@ -11,12 +11,10 @@ import { getAppNameFromNodeName } from '@/utils/nodeTypesUtils';
|
||||
import type { INodeCredentialsDetails, INodeTypeDescription } from 'n8n-workflow';
|
||||
import type {
|
||||
ICredentialsResponse,
|
||||
IExternalHooks,
|
||||
INodeUi,
|
||||
ITemplatesWorkflowFull,
|
||||
IWorkflowTemplateNode,
|
||||
} from '@/Interface';
|
||||
import type { Telemetry } from '@/plugins/telemetry';
|
||||
import { VIEWS } from '@/constants';
|
||||
import { createWorkflowFromTemplate } from '@/utils/templates/templateActions';
|
||||
import type {
|
||||
@@ -28,6 +26,8 @@ import {
|
||||
keyFromCredentialTypeAndName,
|
||||
normalizeTemplateNodeCredentials,
|
||||
} from '@/utils/templates/templateTransforms';
|
||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
|
||||
export type NodeAndType = {
|
||||
node: INodeUi;
|
||||
@@ -300,25 +300,23 @@ export const useSetupTemplateStore = defineStore('setupTemplate', () => {
|
||||
/**
|
||||
* Skips the setup and goes directly to the workflow view.
|
||||
*/
|
||||
const skipSetup = async (opts: {
|
||||
$externalHooks: IExternalHooks;
|
||||
$telemetry: Telemetry;
|
||||
$router: Router;
|
||||
}) => {
|
||||
const { $externalHooks, $telemetry, $router } = opts;
|
||||
const skipSetup = async ({ router }: { router: Router }) => {
|
||||
const externalHooks = useExternalHooks();
|
||||
const telemetry = useTelemetry();
|
||||
|
||||
const telemetryPayload = {
|
||||
source: 'workflow',
|
||||
template_id: templateId.value,
|
||||
wf_template_repo_session_id: templatesStore.currentSessionId,
|
||||
};
|
||||
|
||||
await $externalHooks.run('templatesWorkflowView.openWorkflow', telemetryPayload);
|
||||
$telemetry.track('User inserted workflow template', telemetryPayload, {
|
||||
await externalHooks.run('templatesWorkflowView.openWorkflow', telemetryPayload);
|
||||
telemetry.track('User inserted workflow template', telemetryPayload, {
|
||||
withPostHog: true,
|
||||
});
|
||||
|
||||
// Replace the URL so back button doesn't come back to this setup view
|
||||
await $router.replace({
|
||||
await router.replace({
|
||||
name: VIEWS.TEMPLATE_IMPORT,
|
||||
params: { id: templateId.value },
|
||||
});
|
||||
|
||||
@@ -71,6 +71,7 @@ import { useTemplatesStore } from '@/stores/templates.store';
|
||||
import { usePostHog } from '@/stores/posthog.store';
|
||||
import { FeatureFlag, isFeatureFlagEnabled } from '@/utils/featureFlag';
|
||||
import { openTemplateCredentialSetup } from '@/utils/templates/templateActions';
|
||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TemplatesCollectionView',
|
||||
@@ -80,6 +81,13 @@ export default defineComponent({
|
||||
TemplateList,
|
||||
TemplatesView,
|
||||
},
|
||||
setup() {
|
||||
const externalHooks = useExternalHooks();
|
||||
|
||||
return {
|
||||
externalHooks,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useTemplatesStore, usePostHog),
|
||||
collection(): ITemplatesCollectionFull | null {
|
||||
@@ -127,7 +135,7 @@ export default defineComponent({
|
||||
wf_template_repo_session_id: this.templatesStore.currentSessionId,
|
||||
source: 'collection',
|
||||
};
|
||||
await this.$externalHooks().run('templatesCollectionView.onUseWorkflow', telemetryPayload);
|
||||
await this.externalHooks.run('templatesCollectionView.onUseWorkflow', telemetryPayload);
|
||||
this.$telemetry.track('User inserted workflow template', telemetryPayload, {
|
||||
withPostHog: true,
|
||||
});
|
||||
|
||||
@@ -70,6 +70,7 @@ import { useTemplatesStore } from '@/stores/templates.store';
|
||||
import { usePostHog } from '@/stores/posthog.store';
|
||||
import { openTemplateCredentialSetup } from '@/utils/templates/templateActions';
|
||||
import { FeatureFlag, isFeatureFlagEnabled } from '@/utils/featureFlag';
|
||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TemplatesWorkflowView',
|
||||
@@ -79,6 +80,13 @@ export default defineComponent({
|
||||
TemplatesView,
|
||||
WorkflowPreview,
|
||||
},
|
||||
setup() {
|
||||
const externalHooks = useExternalHooks();
|
||||
|
||||
return {
|
||||
externalHooks,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useTemplatesStore, usePostHog),
|
||||
template(): ITemplatesWorkflowFull | null {
|
||||
@@ -109,7 +117,7 @@ export default defineComponent({
|
||||
this.$telemetry.track('User inserted workflow template', telemetryPayload, {
|
||||
withPostHog: true,
|
||||
});
|
||||
await this.$externalHooks().run('templatesWorkflowView.openWorkflow', telemetryPayload);
|
||||
await this.externalHooks.run('templatesWorkflowView.openWorkflow', telemetryPayload);
|
||||
}
|
||||
|
||||
await openTemplateCredentialSetup({
|
||||
|
||||
Reference in New Issue
Block a user