feat(editor): Replace root events with event bus events (no-changelog) (#6454)

* feat: replace root events with event bus events

* fix: prevent cypress from replacing global with globalThis in import path

* feat: remove emitter mixin

* fix: replace component events with event bus

* fix: fix linting issue

* fix: fix breaking expression switch

* chore: prettify ndv e2e suite code
This commit is contained in:
Alex Grozav 2023-06-20 13:00:53 +03:00 committed by GitHub
parent 18f588444f
commit 0154a97773
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 215 additions and 158 deletions

View file

@ -50,7 +50,7 @@ describe('NDV', () => {
workflowPage.getters.canvasNodes().last().dblclick();
ndv.getters.inputSelect().click();
ndv.getters.inputOption().last().click();
ndv.getters.inputDataContainer().find('[class*=schema_]').should('exist')
ndv.getters.inputDataContainer().find('[class*=schema_]').should('exist');
ndv.getters.inputDataContainer().should('contain', 'start');
});
@ -96,10 +96,20 @@ describe('NDV', () => {
ndv.getters.container().should('be.visible');
workflowPage.actions.saveWorkflowUsingKeyboardShortcut();
workflowPage.getters.isWorkflowSaved();
})
});
describe('test output schema view', () => {
const schemaKeys = ['id', 'name', 'email', 'notes', 'country', 'created', 'objectValue', 'prop1', 'prop2'];
const schemaKeys = [
'id',
'name',
'email',
'notes',
'country',
'created',
'objectValue',
'prop1',
'prop2',
];
function setupSchemaWorkflow() {
cy.createFixtureWorkflow('Test_workflow_schema_test.json', `NDV test schema view ${uuid()}`);
workflowPage.actions.zoomToFit();
@ -108,41 +118,62 @@ describe('NDV', () => {
}
it('should switch to output schema view and validate it', () => {
setupSchemaWorkflow()
setupSchemaWorkflow();
ndv.getters.outputDisplayMode().children().should('have.length', 3);
ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Table');
ndv.getters.outputDisplayMode().contains('Schema').click();
ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Schema');
schemaKeys.forEach((key) => {
ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item]').contains(key).should('exist');
ndv.getters
.outputPanel()
.find('[data-test-id=run-data-schema-item]')
.contains(key)
.should('exist');
});
});
it('should preserve schema view after execution', () => {
setupSchemaWorkflow()
setupSchemaWorkflow();
ndv.getters.outputDisplayMode().contains('Schema').click();
ndv.actions.execute();
ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Schema');
})
});
it('should collapse and expand nested schema object', () => {
setupSchemaWorkflow()
const expandedObjectProps = ['prop1', 'prop2'];;
const getObjectValueItem = () => ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item]').filter(':contains("objectValue")');
setupSchemaWorkflow();
const expandedObjectProps = ['prop1', 'prop2'];
const getObjectValueItem = () =>
ndv.getters
.outputPanel()
.find('[data-test-id=run-data-schema-item]')
.filter(':contains("objectValue")');
ndv.getters.outputDisplayMode().contains('Schema').click();
expandedObjectProps.forEach((key) => {
ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item]').contains(key).should('be.visible');
ndv.getters
.outputPanel()
.find('[data-test-id=run-data-schema-item]')
.contains(key)
.should('be.visible');
});
getObjectValueItem().find('label').click();
expandedObjectProps.forEach((key) => {
ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item]').contains(key).should('not.be.visible');
ndv.getters
.outputPanel()
.find('[data-test-id=run-data-schema-item]')
.contains(key)
.should('not.be.visible');
});
})
});
it('should not display pagination for schema', () => {
setupSchemaWorkflow()
setupSchemaWorkflow();
ndv.getters.backToCanvas().click();
workflowPage.getters.canvasNodeByName('Set').click();
workflowPage.actions.addNodeToCanvas('Customer Datastore (n8n training)', true, true, 'Get All People');
workflowPage.actions.addNodeToCanvas(
'Customer Datastore (n8n training)',
true,
true,
'Get All People',
);
ndv.actions.execute();
ndv.getters.outputPanel().contains('25 items').should('exist');
ndv.getters.outputPanel().find('[class*=_pagination]').should('exist');
@ -150,9 +181,12 @@ describe('NDV', () => {
ndv.getters.outputPanel().find('[class*=_pagination]').should('not.exist');
ndv.getters.outputDisplayMode().contains('JSON').click();
ndv.getters.outputPanel().find('[class*=_pagination]').should('exist');
})
});
it('should display large schema', () => {
cy.createFixtureWorkflow('Test_workflow_schema_test_pinned_data.json', `NDV test schema view ${uuid()}`);
cy.createFixtureWorkflow(
'Test_workflow_schema_test_pinned_data.json',
`NDV test schema view ${uuid()}`,
);
workflowPage.actions.zoomToFit();
workflowPage.actions.openNode('Set');
@ -160,8 +194,11 @@ describe('NDV', () => {
ndv.getters.outputPanel().find('[class*=_pagination]').should('exist');
ndv.getters.outputDisplayMode().contains('Schema').click();
ndv.getters.outputPanel().find('[class*=_pagination]').should('not.exist');
ndv.getters.outputPanel().find('[data-test-id=run-data-schema-item] [data-test-id=run-data-schema-item]').should('have.length', 20);
})
ndv.getters
.outputPanel()
.find('[data-test-id=run-data-schema-item] [data-test-id=run-data-schema-item]')
.should('have.length', 20);
});
});
it('can link and unlink run selectors between input and output', () => {
@ -170,11 +207,13 @@ describe('NDV', () => {
workflowPage.actions.executeWorkflow();
workflowPage.actions.openNode('Set3');
ndv.getters.inputRunSelector()
ndv.getters
.inputRunSelector()
.should('exist')
.find('input')
.should('include.value', '2 of 2 (6 items)');
ndv.getters.outputRunSelector()
ndv.getters
.outputRunSelector()
.should('exist')
.find('input')
.should('include.value', '2 of 2 (6 items)');
@ -183,23 +222,20 @@ describe('NDV', () => {
ndv.actions.switchOutputMode('Table');
ndv.actions.changeOutputRunSelector('1 of 2 (6 items)');
ndv.getters.inputRunSelector()
.find('input')
.should('include.value', '1 of 2 (6 items)');
ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 2 (6 items)');
ndv.getters.inputTbodyCell(1, 0).should('have.text', '1111');
ndv.getters.outputTbodyCell(1, 0).should('have.text', '1111');
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
ndv.actions.changeInputRunSelector('2 of 2 (6 items)');
ndv.getters.outputRunSelector()
.find('input')
.should('include.value', '2 of 2 (6 items)');
ndv.getters.outputRunSelector().find('input').should('include.value', '2 of 2 (6 items)');
// unlink
ndv.actions.toggleOutputRunLinking();
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
ndv.actions.changeOutputRunSelector('1 of 2 (6 items)');
ndv.getters.inputRunSelector()
ndv.getters
.inputRunSelector()
.should('exist')
.find('input')
.should('include.value', '2 of 2 (6 items)');
@ -207,24 +243,18 @@ describe('NDV', () => {
// link again
ndv.actions.toggleOutputRunLinking();
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
ndv.getters.inputRunSelector()
.find('input')
.should('include.value', '1 of 2 (6 items)');
ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 2 (6 items)');
// unlink again
ndv.actions.toggleInputRunLinking();
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
ndv.actions.changeInputRunSelector('2 of 2 (6 items)');
ndv.getters.outputRunSelector()
.find('input')
.should('include.value', '1 of 2 (6 items)');
ndv.getters.outputRunSelector().find('input').should('include.value', '1 of 2 (6 items)');
// link again
ndv.actions.toggleInputRunLinking();
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
ndv.getters.outputRunSelector()
.find('input')
.should('include.value', '2 of 2 (6 items)');
ndv.getters.outputRunSelector().find('input').should('include.value', '2 of 2 (6 items)');
});
it('should display parameter hints correctly', () => {
@ -247,21 +277,19 @@ describe('NDV', () => {
input: ' test',
},
{
input: ' '
input: ' ',
},
{
input: '<div></div>'
input: '<div></div>',
},
].forEach(({ input, output }) => {
if (input) {
ndv.actions.typeIntoParameterInput('value', input);
}
ndv.getters.parameterInput('name').click(); // remove focus from input, hide expression preview
if (input) {
ndv.actions.typeIntoParameterInput('value', input);
}
ndv.getters.parameterInput('name').click(); // remove focus from input, hide expression preview
ndv.actions.validateExpressionPreview('value', output || input);
ndv.getters.parameterInput('value').clear();
});
ndv.actions.validateExpressionPreview('value', output || input);
ndv.getters.parameterInput('value').clear();
});
});
});

View file

@ -5,25 +5,35 @@
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { defineComponent } from 'vue';
import emitter from '@/mixins/emitter';
import type { EventBus } from 'n8n-design-system/utils';
import { createEventBus } from 'n8n-design-system/utils';
export default defineComponent({
name: 'IntersectionObserved',
mixins: [emitter],
props: ['enabled'],
props: {
enabled: {
type: Boolean,
default: false,
},
eventBus: {
type: Object as PropType<EventBus>,
default: () => createEventBus(),
},
},
mounted() {
if (!this.enabled) {
return;
}
this.$nextTick(() => {
this.$dispatch('IntersectionObserver', 'observe', this.$refs.observed);
this.eventBus.emit('observe', this.$refs.observed);
});
},
beforeDestroy() {
if (this.enabled) {
this.$dispatch('IntersectionObserver', 'unobserve', this.$refs.observed);
this.eventBus.emit('unobserve', this.$refs.observed);
}
},
});

View file

@ -5,11 +5,27 @@
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { defineComponent } from 'vue';
import type { EventBus } from 'n8n-design-system/utils';
import { createEventBus } from 'n8n-design-system/utils';
export default defineComponent({
name: 'IntersectionObserver',
props: ['threshold', 'enabled'],
props: {
threshold: {
type: Number,
default: 0,
},
enabled: {
type: Boolean,
default: false,
},
eventBus: {
type: Object as PropType<EventBus>,
default: () => createEventBus(),
},
},
data() {
return {
observer: null,
@ -35,13 +51,13 @@ export default defineComponent({
});
}, options);
this.$data.observer = observer;
this.observer = observer;
this.$on('observe', (observed: Element) => {
this.eventBus.on('observe', (observed: Element) => {
observer.observe(observed);
});
this.$on('unobserve', (observed: Element) => {
this.eventBus.on('unobserve', (observed: Element) => {
observer.unobserve(observed);
});
},

View file

@ -166,6 +166,7 @@ import type { IPermissions } from '@/permissions';
import { getWorkflowPermissions } from '@/permissions';
import { createEventBus } from 'n8n-design-system';
import { useCloudPlanStore } from '@/stores';
import { nodeViewEventBus } from '@/event-bus';
const hasChanged = (prev: string[], curr: string[]) => {
if (prev.length !== curr.length) {
@ -445,7 +446,7 @@ export default defineComponent({
return;
}
this.$root.$emit('importWorkflowData', { data: workflowData });
nodeViewEventBus.emit('importWorkflowData', { data: workflowData });
};
const inputRef = this.$refs.importFile as HTMLInputElement | undefined;
@ -505,7 +506,7 @@ export default defineComponent({
},
)) as MessageBoxInputData;
this.$root.$emit('importWorkflowUrl', { url: promptResponse.value });
nodeViewEventBus.emit('importWorkflowUrl', { url: promptResponse.value });
} catch (e) {}
break;
}

View file

@ -28,6 +28,7 @@
:droppable="droppable"
:node="node"
:path="path"
:event-bus="eventBus"
@input="valueChanged"
@modalOpenerClick="openExpressionEditorModal"
@focus="setFocus"
@ -391,8 +392,8 @@ import { useCredentialsStore } from '@/stores/credentials.store';
import { useSettingsStore } from '@/stores/settings.store';
import { htmlEditorEventBus } from '@/event-bus';
import Vue from 'vue';
type ResourceLocatorRef = InstanceType<typeof ResourceLocator>;
import type { EventBus } from 'n8n-design-system/utils';
import { createEventBus } from 'n8n-design-system/utils';
export default defineComponent({
name: 'parameter-input',
@ -463,6 +464,10 @@ export default defineComponent({
size: 'small',
}),
},
eventBus: {
type: Object as PropType<EventBus>,
default: () => createEventBus(),
},
},
data() {
return {
@ -1112,9 +1117,7 @@ export default defineComponent({
}
} else if (command === 'refreshOptions') {
if (this.isResourceLocatorParameter) {
const resourceLocatorRef = this.$refs.resourceLocator as ResourceLocatorRef | undefined;
resourceLocatorRef?.$emit('refreshList');
this.eventBus.emit('refreshList');
}
void this.loadRemoteParameterOptions();
} else if (command === 'formatHtml') {
@ -1146,7 +1149,7 @@ export default defineComponent({
});
},
mounted() {
this.$on('optionSelected', this.optionSelected);
this.eventBus.on('optionSelected', this.optionSelected);
this.tempValue = this.displayValue as string;
if (this.node !== null) {
@ -1186,6 +1189,9 @@ export default defineComponent({
inputFieldRef: this.$refs['inputField'],
});
},
beforeDestroy() {
this.eventBus.off('optionSelected', this.optionSelected);
},
});
</script>

View file

@ -32,6 +32,7 @@
:isForCredential="true"
:eventSource="eventSource"
:hint="!showRequiredErrors ? hint : ''"
:event-bus="eventBus"
@focus="onFocus"
@blur="onBlur"
@textInput="valueChanged"
@ -65,6 +66,7 @@ import { isValueExpression } from '@/utils';
import type { INodeParameterResourceLocator, INodeProperties, IParameterLabel } from 'n8n-workflow';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { createEventBus } from 'n8n-design-system/utils';
type ParamRef = InstanceType<typeof ParameterInputWrapper>;
@ -100,6 +102,7 @@ export default defineComponent({
focused: false,
blurredEver: false,
menuExpanded: false,
eventBus: createEventBus(),
};
},
computed: {
@ -147,9 +150,7 @@ export default defineComponent({
this.menuExpanded = expanded;
},
optionSelected(command: string) {
if (this.$refs.param) {
(this.$refs.param as ParamRef).$emit('optionSelected', command);
}
this.eventBus.emit('optionSelected', command);
},
valueChanged(parameterData: IUpdateInformation) {
this.$emit('change', parameterData);

View file

@ -56,6 +56,7 @@
:hint="hint"
:hide-issues="hideIssues"
:label="label"
:event-bus="eventBus"
@valueChanged="valueChanged"
@textInput="onTextInput"
@focus="onFocus"
@ -98,6 +99,7 @@ import { useNDVStore } from '@/stores/ndv.store';
import { useSegment } from '@/stores/segment.store';
import { externalHooks } from '@/mixins/externalHooks';
import { getMappedResult } from '@/utils/mappingUtils';
import { createEventBus } from 'n8n-design-system/utils';
type ParameterInputWrapperRef = InstanceType<typeof ParameterInputWrapper>;
@ -112,7 +114,10 @@ export default defineComponent({
ParameterInputWrapper,
},
setup() {
const eventBus = createEventBus();
return {
eventBus,
...useToast(),
};
},
@ -234,17 +239,14 @@ export default defineComponent({
this.menuExpanded = expanded;
},
optionSelected(command: string) {
const paramRef = this.$refs.param as ParameterInputWrapperRef | undefined;
paramRef?.$emit('optionSelected', command);
this.eventBus.emit('optionSelected', command);
},
valueChanged(parameterData: IUpdateInformation) {
this.$emit('valueChanged', parameterData);
},
onTextInput(parameterData: IUpdateInformation) {
const paramRef = this.$refs.param as ParameterInputWrapperRef | undefined;
if (isValueExpression(this.parameter, parameterData.value)) {
paramRef?.$emit('optionSelected', 'addExpression');
this.eventBus.emit('optionSelected', 'addExpression');
}
},
onDrop(newParamValue: string) {

View file

@ -18,6 +18,7 @@
:expressionEvaluated="expressionValueComputed"
:label="label"
:data-test-id="`parameter-input-${parameter.name}`"
:event-bus="internalEventBus"
@focus="onFocus"
@blur="onBlur"
@drop="onDrop"
@ -61,8 +62,8 @@ import type { INodeUi, IUpdateInformation, TargetItem } from '@/Interface';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { isValueExpression } from '@/utils';
import { useNDVStore } from '@/stores/ndv.store';
type ParamRef = InstanceType<typeof ParameterInput>;
import type { EventBus } from 'n8n-design-system/utils';
import { createEventBus } from 'n8n-design-system/utils';
export default defineComponent({
name: 'parameter-input-wrapper',
@ -71,8 +72,16 @@ export default defineComponent({
ParameterInput,
InputHint,
},
data() {
return {
internalEventBus: createEventBus(),
};
},
mounted() {
this.$on('optionSelected', this.optionSelected);
this.eventBus.on('optionSelected', this.optionSelected);
},
beforeDestroy() {
this.eventBus.off('optionSelected', this.optionSelected);
},
props: {
isReadOnly: {
@ -124,6 +133,10 @@ export default defineComponent({
size: 'small',
}),
},
eventBus: {
type: Object as PropType<EventBus>,
default: () => createEventBus(),
},
},
computed: {
...mapStores(useNDVStore),
@ -217,9 +230,7 @@ export default defineComponent({
this.$emit('drop', data);
},
optionSelected(command: string) {
const paramRef = this.$refs.param as ParamRef | undefined;
paramRef?.$emit('optionSelected', command);
this.internalEventBus.emit('optionSelected', command);
},
onValueChanged(parameterData: IUpdateInformation) {
this.$emit('valueChanged', parameterData);

View file

@ -15,11 +15,11 @@
:hasMore="currentQueryHasMore"
:errorView="currentQueryError"
:width="width"
:event-bus="eventBus"
@input="onListItemSelected"
@hide="onDropdownHide"
@filter="onSearchFilter"
@loadMore="loadResourcesDebounced"
ref="dropdown"
>
<template #error>
<div :class="$style.error" data-test-id="rlc-error-container">
@ -176,8 +176,8 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { useRootStore } from '@/stores/n8nRoot.store';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
type ResourceLocatorDropdownRef = InstanceType<typeof ResourceLocatorDropdown>;
import { createEventBus } from 'n8n-design-system/utils';
import type { EventBus } from 'n8n-design-system/utils';
interface IResourceLocatorQuery {
results: INodeListSearchItems[];
@ -256,6 +256,10 @@ export default defineComponent({
loadOptionsMethod: {
type: String,
},
eventBus: {
type: Object as PropType<EventBus>,
default: () => createEventBus(),
},
},
data() {
return {
@ -475,17 +479,20 @@ export default defineComponent({
},
},
mounted() {
this.$on('refreshList', this.refreshList);
this.eventBus.on('refreshList', this.refreshList);
window.addEventListener('resize', this.setWidth);
useNDVStore().$subscribe((mutation, state) => {
// Update the width when main panel dimension change
this.setWidth();
});
setTimeout(() => {
this.setWidth();
}, 0);
},
beforeDestroy() {
this.eventBus.off('refreshList', this.refreshList);
window.removeEventListener('resize', this.setWidth);
},
methods: {
@ -510,9 +517,8 @@ export default defineComponent({
this.trackEvent('User refreshed resource locator list');
},
onKeyDown(e: MouseEvent) {
const dropdownRef = this.$refs.dropdown as ResourceLocatorDropdownRef | undefined;
if (dropdownRef && this.showResourceDropdown && !this.isSearchable) {
dropdownRef.$emit('keyDown', e);
if (this.showResourceDropdown && !this.isSearchable) {
this.eventBus.emit('keyDown', e);
}
},
openResource(url: string) {

View file

@ -82,6 +82,8 @@
import type { IResourceLocatorResultExpanded } from '@/Interface';
import { defineComponent } from 'vue';
import type { PropType } from 'vue';
import type { EventBus } from 'n8n-design-system/utils';
import { createEventBus } from 'n8n-design-system/utils';
const SEARCH_BAR_HEIGHT_PX = 40;
const SCROLL_MARGIN_PX = 10;
@ -120,6 +122,10 @@ export default defineComponent({
width: {
type: Number,
},
eventBus: {
type: Object as PropType<EventBus>,
default: () => createEventBus(),
},
},
data() {
return {
@ -128,7 +134,10 @@ export default defineComponent({
};
},
mounted() {
this.$on('keyDown', this.onKeyDown);
this.eventBus.on('keyDown', this.onKeyDown);
},
beforeDestroy() {
this.eventBus.off('keyDown', this.onKeyDown);
},
computed: {
sortedResources(): IResourceLocatorResultExpanded[] {

View file

@ -4,6 +4,7 @@
@observed="onObserved"
class="tags-container"
:enabled="responsive"
:event-bus="intersectionEventBus"
>
<template>
<span class="tags">
@ -26,6 +27,7 @@
:class="{ hidden: tag.hidden }"
:data-id="tag.id"
:enabled="responsive"
:event-bus="intersectionEventBus"
v-else
>
<el-tag :title="tag.name" type="info" size="small" :class="{ hoverable }">
@ -46,6 +48,7 @@ import IntersectionObserver from './IntersectionObserver.vue';
import IntersectionObserved from './IntersectionObserved.vue';
import { mapStores } from 'pinia';
import { useTagsStore } from '@/stores/tags.store';
import { createEventBus } from 'n8n-design-system/utils';
// random upper limit if none is set to minimize performance impact of observers
const DEFAULT_MAX_TAGS_LIMIT = 20;
@ -62,6 +65,7 @@ export default defineComponent({
props: ['tagIds', 'limit', 'clickable', 'responsive', 'hoverable'],
data() {
return {
intersectionEventBus: createEventBus(),
visibility: {} as { [id: string]: boolean },
};
},

View file

@ -3,13 +3,14 @@
* unsafe onclick attribute
*/
import { reactive, del, computed, onMounted, onUnmounted, getCurrentInstance } from 'vue';
import { globalLinkActionsEventBus } from '@/event-bus';
const state = reactive({
customActions: {} as Record<string, Function>,
});
export default () => {
function registerCustomAction(key: string, action: Function) {
function registerCustomAction({ key, action }: { key: string; action: Function }) {
state.customActions[key] = action;
}
function unregisterCustomAction(key: string) {
@ -42,13 +43,15 @@ export default () => {
onMounted(() => {
const instance = getCurrentInstance();
window.addEventListener('click', delegateClick);
instance?.proxy.$root.$on('registerGlobalLinkAction', registerCustomAction);
globalLinkActionsEventBus.on('registerGlobalLinkAction', registerCustomAction);
});
onUnmounted(() => {
const instance = getCurrentInstance();
window.removeEventListener('click', delegateClick);
instance?.proxy.$root.$off('registerGlobalLinkAction', registerCustomAction);
globalLinkActionsEventBus.off('registerGlobalLinkAction', registerCustomAction);
});
return {

View file

@ -1,4 +1,5 @@
export * from './code-node-editor';
export * from './data-pinning';
export * from './link-actions';
export * from './html-editor';
export * from './node-view';

View file

@ -0,0 +1,3 @@
import { createEventBus } from 'n8n-design-system';
export const globalLinkActionsEventBus = createEventBus();

View file

@ -1,50 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { defineComponent } from 'vue';
function broadcast(
this: InstanceType<typeof EmitterMixin>,
componentName: string,
eventName: string,
params: any,
) {
this.$children.forEach((child) => {
const name = child.$options.name;
if (name === componentName) {
// eslint-disable-next-line prefer-spread
child.$emit.apply(child, [eventName].concat(params) as Parameters<typeof child.$emit>);
} else {
broadcast.apply(
child as InstanceType<typeof EmitterMixin>,
[componentName, eventName].concat([params]) as Parameters<typeof broadcast>,
);
}
});
}
const EmitterMixin = defineComponent({
methods: {
$dispatch(componentName: string, eventName: string, params: any) {
let parent = this.$parent || this.$root;
let name = parent.$options.name;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent as InstanceType<typeof EmitterMixin>;
if (parent) {
name = parent.$options.name;
}
}
if (parent) {
// eslint-disable-next-line prefer-spread
parent.$emit.apply(parent, [eventName].concat(params) as Parameters<typeof parent.$emit>);
}
},
$broadcast(componentName: string, eventName: string, params: any) {
broadcast.call(this, componentName, eventName, params);
},
},
});
export default EmitterMixin;

View file

@ -24,7 +24,7 @@ import { TelemetryHelpers } from 'n8n-workflow';
import { WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants';
import { getTriggerNodeServiceName } from '@/utils';
import { codeNodeEditorEventBus } from '@/event-bus';
import { codeNodeEditorEventBus, globalLinkActionsEventBus } from '@/event-bus';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
@ -351,9 +351,12 @@ export const pushConnection = defineComponent({
let action;
if (!isSavingExecutions) {
this.$root.$emit('registerGlobalLinkAction', 'open-settings', async () => {
if (this.workflowsStore.isNewWorkflow) await this.saveAsNewWorkflow();
this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
globalLinkActionsEventBus.emit('registerGlobalLinkAction', {
key: 'open-settings',
action: async () => {
if (this.workflowsStore.isNewWorkflow) await this.saveAsNewWorkflow();
this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
},
});
action =

View file

@ -672,9 +672,12 @@ export default defineComponent({
? this.$locale.baseText('nodeView.addOrEnableTriggerNode')
: this.$locale.baseText('nodeView.addATriggerNodeFirst');
this.registerCustomAction('showNodeCreator', () =>
this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.NO_TRIGGER_EXECUTION_TOOLTIP),
);
this.registerCustomAction({
key: 'showNodeCreator',
action: () =>
this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.NO_TRIGGER_EXECUTION_TOOLTIP),
});
const notice = this.showMessage({
type: 'info',
title: this.$locale.baseText('nodeView.cantExecuteNoTrigger'),
@ -1050,7 +1053,7 @@ export default defineComponent({
}
if (this.$router.currentRoute.name === VIEWS.NEW_WORKFLOW) {
this.$root.$emit('newWorkflow');
nodeViewEventBus.emit('newWorkflow');
} else {
void this.$router.push({ name: VIEWS.NEW_WORKFLOW });
}
@ -3913,9 +3916,9 @@ export default defineComponent({
window.addEventListener('message', this.onPostMessageReceived);
window.addEventListener('pageshow', this.onPageShow);
this.$root.$on('newWorkflow', this.newWorkflow);
this.$root.$on('importWorkflowData', this.onImportWorkflowDataEvent);
this.$root.$on('importWorkflowUrl', this.onImportWorkflowUrlEvent);
nodeViewEventBus.on('newWorkflow', this.newWorkflow);
nodeViewEventBus.on('importWorkflowData', this.onImportWorkflowDataEvent);
nodeViewEventBus.on('importWorkflowUrl', this.onImportWorkflowUrlEvent);
historyBus.on('nodeMove', this.onMoveNode);
historyBus.on('revertAddNode', this.onRevertAddNode);
historyBus.on('revertRemoveNode', this.onRevertRemoveNode);
@ -3938,9 +3941,9 @@ export default defineComponent({
window.removeEventListener('beforeunload', this.onBeforeUnload);
window.removeEventListener('pageshow', this.onPageShow);
this.$root.$off('newWorkflow', this.newWorkflow);
this.$root.$off('importWorkflowData', this.onImportWorkflowDataEvent);
this.$root.$off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
nodeViewEventBus.off('newWorkflow', this.newWorkflow);
nodeViewEventBus.off('importWorkflowData', this.onImportWorkflowDataEvent);
nodeViewEventBus.off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
historyBus.off('nodeMove', this.onMoveNode);
historyBus.off('revertAddNode', this.onRevertAddNode);
historyBus.off('revertRemoveNode', this.onRevertRemoveNode);
@ -3959,9 +3962,9 @@ export default defineComponent({
this.instance.destroy();
this.uiStore.stateIsDirty = false;
window.removeEventListener('message', this.onPostMessageReceived);
this.$root.$off('newWorkflow', this.newWorkflow);
this.$root.$off('importWorkflowData', this.onImportWorkflowDataEvent);
this.$root.$off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
nodeViewEventBus.off('newWorkflow', this.newWorkflow);
nodeViewEventBus.off('importWorkflowData', this.onImportWorkflowDataEvent);
nodeViewEventBus.off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
},
});