refactor(editor): Apply Prettier (no-changelog) (#4920)
* ⚡ Adjust `format` script * 🔥 Remove exemption for `editor-ui` * 🎨 Prettify * 👕 Fix lint
This commit is contained in:
@@ -6,7 +6,7 @@ import Vue from 'vue';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
export const copyPaste = Vue.extend({
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
copyPasteElementsGotCreated: false,
|
||||
hiddenInput: null as null | Element,
|
||||
@@ -14,7 +14,7 @@ export const copyPaste = Vue.extend({
|
||||
onBeforePaste: null as null | Function,
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
mounted() {
|
||||
if (this.copyPasteElementsGotCreated === true) {
|
||||
return;
|
||||
}
|
||||
@@ -49,8 +49,14 @@ export const copyPaste = Vue.extend({
|
||||
|
||||
// Code is mainly from
|
||||
// https://www.lucidchart.com/techblog/2014/12/02/definitive-guide-copying-pasting-javascript/
|
||||
const isSafari = navigator.appVersion.search('Safari') !== -1 && navigator.appVersion.search('Chrome') === -1 && navigator.appVersion.search('CrMo') === -1 && navigator.appVersion.search('CriOS') === -1;
|
||||
const isIe = (navigator.userAgent.toLowerCase().indexOf('msie') !== -1 || navigator.userAgent.toLowerCase().indexOf('trident') !== -1);
|
||||
const isSafari =
|
||||
navigator.appVersion.search('Safari') !== -1 &&
|
||||
navigator.appVersion.search('Chrome') === -1 &&
|
||||
navigator.appVersion.search('CrMo') === -1 &&
|
||||
navigator.appVersion.search('CriOS') === -1;
|
||||
const isIe =
|
||||
navigator.userAgent.toLowerCase().indexOf('msie') !== -1 ||
|
||||
navigator.userAgent.toLowerCase().indexOf('trident') !== -1;
|
||||
|
||||
const hiddenInput = document.createElement('input');
|
||||
hiddenInput.setAttribute('type', 'text');
|
||||
@@ -69,7 +75,7 @@ export const copyPaste = Vue.extend({
|
||||
ieClipboardDiv.setAttribute('contenteditable', 'true');
|
||||
document.body.append(ieClipboardDiv);
|
||||
|
||||
this.onBeforePaste = () => {
|
||||
this.onBeforePaste = () => {
|
||||
// @ts-ignore
|
||||
if (hiddenInput.is(':focus')) {
|
||||
this.focusIeClipboardDiv(ieClipboardDiv as HTMLDivElement);
|
||||
@@ -80,7 +86,7 @@ export const copyPaste = Vue.extend({
|
||||
}
|
||||
|
||||
let userInput = '';
|
||||
const hiddenInputListener = (text: string) => { };
|
||||
const hiddenInputListener = (text: string) => {};
|
||||
|
||||
hiddenInput.addEventListener('input', (e) => {
|
||||
const value = hiddenInput.value;
|
||||
@@ -91,52 +97,65 @@ export const copyPaste = Vue.extend({
|
||||
// the input event, so we update the input area after the event is done being processed
|
||||
if (isSafari) {
|
||||
hiddenInput.focus();
|
||||
setTimeout(() => { this.focusHiddenArea(hiddenInput); }, 0);
|
||||
setTimeout(() => {
|
||||
this.focusHiddenArea(hiddenInput);
|
||||
}, 0);
|
||||
} else {
|
||||
this.focusHiddenArea(hiddenInput);
|
||||
}
|
||||
});
|
||||
|
||||
this.onPaste = debounce((e) => {
|
||||
const event = 'paste';
|
||||
// Check if the event got emitted from a message box or from something
|
||||
// else which should ignore the copy/paste
|
||||
// @ts-ignore
|
||||
const path = e.path || (e.composedPath && e.composedPath());
|
||||
for (let index = 0; index < path.length; index++) {
|
||||
if (path[index].className && typeof path[index].className === 'string' && (
|
||||
path[index].className.includes('el-message-box') || path[index].className.includes('ignore-key-press')
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ieClipboardDiv !== null) {
|
||||
this.ieClipboardEvent(event, ieClipboardDiv);
|
||||
} else {
|
||||
this.standardClipboardEvent(event, e as ClipboardEvent);
|
||||
this.onPaste = debounce(
|
||||
(e) => {
|
||||
const event = 'paste';
|
||||
// Check if the event got emitted from a message box or from something
|
||||
// else which should ignore the copy/paste
|
||||
// @ts-ignore
|
||||
if (!document.activeElement || (document.activeElement && ['textarea', 'text', 'email', 'password'].indexOf(document.activeElement.type) === -1)) {
|
||||
// That it still allows to paste into text, email, password & textarea-fields we
|
||||
// check if we can identify the active element and if so only
|
||||
// run it if something else is selected.
|
||||
this.focusHiddenArea(hiddenInput);
|
||||
e.preventDefault();
|
||||
const path = e.path || (e.composedPath && e.composedPath());
|
||||
for (let index = 0; index < path.length; index++) {
|
||||
if (
|
||||
path[index].className &&
|
||||
typeof path[index].className === 'string' &&
|
||||
(path[index].className.includes('el-message-box') ||
|
||||
path[index].className.includes('ignore-key-press'))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1000, { leading: true });
|
||||
|
||||
if (ieClipboardDiv !== null) {
|
||||
this.ieClipboardEvent(event, ieClipboardDiv);
|
||||
} else {
|
||||
this.standardClipboardEvent(event, e as ClipboardEvent);
|
||||
// @ts-ignore
|
||||
if (
|
||||
!document.activeElement ||
|
||||
(document.activeElement &&
|
||||
['textarea', 'text', 'email', 'password'].indexOf(document.activeElement.type) === -1)
|
||||
) {
|
||||
// That it still allows to paste into text, email, password & textarea-fields we
|
||||
// check if we can identify the active element and if so only
|
||||
// run it if something else is selected.
|
||||
this.focusHiddenArea(hiddenInput);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
1000,
|
||||
{ leading: true },
|
||||
);
|
||||
|
||||
// Set clipboard event listeners on the document.
|
||||
// @ts-ignore
|
||||
document.addEventListener('paste', this.onPaste);
|
||||
},
|
||||
methods: {
|
||||
receivedCopyPasteData (plainTextData: string, event?: ClipboardEvent): void {
|
||||
receivedCopyPasteData(plainTextData: string, event?: ClipboardEvent): void {
|
||||
// THIS HAS TO BE DEFINED IN COMPONENT!
|
||||
},
|
||||
|
||||
// For every browser except IE, we can easily get and set data on the clipboard
|
||||
standardClipboardEvent (clipboardEventName: string, event: ClipboardEvent) {
|
||||
standardClipboardEvent(clipboardEventName: string, event: ClipboardEvent) {
|
||||
const clipboardData = event.clipboardData;
|
||||
if (clipboardData !== null && clipboardEventName === 'paste') {
|
||||
const clipboardText = clipboardData.getData('text/plain');
|
||||
@@ -145,7 +164,7 @@ export const copyPaste = Vue.extend({
|
||||
},
|
||||
|
||||
// For IE, we can get/set Text or URL just as we normally would
|
||||
ieClipboardEvent (clipboardEventName: string, ieClipboardDiv: HTMLDivElement) {
|
||||
ieClipboardEvent(clipboardEventName: string, ieClipboardDiv: HTMLDivElement) {
|
||||
// @ts-ignore
|
||||
const clipboardData = window.clipboardData;
|
||||
if (clipboardEventName === 'paste') {
|
||||
@@ -157,11 +176,11 @@ export const copyPaste = Vue.extend({
|
||||
},
|
||||
|
||||
// Focuses an element to be ready for copy/paste (used exclusively for IE)
|
||||
focusIeClipboardDiv (ieClipboardDiv: HTMLDivElement) {
|
||||
focusIeClipboardDiv(ieClipboardDiv: HTMLDivElement) {
|
||||
ieClipboardDiv.focus();
|
||||
const range = document.createRange();
|
||||
// @ts-ignore
|
||||
range.selectNodeContents((ieClipboardDiv.get(0)));
|
||||
range.selectNodeContents(ieClipboardDiv.get(0));
|
||||
const selection = window.getSelection();
|
||||
if (selection !== null) {
|
||||
selection.removeAllRanges();
|
||||
@@ -169,7 +188,7 @@ export const copyPaste = Vue.extend({
|
||||
}
|
||||
},
|
||||
|
||||
focusHiddenArea (hiddenInput: HTMLInputElement) {
|
||||
focusHiddenArea(hiddenInput: HTMLInputElement) {
|
||||
// In order to ensure that the browser will fire clipboard events, we always need to have something selected
|
||||
hiddenInput.value = ' ';
|
||||
hiddenInput.focus();
|
||||
@@ -179,7 +198,7 @@ export const copyPaste = Vue.extend({
|
||||
/**
|
||||
* Copies given data to clipboard
|
||||
*/
|
||||
copyToClipboard (value: string): void {
|
||||
copyToClipboard(value: string): void {
|
||||
// FROM: https://hackernoon.com/copying-text-to-clipboard-with-javascript-df4d4988697f
|
||||
const element = document.createElement('textarea'); // Create a <textarea> element
|
||||
element.value = value; // Set its value to the string that you want copied
|
||||
@@ -193,9 +212,10 @@ export const copyPaste = Vue.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
const selected = selection.rangeCount > 0 // Check if there is any content selected previously
|
||||
? selection.getRangeAt(0) // Store selection if found
|
||||
: false; // Mark as false to know no selection existed before
|
||||
const selected =
|
||||
selection.rangeCount > 0 // Check if there is any content selected previously
|
||||
? selection.getRangeAt(0) // Store selection if found
|
||||
: false; // Mark as false to know no selection existed before
|
||||
element.select(); // Select the <textarea> content
|
||||
document.execCommand('copy'); // Copy - only works as a result of a user action (e.g. click events)
|
||||
document.body.removeChild(element); // Remove the <textarea> element
|
||||
@@ -205,7 +225,6 @@ export const copyPaste = Vue.extend({
|
||||
selection.addRange(selected); // Restore the original selection
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.hiddenInput) {
|
||||
|
||||
@@ -2,20 +2,25 @@ import { debounce } from 'lodash';
|
||||
import Vue from 'vue';
|
||||
|
||||
export const debounceHelper = Vue.extend({
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
debouncedFunctions: [] as any[], // tslint:disable-line:no-any
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async callDebounced (...inputParameters: any[]): Promise<void> { // tslint:disable-line:no-any
|
||||
// tslint:disable-next-line:no-any
|
||||
async callDebounced(...inputParameters: any[]): Promise<void> {
|
||||
const functionName = inputParameters.shift() as string;
|
||||
const { trailing, debounceTime } = inputParameters.shift();
|
||||
const { trailing, debounceTime } = inputParameters.shift();
|
||||
|
||||
// @ts-ignore
|
||||
if (this.debouncedFunctions[functionName] === undefined) {
|
||||
// @ts-ignore
|
||||
this.debouncedFunctions[functionName] = debounce(this[functionName], debounceTime, trailing ? { trailing } : { leading: true } );
|
||||
this.debouncedFunctions[functionName] = debounce(
|
||||
this[functionName],
|
||||
debounceTime,
|
||||
trailing ? { trailing } : { leading: true },
|
||||
);
|
||||
}
|
||||
// @ts-ignore
|
||||
await this.debouncedFunctions[functionName].apply(this, inputParameters);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
function broadcast(componentName: string, eventName: string, params: any) { // tslint:disable-line:no-any
|
||||
// tslint:disable-next-line:no-any
|
||||
function broadcast(componentName: string, eventName: string, params: any) {
|
||||
// @ts-ignore
|
||||
(this as Vue).$children.forEach(child => {
|
||||
(this as Vue).$children.forEach((child) => {
|
||||
const name = child.$options.name;
|
||||
|
||||
if (name === componentName) {
|
||||
@@ -18,7 +19,8 @@ function broadcast(componentName: string, eventName: string, params: any) { // t
|
||||
|
||||
export default Vue.extend({
|
||||
methods: {
|
||||
$dispatch(componentName: string, eventName: string, params: any) { // tslint:disable-line:no-any
|
||||
// tslint:disable-next-line:no-any
|
||||
$dispatch(componentName: string, eventName: string, params: any) {
|
||||
let parent = this.$parent || this.$root;
|
||||
let name = parent.$options.name;
|
||||
|
||||
@@ -35,7 +37,8 @@ export default Vue.extend({
|
||||
parent.$emit.apply(parent, [eventName].concat(params));
|
||||
}
|
||||
},
|
||||
$broadcast(componentName: string, eventName: string, params: any) { // tslint:disable-line:no-any
|
||||
// tslint:disable-next-line:no-any
|
||||
$broadcast(componentName: string, eventName: string, params: any) {
|
||||
broadcast.call(this, componentName, eventName, params);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { IExecutionsSummary } from "@/Interface";
|
||||
import { useWorkflowsStore } from "@/stores/workflows";
|
||||
import dateFormat from "dateformat";
|
||||
import { mapStores } from "pinia";
|
||||
import mixins from "vue-typed-mixins";
|
||||
import { genericHelpers } from "./genericHelpers";
|
||||
import { IExecutionsSummary } from '@/Interface';
|
||||
import { useWorkflowsStore } from '@/stores/workflows';
|
||||
import dateFormat from 'dateformat';
|
||||
import { mapStores } from 'pinia';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { genericHelpers } from './genericHelpers';
|
||||
|
||||
export interface IExecutionUIData {
|
||||
name: string;
|
||||
@@ -14,16 +14,14 @@ export interface IExecutionUIData {
|
||||
|
||||
export const executionHelpers = mixins(genericHelpers).extend({
|
||||
computed: {
|
||||
...mapStores(
|
||||
useWorkflowsStore,
|
||||
),
|
||||
...mapStores(useWorkflowsStore),
|
||||
executionId(): string {
|
||||
return this.$route.params.executionId;
|
||||
},
|
||||
workflowName (): string {
|
||||
workflowName(): string {
|
||||
return this.workflowsStore.workflowName;
|
||||
},
|
||||
currentWorkflow (): string {
|
||||
currentWorkflow(): string {
|
||||
return this.$route.params.name || this.workflowsStore.workflowId;
|
||||
},
|
||||
executions(): IExecutionsSummary[] {
|
||||
@@ -48,18 +46,27 @@ export const executionHelpers = mixins(genericHelpers).extend({
|
||||
} else if (execution.stoppedAt === undefined) {
|
||||
status.name = 'running';
|
||||
status.label = this.$locale.baseText('executionsList.running');
|
||||
status.runningTime = this.displayTimer(new Date().getTime() - new Date(execution.startedAt).getTime(), true);
|
||||
status.runningTime = this.displayTimer(
|
||||
new Date().getTime() - new Date(execution.startedAt).getTime(),
|
||||
true,
|
||||
);
|
||||
} else if (execution.finished) {
|
||||
status.name = 'success';
|
||||
status.label = this.$locale.baseText('executionsList.succeeded');
|
||||
if (execution.stoppedAt) {
|
||||
status.runningTime = this.displayTimer(new Date(execution.stoppedAt).getTime() - new Date(execution.startedAt).getTime(), true);
|
||||
status.runningTime = this.displayTimer(
|
||||
new Date(execution.stoppedAt).getTime() - new Date(execution.startedAt).getTime(),
|
||||
true,
|
||||
);
|
||||
}
|
||||
} else if (execution.stoppedAt !== null) {
|
||||
status.name = 'error';
|
||||
status.label = this.$locale.baseText('executionsList.error');
|
||||
if (execution.stoppedAt) {
|
||||
status.runningTime = this.displayTimer(new Date(execution.stoppedAt).getTime() - new Date(execution.startedAt).getTime(), true);
|
||||
status.runningTime = this.displayTimer(
|
||||
new Date(execution.stoppedAt).getTime() - new Date(execution.startedAt).getTime(),
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,15 +6,14 @@ import Vue from 'vue';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
n8nExternalHooks?: Record<string, Record<string, Array<(store: Store, metadata?: IDataObject) => Promise<void>>>>;
|
||||
n8nExternalHooks?: Record<
|
||||
string,
|
||||
Record<string, Array<(store: Store, metadata?: IDataObject) => Promise<void>>>
|
||||
>;
|
||||
}
|
||||
}
|
||||
|
||||
export async function runExternalHook(
|
||||
eventName: string,
|
||||
store: Store,
|
||||
metadata?: IDataObject,
|
||||
) {
|
||||
export async function runExternalHook(eventName: string, store: Store, metadata?: IDataObject) {
|
||||
if (!window.n8nExternalHooks) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,18 +4,18 @@ import { VIEWS } from '@/constants';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
export const genericHelpers = mixins(showMessage).extend({
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
loadingService: null as any | null, // tslint:disable-line:no-any
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isReadOnly (): boolean {
|
||||
isReadOnly(): boolean {
|
||||
return ![VIEWS.WORKFLOW, VIEWS.NEW_WORKFLOW].includes(this.$route.name as VIEWS);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
displayTimer (msPassed: number, showMs = false): string {
|
||||
displayTimer(msPassed: number, showMs = false): string {
|
||||
if (msPassed < 60000) {
|
||||
if (!showMs) {
|
||||
return `${Math.floor(msPassed / 1000)} ${this.$locale.baseText('genericHelpers.sec')}`;
|
||||
@@ -26,11 +26,11 @@ export const genericHelpers = mixins(showMessage).extend({
|
||||
|
||||
const secondsPassed = Math.floor(msPassed / 1000);
|
||||
const minutesPassed = Math.floor(secondsPassed / 60);
|
||||
const secondsLeft = (secondsPassed - (minutesPassed * 60)).toString().padStart(2, '0');
|
||||
const secondsLeft = (secondsPassed - minutesPassed * 60).toString().padStart(2, '0');
|
||||
|
||||
return `${minutesPassed}:${secondsLeft} ${this.$locale.baseText('genericHelpers.min')}`;
|
||||
},
|
||||
editAllowedCheck (): boolean {
|
||||
editAllowedCheck(): boolean {
|
||||
if (this.isReadOnly) {
|
||||
this.$showMessage({
|
||||
// title: 'Workflow can not be changed!',
|
||||
@@ -45,25 +45,23 @@ export const genericHelpers = mixins(showMessage).extend({
|
||||
return true;
|
||||
},
|
||||
|
||||
startLoading (text?: string) {
|
||||
startLoading(text?: string) {
|
||||
if (this.loadingService !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
this.loadingService = this.$loading(
|
||||
{
|
||||
lock: true,
|
||||
text: text || this.$locale.baseText('genericHelpers.loading'),
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(255, 255, 255, 0.8)',
|
||||
},
|
||||
);
|
||||
this.loadingService = this.$loading({
|
||||
lock: true,
|
||||
text: text || this.$locale.baseText('genericHelpers.loading'),
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(255, 255, 255, 0.8)',
|
||||
});
|
||||
},
|
||||
setLoadingText (text: string) {
|
||||
setLoadingText(text: string) {
|
||||
this.loadingService.text = text;
|
||||
},
|
||||
stopLoading () {
|
||||
stopLoading() {
|
||||
if (this.loadingService !== null) {
|
||||
this.loadingService.close();
|
||||
this.loadingService = null;
|
||||
|
||||
@@ -16,12 +16,7 @@ const UNDO_REDO_DEBOUNCE_INTERVAL = 100;
|
||||
|
||||
export const historyHelper = mixins(debounceHelper, deviceSupportHelpers).extend({
|
||||
computed: {
|
||||
...mapStores(
|
||||
useNDVStore,
|
||||
useHistoryStore,
|
||||
useUIStore,
|
||||
useWorkflowsStore,
|
||||
),
|
||||
...mapStores(useNDVStore, useHistoryStore, useUIStore, useWorkflowsStore),
|
||||
isNDVOpen(): boolean {
|
||||
return this.ndvStore.activeNodeName !== null;
|
||||
},
|
||||
@@ -41,9 +36,15 @@ export const historyHelper = mixins(debounceHelper, deviceSupportHelpers).extend
|
||||
event.preventDefault();
|
||||
if (!this.isNDVOpen) {
|
||||
if (event.shiftKey) {
|
||||
this.callDebounced('redo', { debounceTime: UNDO_REDO_DEBOUNCE_INTERVAL, trailing: true });
|
||||
this.callDebounced('redo', {
|
||||
debounceTime: UNDO_REDO_DEBOUNCE_INTERVAL,
|
||||
trailing: true,
|
||||
});
|
||||
} else {
|
||||
this.callDebounced('undo', { debounceTime: UNDO_REDO_DEBOUNCE_INTERVAL, trailing: true });
|
||||
this.callDebounced('undo', {
|
||||
debounceTime: UNDO_REDO_DEBOUNCE_INTERVAL,
|
||||
trailing: true,
|
||||
});
|
||||
}
|
||||
} else if (!event.shiftKey) {
|
||||
this.trackUndoAttempt(event);
|
||||
@@ -98,11 +99,14 @@ export const historyHelper = mixins(debounceHelper, deviceSupportHelpers).extend
|
||||
}
|
||||
this.trackCommand(command, 'redo');
|
||||
},
|
||||
trackCommand(command: Undoable, type: 'undo'|'redo'): void {
|
||||
trackCommand(command: Undoable, type: 'undo' | 'redo'): void {
|
||||
if (command instanceof Command) {
|
||||
this.$telemetry.track(`User hit ${type}`, { commands_length: 1, commands: [ command.name ] });
|
||||
this.$telemetry.track(`User hit ${type}`, { commands_length: 1, commands: [command.name] });
|
||||
} else if (command instanceof BulkCommand) {
|
||||
this.$telemetry.track(`User hit ${type}`, { commands_length: command.commands.length, commands: command.commands.map(c => c.name) });
|
||||
this.$telemetry.track(`User hit ${type}`, {
|
||||
commands_length: command.commands.length,
|
||||
commands: command.commands.map((c) => c.name),
|
||||
});
|
||||
}
|
||||
},
|
||||
trackUndoAttempt(event: KeyboardEvent) {
|
||||
|
||||
@@ -7,31 +7,32 @@ import { VIEWS } from '@/constants';
|
||||
import { mapStores } from 'pinia';
|
||||
import { useUIStore } from '@/stores/ui';
|
||||
import { useWorkflowsStore } from '@/stores/workflows';
|
||||
import { getMousePosition, getRelativePosition, HEADER_HEIGHT, SIDEBAR_WIDTH, SIDEBAR_WIDTH_EXPANDED } from '@/utils/nodeViewUtils';
|
||||
import {
|
||||
getMousePosition,
|
||||
getRelativePosition,
|
||||
HEADER_HEIGHT,
|
||||
SIDEBAR_WIDTH,
|
||||
SIDEBAR_WIDTH_EXPANDED,
|
||||
} from '@/utils/nodeViewUtils';
|
||||
|
||||
export const mouseSelect = mixins(
|
||||
deviceSupportHelpers,
|
||||
).extend({
|
||||
data () {
|
||||
export const mouseSelect = mixins(deviceSupportHelpers).extend({
|
||||
data() {
|
||||
return {
|
||||
selectActive: false,
|
||||
selectBox: document.createElement('span'),
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
mounted() {
|
||||
this.createSelectBox();
|
||||
},
|
||||
computed: {
|
||||
...mapStores(
|
||||
useUIStore,
|
||||
useWorkflowsStore,
|
||||
),
|
||||
isDemo (): boolean {
|
||||
...mapStores(useUIStore, useWorkflowsStore),
|
||||
isDemo(): boolean {
|
||||
return this.$route.name === VIEWS.DEMO;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
createSelectBox () {
|
||||
createSelectBox() {
|
||||
this.selectBox.id = 'select-box';
|
||||
this.selectBox.style.margin = '0px auto';
|
||||
this.selectBox.style.border = '2px dotted #FF0000';
|
||||
@@ -45,7 +46,7 @@ export const mouseSelect = mixins(
|
||||
const nodeViewEl = this.$el.querySelector('#node-view') as HTMLDivElement;
|
||||
nodeViewEl.appendChild(this.selectBox);
|
||||
},
|
||||
isCtrlKeyPressed (e: MouseEvent | KeyboardEvent): boolean {
|
||||
isCtrlKeyPressed(e: MouseEvent | KeyboardEvent): boolean {
|
||||
if (this.isTouchDevice === true) {
|
||||
return true;
|
||||
}
|
||||
@@ -54,16 +55,25 @@ export const mouseSelect = mixins(
|
||||
}
|
||||
return e.ctrlKey;
|
||||
},
|
||||
getMousePositionWithinNodeView (event: MouseEvent | TouchEvent): XYPosition {
|
||||
getMousePositionWithinNodeView(event: MouseEvent | TouchEvent): XYPosition {
|
||||
const [x, y] = getMousePosition(event);
|
||||
const sidebarOffset = this.isDemo ? 0 : this.uiStore.sidebarMenuCollapsed ? SIDEBAR_WIDTH : SIDEBAR_WIDTH_EXPANDED;
|
||||
const sidebarOffset = this.isDemo
|
||||
? 0
|
||||
: this.uiStore.sidebarMenuCollapsed
|
||||
? SIDEBAR_WIDTH
|
||||
: SIDEBAR_WIDTH_EXPANDED;
|
||||
const headerOffset = this.isDemo ? 0 : HEADER_HEIGHT;
|
||||
// @ts-ignore
|
||||
return getRelativePosition(x - sidebarOffset, y - headerOffset, this.nodeViewScale, this.uiStore.nodeViewOffsetPosition);
|
||||
return getRelativePosition(
|
||||
x - sidebarOffset,
|
||||
y - headerOffset,
|
||||
this.nodeViewScale,
|
||||
this.uiStore.nodeViewOffsetPosition,
|
||||
);
|
||||
},
|
||||
showSelectBox (event: MouseEvent) {
|
||||
showSelectBox(event: MouseEvent) {
|
||||
const [x, y] = this.getMousePositionWithinNodeView(event);
|
||||
this.selectBox = Object.assign(this.selectBox, {x, y});
|
||||
this.selectBox = Object.assign(this.selectBox, { x, y });
|
||||
|
||||
// @ts-ignore
|
||||
this.selectBox.style.left = this.selectBox.x + 'px';
|
||||
@@ -73,7 +83,7 @@ export const mouseSelect = mixins(
|
||||
|
||||
this.selectActive = true;
|
||||
},
|
||||
updateSelectBox (event: MouseEvent) {
|
||||
updateSelectBox(event: MouseEvent) {
|
||||
const selectionBox = this.getSelectionBox(event);
|
||||
this.selectBox.style.left = selectionBox.x + 'px';
|
||||
this.selectBox.style.top = selectionBox.y + 'px';
|
||||
@@ -81,7 +91,7 @@ export const mouseSelect = mixins(
|
||||
this.selectBox.style.width = selectionBox.width + 'px';
|
||||
this.selectBox.style.height = selectionBox.height + 'px';
|
||||
},
|
||||
hideSelectBox () {
|
||||
hideSelectBox() {
|
||||
this.selectBox.style.visibility = 'hidden';
|
||||
// @ts-ignore
|
||||
this.selectBox.x = 0;
|
||||
@@ -94,7 +104,7 @@ export const mouseSelect = mixins(
|
||||
|
||||
this.selectActive = false;
|
||||
},
|
||||
getSelectionBox (event: MouseEvent) {
|
||||
getSelectionBox(event: MouseEvent) {
|
||||
const [x, y] = this.getMousePositionWithinNodeView(event);
|
||||
return {
|
||||
// @ts-ignore
|
||||
@@ -107,17 +117,23 @@ export const mouseSelect = mixins(
|
||||
height: Math.abs(y - this.selectBox.y),
|
||||
};
|
||||
},
|
||||
getNodesInSelection (event: MouseEvent): INodeUi[] {
|
||||
getNodesInSelection(event: MouseEvent): INodeUi[] {
|
||||
const returnNodes: INodeUi[] = [];
|
||||
const selectionBox = this.getSelectionBox(event);
|
||||
|
||||
// Go through all nodes and check if they are selected
|
||||
this.workflowsStore.allNodes.forEach((node: INodeUi) => {
|
||||
// TODO: Currently always uses the top left corner for checking. Should probably use the center instead
|
||||
if (node.position[0] < selectionBox.x || node.position[0] > (selectionBox.x + selectionBox.width)) {
|
||||
if (
|
||||
node.position[0] < selectionBox.x ||
|
||||
node.position[0] > selectionBox.x + selectionBox.width
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (node.position[1] < selectionBox.y || node.position[1] > (selectionBox.y + selectionBox.height)) {
|
||||
if (
|
||||
node.position[1] < selectionBox.y ||
|
||||
node.position[1] > selectionBox.y + selectionBox.height
|
||||
) {
|
||||
return;
|
||||
}
|
||||
returnNodes.push(node);
|
||||
@@ -125,7 +141,7 @@ export const mouseSelect = mixins(
|
||||
|
||||
return returnNodes;
|
||||
},
|
||||
mouseDownMouseSelect (e: MouseEvent) {
|
||||
mouseDownMouseSelect(e: MouseEvent) {
|
||||
if (this.isCtrlKeyPressed(e) === true) {
|
||||
// We only care about it when the ctrl key is not pressed at the same time.
|
||||
// So we exit when it is pressed.
|
||||
@@ -141,7 +157,7 @@ export const mouseSelect = mixins(
|
||||
// @ts-ignore // Leave like this. Do not add a anonymous function because then remove would not work anymore
|
||||
this.$el.addEventListener('mousemove', this.mouseMoveSelect);
|
||||
},
|
||||
mouseUpMouseSelect (e: MouseEvent) {
|
||||
mouseUpMouseSelect(e: MouseEvent) {
|
||||
if (this.selectActive === false) {
|
||||
if (this.isTouchDevice === true) {
|
||||
// @ts-ignore
|
||||
@@ -173,7 +189,7 @@ export const mouseSelect = mixins(
|
||||
|
||||
this.hideSelectBox();
|
||||
},
|
||||
mouseMoveSelect (e: MouseEvent) {
|
||||
mouseMoveSelect(e: MouseEvent) {
|
||||
if (e.buttons === 0) {
|
||||
// Mouse button is not pressed anymore so stop selection mode
|
||||
// Happens normally when mouse leave the view pressed and then
|
||||
@@ -184,17 +200,17 @@ export const mouseSelect = mixins(
|
||||
|
||||
this.updateSelectBox(e);
|
||||
},
|
||||
nodeDeselected (node: INodeUi) {
|
||||
nodeDeselected(node: INodeUi) {
|
||||
this.uiStore.removeNodeFromSelection(node);
|
||||
// @ts-ignore
|
||||
this.instance.removeFromDragSelection(node.id);
|
||||
},
|
||||
nodeSelected (node: INodeUi) {
|
||||
nodeSelected(node: INodeUi) {
|
||||
this.uiStore.addSelectedNode(node);
|
||||
// @ts-ignore
|
||||
this.instance.addToDragSelection(node.id);
|
||||
},
|
||||
deselectAllNodes () {
|
||||
deselectAllNodes() {
|
||||
// @ts-ignore
|
||||
this.instance.clearDragSelection();
|
||||
this.uiStore.resetSelectedNodes();
|
||||
|
||||
@@ -4,10 +4,8 @@ import { getMousePosition } from '@/utils/nodeViewUtils';
|
||||
import { mapStores } from 'pinia';
|
||||
import { useUIStore } from '@/stores/ui';
|
||||
|
||||
export const moveNodeWorkflow = mixins(
|
||||
deviceSupportHelpers,
|
||||
).extend({
|
||||
data () {
|
||||
export const moveNodeWorkflow = mixins(deviceSupportHelpers).extend({
|
||||
data() {
|
||||
return {
|
||||
moveLastPosition: [0, 0],
|
||||
};
|
||||
@@ -16,7 +14,7 @@ export const moveNodeWorkflow = mixins(
|
||||
...mapStores(useUIStore),
|
||||
},
|
||||
methods: {
|
||||
moveWorkflow (e: MouseEvent) {
|
||||
moveWorkflow(e: MouseEvent) {
|
||||
const offsetPosition = this.uiStore.nodeViewOffsetPosition;
|
||||
|
||||
const [x, y] = getMousePosition(e);
|
||||
@@ -29,7 +27,7 @@ export const moveNodeWorkflow = mixins(
|
||||
this.moveLastPosition[0] = x;
|
||||
this.moveLastPosition[1] = y;
|
||||
},
|
||||
mouseDownMoveWorkflow (e: MouseEvent) {
|
||||
mouseDownMoveWorkflow(e: MouseEvent) {
|
||||
if (this.isCtrlKeyPressed(e) === false) {
|
||||
// We only care about it when the ctrl key is pressed at the same time.
|
||||
// So we exit when it is not pressed.
|
||||
@@ -51,7 +49,7 @@ export const moveNodeWorkflow = mixins(
|
||||
// @ts-ignore
|
||||
this.$el.addEventListener('mousemove', this.mouseMoveNodeWorkflow);
|
||||
},
|
||||
mouseUpMoveWorkflow (e: MouseEvent) {
|
||||
mouseUpMoveWorkflow(e: MouseEvent) {
|
||||
if (this.uiStore.nodeViewMoveInProgress === false) {
|
||||
// If it is not active return directly.
|
||||
// Else normal node dragging will not work.
|
||||
@@ -65,7 +63,7 @@ export const moveNodeWorkflow = mixins(
|
||||
|
||||
// Nothing else to do. Simply leave the node view at the current offset
|
||||
},
|
||||
mouseMoveNodeWorkflow (e: MouseEvent) {
|
||||
mouseMoveNodeWorkflow(e: MouseEvent) {
|
||||
// @ts-ignore
|
||||
if (e.target && !e.target.id.includes('node-view')) {
|
||||
return;
|
||||
|
||||
@@ -5,14 +5,9 @@ import { mapStores } from 'pinia';
|
||||
import { useUIStore } from '@/stores/ui';
|
||||
import { useVersionsStore } from '@/stores/versions';
|
||||
|
||||
export const newVersions = mixins(
|
||||
showMessage,
|
||||
).extend({
|
||||
export const newVersions = mixins(showMessage).extend({
|
||||
computed: {
|
||||
...mapStores(
|
||||
useUIStore,
|
||||
useVersionsStore,
|
||||
),
|
||||
...mapStores(useUIStore, useVersionsStore),
|
||||
},
|
||||
methods: {
|
||||
async checkForNewVersions() {
|
||||
|
||||
@@ -1,47 +1,38 @@
|
||||
import { PropType } from "vue";
|
||||
import { PropType } from 'vue';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { IJsPlumbInstance, IEndpointOptions, INodeUi, XYPosition } from '@/Interface';
|
||||
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers';
|
||||
import { NO_OP_NODE_TYPE, STICKY_NODE_TYPE } from '@/constants';
|
||||
|
||||
import {
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { INodeTypeDescription } from 'n8n-workflow';
|
||||
import { mapStores } from 'pinia';
|
||||
import { useUIStore } from '@/stores/ui';
|
||||
import { useWorkflowsStore } from "@/stores/workflows";
|
||||
import { useNodeTypesStore } from "@/stores/nodeTypes";
|
||||
import { useWorkflowsStore } from '@/stores/workflows';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes';
|
||||
import * as NodeViewUtils from '@/utils/nodeViewUtils';
|
||||
import { getStyleTokenValue } from "@/utils";
|
||||
import { useHistoryStore } from "@/stores/history";
|
||||
import { MoveNodeCommand } from "@/models/history";
|
||||
import { getStyleTokenValue } from '@/utils';
|
||||
import { useHistoryStore } from '@/stores/history';
|
||||
import { MoveNodeCommand } from '@/models/history';
|
||||
|
||||
export const nodeBase = mixins(
|
||||
deviceSupportHelpers,
|
||||
).extend({
|
||||
mounted () {
|
||||
export const nodeBase = mixins(deviceSupportHelpers).extend({
|
||||
mounted() {
|
||||
// Initialize the node
|
||||
if (this.data !== null) {
|
||||
try {
|
||||
this.__addNode(this.data);
|
||||
} catch(error) {
|
||||
} catch (error) {
|
||||
// This breaks when new nodes are loaded into store but workflow tab is not currently active
|
||||
// Shouldn't affect anything
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(
|
||||
useNodeTypesStore,
|
||||
useUIStore,
|
||||
useWorkflowsStore,
|
||||
useHistoryStore,
|
||||
),
|
||||
data (): INodeUi | null {
|
||||
...mapStores(useNodeTypesStore, useUIStore, useWorkflowsStore, useHistoryStore),
|
||||
data(): INodeUi | null {
|
||||
return this.workflowsStore.getNodeByName(this.name);
|
||||
},
|
||||
nodeId (): string {
|
||||
return this.data?.id || '';
|
||||
nodeId(): string {
|
||||
return this.data?.id || '';
|
||||
},
|
||||
},
|
||||
props: {
|
||||
@@ -68,7 +59,7 @@ export const nodeBase = mixins(
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
__addInputEndpoints (node: INodeUi, nodeTypeData: INodeTypeDescription) {
|
||||
__addInputEndpoints(node: INodeUi, nodeTypeData: INodeTypeDescription) {
|
||||
// Add Inputs
|
||||
let index;
|
||||
const indexData: {
|
||||
@@ -85,14 +76,18 @@ export const nodeBase = mixins(
|
||||
index = indexData[inputName];
|
||||
|
||||
// Get the position of the anchor depending on how many it has
|
||||
const anchorPosition = NodeViewUtils.ANCHOR_POSITIONS.input[nodeTypeData.inputs.length][index];
|
||||
const anchorPosition =
|
||||
NodeViewUtils.ANCHOR_POSITIONS.input[nodeTypeData.inputs.length][index];
|
||||
|
||||
const newEndpointData: IEndpointOptions = {
|
||||
uuid:NodeViewUtils. getInputEndpointUUID(this.nodeId, index),
|
||||
uuid: NodeViewUtils.getInputEndpointUUID(this.nodeId, index),
|
||||
anchor: anchorPosition,
|
||||
maxConnections: -1,
|
||||
endpoint: 'Rectangle',
|
||||
endpointStyle: NodeViewUtils.getInputEndpointStyle(nodeTypeData, '--color-foreground-xdark'),
|
||||
endpointStyle: NodeViewUtils.getInputEndpointStyle(
|
||||
nodeTypeData,
|
||||
'--color-foreground-xdark',
|
||||
),
|
||||
endpointHoverStyle: NodeViewUtils.getInputEndpointStyle(nodeTypeData, '--color-primary'),
|
||||
isSource: false,
|
||||
isTarget: !this.isReadOnly && nodeTypeData.inputs.length > 1, // only enabled for nodes with multiple inputs.. otherwise attachment handled by connectionDrag event in NodeView,
|
||||
@@ -118,7 +113,7 @@ export const nodeBase = mixins(
|
||||
}
|
||||
|
||||
const endpoint = this.instance.addEndpoint(this.nodeId, newEndpointData);
|
||||
if(!Array.isArray(endpoint)) {
|
||||
if (!Array.isArray(endpoint)) {
|
||||
endpoint.__meta = {
|
||||
nodeName: node.name,
|
||||
nodeId: this.nodeId,
|
||||
@@ -155,14 +150,18 @@ export const nodeBase = mixins(
|
||||
index = indexData[inputName];
|
||||
|
||||
// Get the position of the anchor depending on how many it has
|
||||
const anchorPosition = NodeViewUtils.ANCHOR_POSITIONS.output[nodeTypeData.outputs.length][index];
|
||||
const anchorPosition =
|
||||
NodeViewUtils.ANCHOR_POSITIONS.output[nodeTypeData.outputs.length][index];
|
||||
|
||||
const newEndpointData: IEndpointOptions = {
|
||||
uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, index),
|
||||
anchor: anchorPosition,
|
||||
maxConnections: -1,
|
||||
endpoint: 'Dot',
|
||||
endpointStyle: NodeViewUtils.getOutputEndpointStyle(nodeTypeData, '--color-foreground-xdark'),
|
||||
endpointStyle: NodeViewUtils.getOutputEndpointStyle(
|
||||
nodeTypeData,
|
||||
'--color-foreground-xdark',
|
||||
),
|
||||
endpointHoverStyle: NodeViewUtils.getOutputEndpointStyle(nodeTypeData, '--color-primary'),
|
||||
isSource: true,
|
||||
isTarget: false,
|
||||
@@ -174,7 +173,7 @@ export const nodeBase = mixins(
|
||||
},
|
||||
cssClass: 'dot-output-endpoint',
|
||||
dragAllowedWhenFull: false,
|
||||
dragProxy: ['Rectangle', {width: 1, height: 1, strokeWidth: 0}],
|
||||
dragProxy: ['Rectangle', { width: 1, height: 1, strokeWidth: 0 }],
|
||||
};
|
||||
|
||||
if (nodeTypeData.outputNames) {
|
||||
@@ -184,15 +183,15 @@ export const nodeBase = mixins(
|
||||
];
|
||||
}
|
||||
|
||||
const endpoint = this.instance.addEndpoint(this.nodeId, {...newEndpointData});
|
||||
if(!Array.isArray(endpoint)) {
|
||||
endpoint.__meta = {
|
||||
nodeName: node.name,
|
||||
nodeId: this.nodeId,
|
||||
index: i,
|
||||
totalEndpoints: nodeTypeData.outputs.length,
|
||||
};
|
||||
}
|
||||
const endpoint = this.instance.addEndpoint(this.nodeId, { ...newEndpointData });
|
||||
if (!Array.isArray(endpoint)) {
|
||||
endpoint.__meta = {
|
||||
nodeName: node.name,
|
||||
nodeId: this.nodeId,
|
||||
index: i,
|
||||
totalEndpoints: nodeTypeData.outputs.length,
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.isReadOnly) {
|
||||
const plusEndpointData: IEndpointOptions = {
|
||||
@@ -223,11 +222,11 @@ export const nodeBase = mixins(
|
||||
},
|
||||
cssClass: 'plus-draggable-endpoint',
|
||||
dragAllowedWhenFull: false,
|
||||
dragProxy: ['Rectangle', {width: 1, height: 1, strokeWidth: 0}],
|
||||
dragProxy: ['Rectangle', { width: 1, height: 1, strokeWidth: 0 }],
|
||||
};
|
||||
|
||||
const plusEndpoint = this.instance.addEndpoint(this.nodeId, plusEndpointData);
|
||||
if(!Array.isArray(plusEndpoint)) {
|
||||
if (!Array.isArray(plusEndpoint)) {
|
||||
plusEndpoint.__meta = {
|
||||
nodeName: node.name,
|
||||
nodeId: this.nodeId,
|
||||
@@ -284,7 +283,7 @@ export const nodeBase = mixins(
|
||||
moveNodes.push(this.data);
|
||||
}
|
||||
|
||||
if(moveNodes.length > 1) {
|
||||
if (moveNodes.length > 1) {
|
||||
this.historyStore.startRecordingUndo();
|
||||
}
|
||||
// This does for some reason just get called once for the node that got clicked
|
||||
@@ -312,12 +311,14 @@ export const nodeBase = mixins(
|
||||
};
|
||||
const oldPosition = node.position;
|
||||
if (oldPosition[0] !== newNodePosition[0] || oldPosition[1] !== newNodePosition[1]) {
|
||||
this.historyStore.pushCommandToUndo(new MoveNodeCommand(node.name, oldPosition, newNodePosition, this));
|
||||
this.historyStore.pushCommandToUndo(
|
||||
new MoveNodeCommand(node.name, oldPosition, newNodePosition, this),
|
||||
);
|
||||
this.workflowsStore.updateNodeProperties(updateInformation);
|
||||
this.$emit('moved', node);
|
||||
}
|
||||
});
|
||||
if(moveNodes.length > 1) {
|
||||
if (moveNodes.length > 1) {
|
||||
this.historyStore.stopRecordingUndo();
|
||||
}
|
||||
}
|
||||
@@ -325,7 +326,7 @@ export const nodeBase = mixins(
|
||||
filter: '.node-description, .node-description .node-name, .node-description .node-subtitle',
|
||||
});
|
||||
},
|
||||
__addNode (node: INodeUi) {
|
||||
__addNode(node: INodeUi) {
|
||||
let nodeTypeData = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||
if (!nodeTypeData) {
|
||||
// If node type is not know use by default the base.noOp data to display it
|
||||
@@ -343,11 +344,15 @@ export const nodeBase = mixins(
|
||||
}
|
||||
}
|
||||
},
|
||||
mouseLeftClick (e: MouseEvent) {
|
||||
mouseLeftClick(e: MouseEvent) {
|
||||
// @ts-ignore
|
||||
const path = e.path || (e.composedPath && e.composedPath());
|
||||
for (let index = 0; index < path.length; index++) {
|
||||
if (path[index].className && typeof path[index].className === 'string' && path[index].className.includes('no-select-on-click')) {
|
||||
if (
|
||||
path[index].className &&
|
||||
typeof path[index].className === 'string' &&
|
||||
path[index].className.includes('no-select-on-click')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ import { get } from 'lodash';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { isObjectLiteral } from '@/utils';
|
||||
import {getCredentialPermissions} from "@/permissions";
|
||||
import { getCredentialPermissions } from '@/permissions';
|
||||
import { mapStores } from 'pinia';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { useUsersStore } from '@/stores/users';
|
||||
@@ -46,467 +46,524 @@ import { useWorkflowsStore } from '@/stores/workflows';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes';
|
||||
import { useCredentialsStore } from '@/stores/credentials';
|
||||
|
||||
export const nodeHelpers = mixins(
|
||||
restApi,
|
||||
)
|
||||
.extend({
|
||||
computed: {
|
||||
...mapStores(
|
||||
useCredentialsStore,
|
||||
useHistoryStore,
|
||||
useNodeTypesStore,
|
||||
useSettingsStore,
|
||||
useWorkflowsStore,
|
||||
),
|
||||
export const nodeHelpers = mixins(restApi).extend({
|
||||
computed: {
|
||||
...mapStores(
|
||||
useCredentialsStore,
|
||||
useHistoryStore,
|
||||
useNodeTypesStore,
|
||||
useSettingsStore,
|
||||
useWorkflowsStore,
|
||||
),
|
||||
},
|
||||
methods: {
|
||||
hasProxyAuth(node: INodeUi): boolean {
|
||||
return Object.keys(node.parameters).includes('nodeCredentialType');
|
||||
},
|
||||
methods: {
|
||||
hasProxyAuth (node: INodeUi): boolean {
|
||||
return Object.keys(node.parameters).includes('nodeCredentialType');
|
||||
},
|
||||
|
||||
isCustomApiCallSelected (nodeValues: INodeParameters): boolean {
|
||||
const { parameters } = nodeValues;
|
||||
isCustomApiCallSelected(nodeValues: INodeParameters): boolean {
|
||||
const { parameters } = nodeValues;
|
||||
|
||||
if (!isObjectLiteral(parameters)) return false;
|
||||
if (!isObjectLiteral(parameters)) return false;
|
||||
|
||||
return (
|
||||
parameters.resource !== undefined && parameters.resource.includes(CUSTOM_API_CALL_KEY) ||
|
||||
parameters.operation !== undefined && parameters.operation.includes(CUSTOM_API_CALL_KEY)
|
||||
);
|
||||
},
|
||||
return (
|
||||
(parameters.resource !== undefined && parameters.resource.includes(CUSTOM_API_CALL_KEY)) ||
|
||||
(parameters.operation !== undefined && parameters.operation.includes(CUSTOM_API_CALL_KEY))
|
||||
);
|
||||
},
|
||||
|
||||
// Returns the parameter value
|
||||
getParameterValue (nodeValues: INodeParameters, parameterName: string, path: string) {
|
||||
return get(
|
||||
nodeValues,
|
||||
path ? path + '.' + parameterName : parameterName,
|
||||
);
|
||||
},
|
||||
// Returns the parameter value
|
||||
getParameterValue(nodeValues: INodeParameters, parameterName: string, path: string) {
|
||||
return get(nodeValues, path ? path + '.' + parameterName : parameterName);
|
||||
},
|
||||
|
||||
// Returns if the given parameter should be displayed or not
|
||||
displayParameter (nodeValues: INodeParameters, parameter: INodeProperties | INodeCredentialDescription, path: string, node: INodeUi | null) {
|
||||
return NodeHelpers.displayParameterPath(nodeValues, parameter, path, node);
|
||||
},
|
||||
// Returns if the given parameter should be displayed or not
|
||||
displayParameter(
|
||||
nodeValues: INodeParameters,
|
||||
parameter: INodeProperties | INodeCredentialDescription,
|
||||
path: string,
|
||||
node: INodeUi | null,
|
||||
) {
|
||||
return NodeHelpers.displayParameterPath(nodeValues, parameter, path, node);
|
||||
},
|
||||
|
||||
// Returns all the issues of the node
|
||||
getNodeIssues (nodeType: INodeTypeDescription | null, node: INodeUi, ignoreIssues?: string[]): INodeIssues | null {
|
||||
const pinDataNodeNames = Object.keys(this.workflowsStore.getPinData || {});
|
||||
// Returns all the issues of the node
|
||||
getNodeIssues(
|
||||
nodeType: INodeTypeDescription | null,
|
||||
node: INodeUi,
|
||||
ignoreIssues?: string[],
|
||||
): INodeIssues | null {
|
||||
const pinDataNodeNames = Object.keys(this.workflowsStore.getPinData || {});
|
||||
|
||||
let nodeIssues: INodeIssues | null = null;
|
||||
ignoreIssues = ignoreIssues || [];
|
||||
let nodeIssues: INodeIssues | null = null;
|
||||
ignoreIssues = ignoreIssues || [];
|
||||
|
||||
if (node.disabled === true || pinDataNodeNames.includes(node.name)) {
|
||||
// Ignore issues on disabled and pindata nodes
|
||||
return null;
|
||||
if (node.disabled === true || pinDataNodeNames.includes(node.name)) {
|
||||
// Ignore issues on disabled and pindata nodes
|
||||
return null;
|
||||
}
|
||||
|
||||
if (nodeType === null) {
|
||||
// Node type is not known
|
||||
if (!ignoreIssues.includes('typeUnknown')) {
|
||||
nodeIssues = {
|
||||
typeUnknown: true,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Node type is known
|
||||
|
||||
// Add potential parameter issues
|
||||
if (!ignoreIssues.includes('parameters')) {
|
||||
nodeIssues = NodeHelpers.getNodeParametersIssues(nodeType.properties, node);
|
||||
}
|
||||
|
||||
if (nodeType === null) {
|
||||
// Node type is not known
|
||||
if (!ignoreIssues.includes('typeUnknown')) {
|
||||
nodeIssues = {
|
||||
typeUnknown: true,
|
||||
};
|
||||
if (!ignoreIssues.includes('credentials')) {
|
||||
// Add potential credential issues
|
||||
const nodeCredentialIssues = this.getNodeCredentialIssues(node, nodeType);
|
||||
if (nodeIssues === null) {
|
||||
nodeIssues = nodeCredentialIssues;
|
||||
} else {
|
||||
NodeHelpers.mergeIssues(nodeIssues, nodeCredentialIssues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hasNodeExecutionIssues(node) === true && !ignoreIssues.includes('execution')) {
|
||||
if (nodeIssues === null) {
|
||||
nodeIssues = {};
|
||||
}
|
||||
nodeIssues.execution = true;
|
||||
}
|
||||
|
||||
return nodeIssues;
|
||||
},
|
||||
|
||||
// Set the status on all the nodes which produced an error so that it can be
|
||||
// displayed in the node-view
|
||||
hasNodeExecutionIssues(node: INodeUi): boolean {
|
||||
const workflowResultData = this.workflowsStore.getWorkflowRunData;
|
||||
|
||||
if (workflowResultData === null || !workflowResultData.hasOwnProperty(node.name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const taskData of workflowResultData[node.name]) {
|
||||
if (taskData.error !== undefined) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
reportUnsetCredential(credentialType: ICredentialType) {
|
||||
return {
|
||||
credentials: {
|
||||
[credentialType.name]: [
|
||||
this.$locale.baseText('nodeHelpers.credentialsUnset', {
|
||||
interpolate: {
|
||||
credentialType: credentialType.displayName,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
// Updates the execution issues.
|
||||
updateNodesExecutionIssues() {
|
||||
const nodes = this.workflowsStore.allNodes;
|
||||
|
||||
for (const node of nodes) {
|
||||
this.workflowsStore.setNodeIssue({
|
||||
node: node.name,
|
||||
type: 'execution',
|
||||
value: this.hasNodeExecutionIssues(node) ? true : null,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Updates the credential-issues of the node
|
||||
updateNodeCredentialIssues(node: INodeUi): void {
|
||||
const fullNodeIssues: INodeIssues | null = this.getNodeCredentialIssues(node);
|
||||
|
||||
let newIssues: INodeIssueObjectProperty | null = null;
|
||||
if (fullNodeIssues !== null) {
|
||||
newIssues = fullNodeIssues.credentials!;
|
||||
}
|
||||
|
||||
this.workflowsStore.setNodeIssue({
|
||||
node: node.name,
|
||||
type: 'credentials',
|
||||
value: newIssues,
|
||||
});
|
||||
},
|
||||
|
||||
// Updates the parameter-issues of the node
|
||||
updateNodeParameterIssues(node: INodeUi, nodeType?: INodeTypeDescription): void {
|
||||
if (nodeType === undefined) {
|
||||
nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||
}
|
||||
|
||||
if (nodeType === null) {
|
||||
// Could not find nodeType so can not update issues
|
||||
return;
|
||||
}
|
||||
|
||||
// All data got updated everywhere so update now the issues
|
||||
const fullNodeIssues: INodeIssues | null = NodeHelpers.getNodeParametersIssues(
|
||||
nodeType!.properties,
|
||||
node,
|
||||
);
|
||||
|
||||
let newIssues: INodeIssueObjectProperty | null = null;
|
||||
if (fullNodeIssues !== null) {
|
||||
newIssues = fullNodeIssues.parameters!;
|
||||
}
|
||||
|
||||
this.workflowsStore.setNodeIssue({
|
||||
node: node.name,
|
||||
type: 'parameters',
|
||||
value: newIssues,
|
||||
});
|
||||
},
|
||||
|
||||
// Returns all the credential-issues of the node
|
||||
getNodeCredentialIssues(node: INodeUi, nodeType?: INodeTypeDescription): INodeIssues | null {
|
||||
if (node.disabled) {
|
||||
// Node is disabled
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!nodeType) {
|
||||
nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||
}
|
||||
|
||||
if (!nodeType?.credentials) {
|
||||
// Node does not need any credentials or nodeType could not be found
|
||||
return null;
|
||||
}
|
||||
|
||||
const foundIssues: INodeIssueObjectProperty = {};
|
||||
|
||||
let userCredentials: ICredentialsResponse[] | null;
|
||||
let credentialType: ICredentialType | null;
|
||||
let credentialDisplayName: string;
|
||||
let selectedCredentials: INodeCredentialsDetails;
|
||||
|
||||
const { authentication, genericAuthType, nodeCredentialType } =
|
||||
node.parameters as HttpRequestNode.V2.AuthParams;
|
||||
|
||||
if (
|
||||
authentication === 'genericCredentialType' &&
|
||||
genericAuthType !== '' &&
|
||||
selectedCredsAreUnusable(node, genericAuthType)
|
||||
) {
|
||||
const credential = this.credentialsStore.getCredentialTypeByName(genericAuthType);
|
||||
return this.reportUnsetCredential(credential);
|
||||
}
|
||||
|
||||
if (
|
||||
this.hasProxyAuth(node) &&
|
||||
authentication === 'predefinedCredentialType' &&
|
||||
nodeCredentialType !== '' &&
|
||||
node.credentials !== undefined
|
||||
) {
|
||||
const stored = this.credentialsStore.getCredentialsByType(nodeCredentialType);
|
||||
|
||||
if (selectedCredsDoNotExist(node, nodeCredentialType, stored)) {
|
||||
const credential = this.credentialsStore.getCredentialTypeByName(nodeCredentialType);
|
||||
return this.reportUnsetCredential(credential);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.hasProxyAuth(node) &&
|
||||
authentication === 'predefinedCredentialType' &&
|
||||
nodeCredentialType !== '' &&
|
||||
selectedCredsAreUnusable(node, nodeCredentialType)
|
||||
) {
|
||||
const credential = this.credentialsStore.getCredentialTypeByName(nodeCredentialType);
|
||||
return this.reportUnsetCredential(credential);
|
||||
}
|
||||
|
||||
for (const credentialTypeDescription of nodeType.credentials) {
|
||||
// Check if credentials should be displayed else ignore
|
||||
if (!this.displayParameter(node.parameters, credentialTypeDescription, '', node)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the display name of the credential type
|
||||
credentialType = this.credentialsStore.getCredentialTypeByName(
|
||||
credentialTypeDescription.name,
|
||||
);
|
||||
if (credentialType === null) {
|
||||
credentialDisplayName = credentialTypeDescription.name;
|
||||
} else {
|
||||
credentialDisplayName = credentialType.displayName;
|
||||
}
|
||||
|
||||
if (!node.credentials || !node.credentials?.[credentialTypeDescription.name]) {
|
||||
// Credentials are not set
|
||||
if (credentialTypeDescription.required) {
|
||||
foundIssues[credentialTypeDescription.name] = [
|
||||
this.$locale.baseText('nodeIssues.credentials.notSet', {
|
||||
interpolate: { type: credentialDisplayName },
|
||||
}),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
// Node type is known
|
||||
|
||||
// Add potential parameter issues
|
||||
if (!ignoreIssues.includes('parameters')) {
|
||||
nodeIssues = NodeHelpers.getNodeParametersIssues(nodeType.properties, node);
|
||||
// If they are set check if the value is valid
|
||||
selectedCredentials = node.credentials[
|
||||
credentialTypeDescription.name
|
||||
] as INodeCredentialsDetails;
|
||||
if (typeof selectedCredentials === 'string') {
|
||||
selectedCredentials = {
|
||||
id: null,
|
||||
name: selectedCredentials,
|
||||
};
|
||||
}
|
||||
|
||||
if (!ignoreIssues.includes('credentials')) {
|
||||
// Add potential credential issues
|
||||
const nodeCredentialIssues = this.getNodeCredentialIssues(node, nodeType);
|
||||
if (nodeIssues === null) {
|
||||
nodeIssues = nodeCredentialIssues;
|
||||
} else {
|
||||
NodeHelpers.mergeIssues(nodeIssues, nodeCredentialIssues);
|
||||
const usersStore = useUsersStore();
|
||||
const currentUser = usersStore.currentUser || ({} as IUser);
|
||||
userCredentials = this.credentialsStore
|
||||
.getCredentialsByType(credentialTypeDescription.name)
|
||||
.filter((credential: ICredentialsResponse) => {
|
||||
const permissions = getCredentialPermissions(currentUser, credential);
|
||||
return permissions.use;
|
||||
});
|
||||
|
||||
if (userCredentials === null) {
|
||||
userCredentials = [];
|
||||
}
|
||||
|
||||
if (selectedCredentials.id) {
|
||||
const idMatch = userCredentials.find(
|
||||
(credentialData) => credentialData.id === selectedCredentials.id,
|
||||
);
|
||||
if (idMatch) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const nameMatches = userCredentials.filter(
|
||||
(credentialData) => credentialData.name === selectedCredentials.name,
|
||||
);
|
||||
if (nameMatches.length > 1) {
|
||||
foundIssues[credentialTypeDescription.name] = [
|
||||
this.$locale.baseText('nodeIssues.credentials.notIdentified', {
|
||||
interpolate: { name: selectedCredentials.name, type: credentialDisplayName },
|
||||
}),
|
||||
this.$locale.baseText('nodeIssues.credentials.notIdentified.hint'),
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nameMatches.length === 0) {
|
||||
const isCredentialUsedInWorkflow =
|
||||
this.workflowsStore.usedCredentials?.[selectedCredentials.id as string];
|
||||
if (!isCredentialUsedInWorkflow) {
|
||||
foundIssues[credentialTypeDescription.name] = [
|
||||
this.$locale.baseText('nodeIssues.credentials.doNotExist', {
|
||||
interpolate: { name: selectedCredentials.name, type: credentialDisplayName },
|
||||
}),
|
||||
this.$locale.baseText('nodeIssues.credentials.doNotExist.hint'),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hasNodeExecutionIssues(node) === true && !ignoreIssues.includes('execution')) {
|
||||
if (nodeIssues === null) {
|
||||
nodeIssues = {};
|
||||
}
|
||||
nodeIssues.execution = true;
|
||||
}
|
||||
// TODO: Could later check also if the node has access to the credentials
|
||||
if (Object.keys(foundIssues).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return nodeIssues;
|
||||
},
|
||||
return {
|
||||
credentials: foundIssues,
|
||||
};
|
||||
},
|
||||
|
||||
// Set the status on all the nodes which produced an error so that it can be
|
||||
// displayed in the node-view
|
||||
hasNodeExecutionIssues (node: INodeUi): boolean {
|
||||
const workflowResultData = this.workflowsStore.getWorkflowRunData;
|
||||
// Updates the node credential issues
|
||||
updateNodesCredentialsIssues() {
|
||||
const nodes = this.workflowsStore.allNodes;
|
||||
let issues: INodeIssues | null;
|
||||
|
||||
if (workflowResultData === null || !workflowResultData.hasOwnProperty(node.name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const taskData of workflowResultData[node.name]) {
|
||||
if (taskData.error !== undefined) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
reportUnsetCredential(credentialType: ICredentialType) {
|
||||
return {
|
||||
credentials: {
|
||||
[credentialType.name]: [
|
||||
this.$locale.baseText(
|
||||
'nodeHelpers.credentialsUnset',
|
||||
{
|
||||
interpolate: {
|
||||
credentialType: credentialType.displayName,
|
||||
},
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
// Updates the execution issues.
|
||||
updateNodesExecutionIssues () {
|
||||
const nodes = this.workflowsStore.allNodes;
|
||||
|
||||
for (const node of nodes) {
|
||||
this.workflowsStore.setNodeIssue({
|
||||
node: node.name,
|
||||
type: 'execution',
|
||||
value: this.hasNodeExecutionIssues(node) ? true : null,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Updates the credential-issues of the node
|
||||
updateNodeCredentialIssues(node: INodeUi): void {
|
||||
const fullNodeIssues: INodeIssues | null = this.getNodeCredentialIssues(node);
|
||||
|
||||
let newIssues: INodeIssueObjectProperty | null = null;
|
||||
if (fullNodeIssues !== null) {
|
||||
newIssues = fullNodeIssues.credentials!;
|
||||
}
|
||||
for (const node of nodes) {
|
||||
issues = this.getNodeCredentialIssues(node);
|
||||
|
||||
this.workflowsStore.setNodeIssue({
|
||||
node: node.name,
|
||||
type: 'credentials',
|
||||
value: newIssues,
|
||||
value: issues === null ? null : issues.credentials,
|
||||
});
|
||||
},
|
||||
|
||||
// Updates the parameter-issues of the node
|
||||
updateNodeParameterIssues(node: INodeUi, nodeType?: INodeTypeDescription): void {
|
||||
if (nodeType === undefined) {
|
||||
nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||
}
|
||||
|
||||
if (nodeType === null) {
|
||||
// Could not find nodeType so can not update issues
|
||||
return;
|
||||
}
|
||||
|
||||
// All data got updated everywhere so update now the issues
|
||||
const fullNodeIssues: INodeIssues | null = NodeHelpers.getNodeParametersIssues(nodeType!.properties, node);
|
||||
|
||||
let newIssues: INodeIssueObjectProperty | null = null;
|
||||
if (fullNodeIssues !== null) {
|
||||
newIssues = fullNodeIssues.parameters!;
|
||||
}
|
||||
|
||||
this.workflowsStore.setNodeIssue({
|
||||
node: node.name,
|
||||
type: 'parameters',
|
||||
value: newIssues,
|
||||
});
|
||||
},
|
||||
|
||||
// Returns all the credential-issues of the node
|
||||
getNodeCredentialIssues (node: INodeUi, nodeType?: INodeTypeDescription): INodeIssues | null {
|
||||
if (node.disabled) {
|
||||
// Node is disabled
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!nodeType) {
|
||||
nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||
}
|
||||
|
||||
if (!nodeType?.credentials) {
|
||||
// Node does not need any credentials or nodeType could not be found
|
||||
return null;
|
||||
}
|
||||
|
||||
const foundIssues: INodeIssueObjectProperty = {};
|
||||
|
||||
let userCredentials: ICredentialsResponse[] | null;
|
||||
let credentialType: ICredentialType | null;
|
||||
let credentialDisplayName: string;
|
||||
let selectedCredentials: INodeCredentialsDetails;
|
||||
|
||||
const {
|
||||
authentication,
|
||||
genericAuthType,
|
||||
nodeCredentialType,
|
||||
} = node.parameters as HttpRequestNode.V2.AuthParams;
|
||||
|
||||
if (
|
||||
authentication === 'genericCredentialType' &&
|
||||
genericAuthType !== '' &&
|
||||
selectedCredsAreUnusable(node, genericAuthType)
|
||||
) {
|
||||
const credential = this.credentialsStore.getCredentialTypeByName(genericAuthType);
|
||||
return this.reportUnsetCredential(credential);
|
||||
}
|
||||
|
||||
if (
|
||||
this.hasProxyAuth(node) &&
|
||||
authentication === 'predefinedCredentialType' &&
|
||||
nodeCredentialType !== '' &&
|
||||
node.credentials !== undefined
|
||||
) {
|
||||
const stored = this.credentialsStore.getCredentialsByType(nodeCredentialType);
|
||||
|
||||
if (selectedCredsDoNotExist(node, nodeCredentialType, stored)) {
|
||||
const credential = this.credentialsStore.getCredentialTypeByName(nodeCredentialType);
|
||||
return this.reportUnsetCredential(credential);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.hasProxyAuth(node) &&
|
||||
authentication === 'predefinedCredentialType' &&
|
||||
nodeCredentialType !== '' &&
|
||||
selectedCredsAreUnusable(node, nodeCredentialType)
|
||||
) {
|
||||
const credential = this.credentialsStore.getCredentialTypeByName(nodeCredentialType);
|
||||
return this.reportUnsetCredential(credential);
|
||||
}
|
||||
|
||||
for (const credentialTypeDescription of nodeType.credentials) {
|
||||
// Check if credentials should be displayed else ignore
|
||||
if (!this.displayParameter(node.parameters, credentialTypeDescription, '', node)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the display name of the credential type
|
||||
credentialType = this.credentialsStore.getCredentialTypeByName(credentialTypeDescription.name);
|
||||
if (credentialType === null) {
|
||||
credentialDisplayName = credentialTypeDescription.name;
|
||||
} else {
|
||||
credentialDisplayName = credentialType.displayName;
|
||||
}
|
||||
|
||||
if (!node.credentials || !node.credentials?.[credentialTypeDescription.name]) {
|
||||
// Credentials are not set
|
||||
if (credentialTypeDescription.required) {
|
||||
foundIssues[credentialTypeDescription.name] = [this.$locale.baseText('nodeIssues.credentials.notSet', { interpolate: { type: credentialDisplayName } })];
|
||||
}
|
||||
} else {
|
||||
// If they are set check if the value is valid
|
||||
selectedCredentials = node.credentials[credentialTypeDescription.name] as INodeCredentialsDetails;
|
||||
if (typeof selectedCredentials === 'string') {
|
||||
selectedCredentials = {
|
||||
id: null,
|
||||
name: selectedCredentials,
|
||||
};
|
||||
}
|
||||
|
||||
const usersStore = useUsersStore();
|
||||
const currentUser = usersStore.currentUser || {} as IUser;
|
||||
userCredentials = this.credentialsStore.getCredentialsByType(credentialTypeDescription.name)
|
||||
.filter((credential: ICredentialsResponse) => {
|
||||
const permissions = getCredentialPermissions(currentUser, credential);
|
||||
return permissions.use;
|
||||
});
|
||||
|
||||
if (userCredentials === null) {
|
||||
userCredentials = [];
|
||||
}
|
||||
|
||||
if (selectedCredentials.id) {
|
||||
const idMatch = userCredentials.find((credentialData) => credentialData.id === selectedCredentials.id);
|
||||
if (idMatch) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const nameMatches = userCredentials.filter((credentialData) => credentialData.name === selectedCredentials.name);
|
||||
if (nameMatches.length > 1) {
|
||||
foundIssues[credentialTypeDescription.name] = [this.$locale.baseText('nodeIssues.credentials.notIdentified', { interpolate: { name: selectedCredentials.name, type: credentialDisplayName } }), this.$locale.baseText('nodeIssues.credentials.notIdentified.hint')];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nameMatches.length === 0) {
|
||||
const isCredentialUsedInWorkflow = this.workflowsStore.usedCredentials?.[selectedCredentials.id as string];
|
||||
if (!isCredentialUsedInWorkflow) {
|
||||
foundIssues[credentialTypeDescription.name] = [this.$locale.baseText('nodeIssues.credentials.doNotExist', { interpolate: { name: selectedCredentials.name, type: credentialDisplayName } }), this.$locale.baseText('nodeIssues.credentials.doNotExist.hint')];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Could later check also if the node has access to the credentials
|
||||
if (Object.keys(foundIssues).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
credentials: foundIssues,
|
||||
};
|
||||
},
|
||||
|
||||
// Updates the node credential issues
|
||||
updateNodesCredentialsIssues () {
|
||||
const nodes = this.workflowsStore.allNodes;
|
||||
let issues: INodeIssues | null;
|
||||
|
||||
for (const node of nodes) {
|
||||
issues = this.getNodeCredentialIssues(node);
|
||||
|
||||
this.workflowsStore.setNodeIssue({
|
||||
node: node.name,
|
||||
type: 'credentials',
|
||||
value: issues === null ? null : issues.credentials,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getNodeInputData (node: INodeUi | null, runIndex = 0, outputIndex = 0): INodeExecutionData[] {
|
||||
if (node === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (this.workflowsStore.getWorkflowExecution === null) {
|
||||
return [];
|
||||
}
|
||||
const executionData = this.workflowsStore.getWorkflowExecution.data;
|
||||
if (!executionData || !executionData.resultData) { // unknown status
|
||||
return [];
|
||||
}
|
||||
const runData = executionData.resultData.runData;
|
||||
|
||||
if (runData === null || runData[node.name] === undefined ||
|
||||
!runData[node.name][runIndex].data ||
|
||||
runData[node.name][runIndex].data === undefined
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.getMainInputData(runData[node.name][runIndex].data!, outputIndex);
|
||||
},
|
||||
|
||||
// Returns the data of the main input
|
||||
getMainInputData (connectionsData: ITaskDataConnections, outputIndex: number): INodeExecutionData[] {
|
||||
if (!connectionsData || !connectionsData.hasOwnProperty('main') || connectionsData.main === undefined || connectionsData.main.length < outputIndex || connectionsData.main[outputIndex] === null) {
|
||||
return [];
|
||||
}
|
||||
return connectionsData.main[outputIndex] as INodeExecutionData[];
|
||||
},
|
||||
|
||||
// Returns all the binary data of all the entries
|
||||
getBinaryData (workflowRunData: IRunData | null, node: string | null, runIndex: number, outputIndex: number): IBinaryKeyData[] {
|
||||
if (node === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const runData: IRunData | null = workflowRunData;
|
||||
|
||||
if (runData === null || !runData[node] || !runData[node][runIndex] ||
|
||||
!runData[node][runIndex].data
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const inputData = this.getMainInputData(runData[node][runIndex].data!, outputIndex);
|
||||
|
||||
const returnData: IBinaryKeyData[] = [];
|
||||
for (let i = 0; i < inputData.length; i++) {
|
||||
if (inputData[i].hasOwnProperty('binary') && inputData[i].binary !== undefined) {
|
||||
returnData.push(inputData[i].binary!);
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
},
|
||||
|
||||
disableNodes(nodes: INodeUi[], trackHistory = false) {
|
||||
if (trackHistory) {
|
||||
this.historyStore.startRecordingUndo();
|
||||
}
|
||||
for (const node of nodes) {
|
||||
const oldState = node.disabled;
|
||||
// Toggle disabled flag
|
||||
const updateInformation = {
|
||||
name: node.name,
|
||||
properties: {
|
||||
disabled: !oldState,
|
||||
} as IDataObject,
|
||||
} as INodeUpdatePropertiesInformation;
|
||||
|
||||
this.$telemetry.track('User set node enabled status', { node_type: node.type, is_enabled: node.disabled, workflow_id: this.workflowsStore.workflowId });
|
||||
|
||||
this.workflowsStore.updateNodeProperties(updateInformation);
|
||||
this.workflowsStore.clearNodeExecutionData(node.name);
|
||||
this.updateNodeParameterIssues(node);
|
||||
this.updateNodeCredentialIssues(node);
|
||||
if (trackHistory) {
|
||||
this.historyStore.pushCommandToUndo(new EnableNodeToggleCommand(node.name, oldState === true, node.disabled === true, this));
|
||||
}
|
||||
}
|
||||
if (trackHistory) {
|
||||
this.historyStore.stopRecordingUndo();
|
||||
}
|
||||
},
|
||||
// @ts-ignore
|
||||
getNodeSubtitle (data, nodeType, workflow): string | undefined {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (data.notesInFlow) {
|
||||
return data.notes;
|
||||
}
|
||||
|
||||
if (nodeType !== null && nodeType.subtitle !== undefined) {
|
||||
return workflow.expression.getSimpleParameterValue(data as INode, nodeType.subtitle, 'internal', PLACEHOLDER_FILLED_AT_EXECUTION_TIME) as string | undefined;
|
||||
}
|
||||
|
||||
if (data.parameters.operation !== undefined) {
|
||||
const operation = data.parameters.operation as string;
|
||||
if (nodeType === null) {
|
||||
return operation;
|
||||
}
|
||||
|
||||
const operationData:INodeProperties = nodeType.properties.find((property: INodeProperties) => {
|
||||
return property.name === 'operation';
|
||||
});
|
||||
if (operationData === undefined) {
|
||||
return operation;
|
||||
}
|
||||
|
||||
if (operationData.options === undefined) {
|
||||
return operation;
|
||||
}
|
||||
|
||||
const optionData = operationData.options.find((option) => {
|
||||
return (option as INodePropertyOptions).value === data.parameters.operation;
|
||||
});
|
||||
if (optionData === undefined) {
|
||||
return operation;
|
||||
}
|
||||
|
||||
return optionData.name;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
getNodeInputData(node: INodeUi | null, runIndex = 0, outputIndex = 0): INodeExecutionData[] {
|
||||
if (node === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (this.workflowsStore.getWorkflowExecution === null) {
|
||||
return [];
|
||||
}
|
||||
const executionData = this.workflowsStore.getWorkflowExecution.data;
|
||||
if (!executionData || !executionData.resultData) {
|
||||
// unknown status
|
||||
return [];
|
||||
}
|
||||
const runData = executionData.resultData.runData;
|
||||
|
||||
if (
|
||||
runData === null ||
|
||||
runData[node.name] === undefined ||
|
||||
!runData[node.name][runIndex].data ||
|
||||
runData[node.name][runIndex].data === undefined
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.getMainInputData(runData[node.name][runIndex].data!, outputIndex);
|
||||
},
|
||||
|
||||
// Returns the data of the main input
|
||||
getMainInputData(
|
||||
connectionsData: ITaskDataConnections,
|
||||
outputIndex: number,
|
||||
): INodeExecutionData[] {
|
||||
if (
|
||||
!connectionsData ||
|
||||
!connectionsData.hasOwnProperty('main') ||
|
||||
connectionsData.main === undefined ||
|
||||
connectionsData.main.length < outputIndex ||
|
||||
connectionsData.main[outputIndex] === null
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
return connectionsData.main[outputIndex] as INodeExecutionData[];
|
||||
},
|
||||
|
||||
// Returns all the binary data of all the entries
|
||||
getBinaryData(
|
||||
workflowRunData: IRunData | null,
|
||||
node: string | null,
|
||||
runIndex: number,
|
||||
outputIndex: number,
|
||||
): IBinaryKeyData[] {
|
||||
if (node === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const runData: IRunData | null = workflowRunData;
|
||||
|
||||
if (
|
||||
runData === null ||
|
||||
!runData[node] ||
|
||||
!runData[node][runIndex] ||
|
||||
!runData[node][runIndex].data
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const inputData = this.getMainInputData(runData[node][runIndex].data!, outputIndex);
|
||||
|
||||
const returnData: IBinaryKeyData[] = [];
|
||||
for (let i = 0; i < inputData.length; i++) {
|
||||
if (inputData[i].hasOwnProperty('binary') && inputData[i].binary !== undefined) {
|
||||
returnData.push(inputData[i].binary!);
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
},
|
||||
|
||||
disableNodes(nodes: INodeUi[], trackHistory = false) {
|
||||
if (trackHistory) {
|
||||
this.historyStore.startRecordingUndo();
|
||||
}
|
||||
for (const node of nodes) {
|
||||
const oldState = node.disabled;
|
||||
// Toggle disabled flag
|
||||
const updateInformation = {
|
||||
name: node.name,
|
||||
properties: {
|
||||
disabled: !oldState,
|
||||
} as IDataObject,
|
||||
} as INodeUpdatePropertiesInformation;
|
||||
|
||||
this.$telemetry.track('User set node enabled status', {
|
||||
node_type: node.type,
|
||||
is_enabled: node.disabled,
|
||||
workflow_id: this.workflowsStore.workflowId,
|
||||
});
|
||||
|
||||
this.workflowsStore.updateNodeProperties(updateInformation);
|
||||
this.workflowsStore.clearNodeExecutionData(node.name);
|
||||
this.updateNodeParameterIssues(node);
|
||||
this.updateNodeCredentialIssues(node);
|
||||
if (trackHistory) {
|
||||
this.historyStore.pushCommandToUndo(
|
||||
new EnableNodeToggleCommand(node.name, oldState === true, node.disabled === true, this),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (trackHistory) {
|
||||
this.historyStore.stopRecordingUndo();
|
||||
}
|
||||
},
|
||||
// @ts-ignore
|
||||
getNodeSubtitle(data, nodeType, workflow): string | undefined {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (data.notesInFlow) {
|
||||
return data.notes;
|
||||
}
|
||||
|
||||
if (nodeType !== null && nodeType.subtitle !== undefined) {
|
||||
return workflow.expression.getSimpleParameterValue(
|
||||
data as INode,
|
||||
nodeType.subtitle,
|
||||
'internal',
|
||||
PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||
) as string | undefined;
|
||||
}
|
||||
|
||||
if (data.parameters.operation !== undefined) {
|
||||
const operation = data.parameters.operation as string;
|
||||
if (nodeType === null) {
|
||||
return operation;
|
||||
}
|
||||
|
||||
const operationData: INodeProperties = nodeType.properties.find(
|
||||
(property: INodeProperties) => {
|
||||
return property.name === 'operation';
|
||||
},
|
||||
);
|
||||
if (operationData === undefined) {
|
||||
return operation;
|
||||
}
|
||||
|
||||
if (operationData.options === undefined) {
|
||||
return operation;
|
||||
}
|
||||
|
||||
const optionData = operationData.options.find((option) => {
|
||||
return (option as INodePropertyOptions).value === data.parameters.operation;
|
||||
});
|
||||
if (optionData === undefined) {
|
||||
return operation;
|
||||
}
|
||||
|
||||
return optionData.name;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Whether the node has no selected credentials, or none of the node's
|
||||
|
||||
@@ -14,10 +14,10 @@ export interface IPinDataContext {
|
||||
export const pinData = (Vue as Vue.VueConstructor<Vue & IPinDataContext>).extend({
|
||||
computed: {
|
||||
...mapStores(useWorkflowsStore),
|
||||
pinData (): IPinData[string] | undefined {
|
||||
pinData(): IPinData[string] | undefined {
|
||||
return this.node ? this.workflowsStore.pinDataByNodeName(this.node!.name) : undefined;
|
||||
},
|
||||
hasPinData (): boolean {
|
||||
hasPinData(): boolean {
|
||||
return !!this.node && typeof this.pinData !== 'undefined';
|
||||
},
|
||||
isPinDataNodeType(): boolean {
|
||||
@@ -40,7 +40,7 @@ export const pinData = (Vue as Vue.VueConstructor<Vue & IPinDataContext>).extend
|
||||
|
||||
error.message = message.charAt(0).toUpperCase() + message.slice(1);
|
||||
error.message = error.message.replace(
|
||||
'Unexpected token \' in JSON',
|
||||
"Unexpected token ' in JSON",
|
||||
this.$locale.baseText('runData.editOutputInvalid.singleQuote'),
|
||||
);
|
||||
|
||||
@@ -48,7 +48,8 @@ export const pinData = (Vue as Vue.VueConstructor<Vue & IPinDataContext>).extend
|
||||
const position = parseInt(positionMatch[1], 10);
|
||||
const lineBreaksUpToPosition = (data.slice(0, position).match(/\n/g) || []).length;
|
||||
|
||||
error.message = error.message.replace(positionMatchRegEx,
|
||||
error.message = error.message.replace(
|
||||
positionMatchRegEx,
|
||||
this.$locale.baseText('runData.editOutputInvalid.atPosition', {
|
||||
interpolate: {
|
||||
position: `${position}`,
|
||||
@@ -56,13 +57,11 @@ export const pinData = (Vue as Vue.VueConstructor<Vue & IPinDataContext>).extend
|
||||
}),
|
||||
);
|
||||
|
||||
error.message = `${
|
||||
this.$locale.baseText('runData.editOutputInvalid.onLine', {
|
||||
interpolate: {
|
||||
line: `${lineBreaksUpToPosition + 1}`,
|
||||
},
|
||||
})
|
||||
} ${error.message}`;
|
||||
error.message = `${this.$locale.baseText('runData.editOutputInvalid.onLine', {
|
||||
interpolate: {
|
||||
line: `${lineBreaksUpToPosition + 1}`,
|
||||
},
|
||||
})} ${error.message}`;
|
||||
}
|
||||
|
||||
this.$showError(error, title);
|
||||
@@ -73,7 +72,10 @@ export const pinData = (Vue as Vue.VueConstructor<Vue & IPinDataContext>).extend
|
||||
isValidPinDataSize(data: string | object): boolean {
|
||||
if (typeof data === 'object') data = JSON.stringify(data);
|
||||
|
||||
if (this.workflowsStore.pinDataSize + stringSizeInBytes(data) > MAX_WORKFLOW_PINNED_DATA_SIZE) {
|
||||
if (
|
||||
this.workflowsStore.pinDataSize + stringSizeInBytes(data) >
|
||||
MAX_WORKFLOW_PINNED_DATA_SIZE
|
||||
) {
|
||||
this.$showError(
|
||||
new Error(this.$locale.baseText('ndv.pinData.error.tooLarge.description')),
|
||||
this.$locale.baseText('ndv.pinData.error.tooLarge.title'),
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
IExecutionResponse,
|
||||
IExecutionsCurrentSummaryExtended,
|
||||
IPushData,
|
||||
} from '../../Interface';
|
||||
import { IExecutionResponse, IExecutionsCurrentSummaryExtended, IPushData } from '../../Interface';
|
||||
|
||||
import { externalHooks } from '@/mixins/externalHooks';
|
||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
||||
@@ -35,60 +31,60 @@ export const pushConnection = mixins(
|
||||
showMessage,
|
||||
titleChange,
|
||||
workflowHelpers,
|
||||
)
|
||||
.extend({
|
||||
data () {
|
||||
return {
|
||||
eventSource: null as EventSource | null,
|
||||
reconnectTimeout: null as NodeJS.Timeout | null,
|
||||
retryTimeout: null as NodeJS.Timeout | null,
|
||||
pushMessageQueue: [] as Array<{ event: Event, retriesLeft: number }>,
|
||||
};
|
||||
).extend({
|
||||
data() {
|
||||
return {
|
||||
eventSource: null as EventSource | null,
|
||||
reconnectTimeout: null as NodeJS.Timeout | null,
|
||||
retryTimeout: null as NodeJS.Timeout | null,
|
||||
pushMessageQueue: [] as Array<{ event: Event; retriesLeft: number }>,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useCredentialsStore, useNodeTypesStore, useUIStore, useWorkflowsStore),
|
||||
sessionId(): string {
|
||||
return this.rootStore.sessionId;
|
||||
},
|
||||
computed: {
|
||||
...mapStores(
|
||||
useCredentialsStore,
|
||||
useNodeTypesStore,
|
||||
useUIStore,
|
||||
useWorkflowsStore,
|
||||
),
|
||||
sessionId (): string {
|
||||
return this.rootStore.sessionId;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
pushAutomaticReconnect(): void {
|
||||
if (this.reconnectTimeout !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.reconnectTimeout = setTimeout(() => {
|
||||
this.pushConnect();
|
||||
}, 3000);
|
||||
},
|
||||
methods: {
|
||||
pushAutomaticReconnect (): void {
|
||||
if (this.reconnectTimeout !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.reconnectTimeout = setTimeout(() => {
|
||||
this.pushConnect();
|
||||
}, 3000);
|
||||
},
|
||||
/**
|
||||
* Connect to server to receive data via EventSource
|
||||
*/
|
||||
pushConnect(): void {
|
||||
// Make sure existing event-source instances get
|
||||
// always removed that we do not end up with multiple ones
|
||||
this.pushDisconnect();
|
||||
|
||||
/**
|
||||
* Connect to server to receive data via EventSource
|
||||
*/
|
||||
pushConnect (): void {
|
||||
// Make sure existing event-source instances get
|
||||
// always removed that we do not end up with multiple ones
|
||||
this.pushDisconnect();
|
||||
const connectionUrl = `${this.rootStore.getRestUrl}/push?sessionId=${this.sessionId}`;
|
||||
|
||||
const connectionUrl = `${this.rootStore.getRestUrl}/push?sessionId=${this.sessionId}`;
|
||||
this.eventSource = new EventSource(connectionUrl, { withCredentials: true });
|
||||
this.eventSource.addEventListener('message', this.pushMessageReceived, false);
|
||||
|
||||
this.eventSource = new EventSource(connectionUrl, { withCredentials: true });
|
||||
this.eventSource.addEventListener('message', this.pushMessageReceived, false);
|
||||
|
||||
this.eventSource.addEventListener('open', () => {
|
||||
this.rootStore.pushConnectionActive = true;
|
||||
this.eventSource.addEventListener(
|
||||
'open',
|
||||
() => {
|
||||
this.rootStore.pushConnectionActive = true;
|
||||
if (this.reconnectTimeout !== null) {
|
||||
clearTimeout(this.reconnectTimeout);
|
||||
this.reconnectTimeout = null;
|
||||
}
|
||||
}, false);
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
this.eventSource.addEventListener('error', () => {
|
||||
this.eventSource.addEventListener(
|
||||
'error',
|
||||
() => {
|
||||
this.pushDisconnect();
|
||||
|
||||
if (this.reconnectTimeout !== null) {
|
||||
@@ -96,362 +92,386 @@ export const pushConnection = mixins(
|
||||
this.reconnectTimeout = null;
|
||||
}
|
||||
|
||||
this.rootStore.pushConnectionActive = false;
|
||||
this.rootStore.pushConnectionActive = false;
|
||||
this.pushAutomaticReconnect();
|
||||
}, false);
|
||||
},
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Close connection to server
|
||||
*/
|
||||
pushDisconnect (): void {
|
||||
if (this.eventSource !== null) {
|
||||
this.eventSource.close();
|
||||
this.eventSource = null;
|
||||
/**
|
||||
* Close connection to server
|
||||
*/
|
||||
pushDisconnect(): void {
|
||||
if (this.eventSource !== null) {
|
||||
this.eventSource.close();
|
||||
this.eventSource = null;
|
||||
|
||||
this.rootStore.pushConnectionActive = false;
|
||||
}
|
||||
},
|
||||
this.rootStore.pushConnectionActive = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sometimes the push message is faster as the result from
|
||||
* the REST API so we do not know yet what execution ID
|
||||
* is currently active. So internally resend the message
|
||||
* a few more times
|
||||
*
|
||||
*/
|
||||
queuePushMessage (event: Event, retryAttempts: number) {
|
||||
this.pushMessageQueue.push({ event, retriesLeft: retryAttempts });
|
||||
/**
|
||||
* Sometimes the push message is faster as the result from
|
||||
* the REST API so we do not know yet what execution ID
|
||||
* is currently active. So internally resend the message
|
||||
* a few more times
|
||||
*
|
||||
*/
|
||||
queuePushMessage(event: Event, retryAttempts: number) {
|
||||
this.pushMessageQueue.push({ event, retriesLeft: retryAttempts });
|
||||
|
||||
if (this.retryTimeout === null) {
|
||||
this.retryTimeout = setTimeout(this.processWaitingPushMessages, 20);
|
||||
}
|
||||
},
|
||||
if (this.retryTimeout === null) {
|
||||
this.retryTimeout = setTimeout(this.processWaitingPushMessages, 20);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Process the push messages which are waiting in the queue
|
||||
*/
|
||||
processWaitingPushMessages() {
|
||||
if (this.retryTimeout !== null) {
|
||||
clearTimeout(this.retryTimeout);
|
||||
this.retryTimeout = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the push messages which are waiting in the queue
|
||||
*/
|
||||
processWaitingPushMessages () {
|
||||
if (this.retryTimeout !== null) {
|
||||
clearTimeout(this.retryTimeout);
|
||||
this.retryTimeout = null;
|
||||
}
|
||||
const queueLength = this.pushMessageQueue.length;
|
||||
for (let i = 0; i < queueLength; i++) {
|
||||
const messageData = this.pushMessageQueue.shift();
|
||||
|
||||
const queueLength = this.pushMessageQueue.length;
|
||||
for (let i = 0; i < queueLength; i++) {
|
||||
const messageData = this.pushMessageQueue.shift();
|
||||
if (this.pushMessageReceived(messageData!.event, true) === false) {
|
||||
// Was not successful
|
||||
messageData!.retriesLeft -= 1;
|
||||
|
||||
if (this.pushMessageReceived(messageData!.event, true) === false) {
|
||||
// Was not successful
|
||||
messageData!.retriesLeft -= 1;
|
||||
|
||||
if (messageData!.retriesLeft > 0) {
|
||||
// If still retries are left add it back and stop execution
|
||||
this.pushMessageQueue.unshift(messageData!);
|
||||
}
|
||||
break;
|
||||
if (messageData!.retriesLeft > 0) {
|
||||
// If still retries are left add it back and stop execution
|
||||
this.pushMessageQueue.unshift(messageData!);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.pushMessageQueue.length !== 0 && this.retryTimeout === null) {
|
||||
this.retryTimeout = setTimeout(this.processWaitingPushMessages, 25);
|
||||
if (this.pushMessageQueue.length !== 0 && this.retryTimeout === null) {
|
||||
this.retryTimeout = setTimeout(this.processWaitingPushMessages, 25);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Process a newly received message
|
||||
*
|
||||
* @param {Event} event The event data with the message data
|
||||
* @param {boolean} [isRetry] If it is a retry
|
||||
*/
|
||||
pushMessageReceived(event: Event, isRetry?: boolean): boolean {
|
||||
const retryAttempts = 5;
|
||||
let receivedData: IPushData;
|
||||
try {
|
||||
// @ts-ignore
|
||||
receivedData = JSON.parse(event.data);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (receivedData.type === 'sendConsoleMessage') {
|
||||
const pushData = receivedData.data;
|
||||
console.log(pushData.source, ...pushData.messages); // eslint-disable-line no-console
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
!['testWebhookReceived'].includes(receivedData.type) &&
|
||||
isRetry !== true &&
|
||||
this.pushMessageQueue.length
|
||||
) {
|
||||
// If there are already messages in the queue add the new one that all of them
|
||||
// get executed in order
|
||||
this.queuePushMessage(event, retryAttempts);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (receivedData.type === 'nodeExecuteAfter' || receivedData.type === 'nodeExecuteBefore') {
|
||||
if (!this.uiStore.isActionActive('workflowRunning')) {
|
||||
// No workflow is running so ignore the messages
|
||||
return false;
|
||||
}
|
||||
},
|
||||
const pushData = receivedData.data;
|
||||
if (this.workflowsStore.activeExecutionId !== pushData.executionId) {
|
||||
// The data is not for the currently active execution or
|
||||
// we do not have the execution id yet.
|
||||
if (isRetry !== true) {
|
||||
this.queuePushMessage(event, retryAttempts);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (receivedData.type === 'executionFinished') {
|
||||
// The workflow finished executing
|
||||
const pushData = receivedData.data;
|
||||
|
||||
/**
|
||||
* Process a newly received message
|
||||
*
|
||||
* @param {Event} event The event data with the message data
|
||||
* @param {boolean} [isRetry] If it is a retry
|
||||
*/
|
||||
pushMessageReceived (event: Event, isRetry?: boolean): boolean {
|
||||
const retryAttempts = 5;
|
||||
let receivedData: IPushData;
|
||||
try {
|
||||
// @ts-ignore
|
||||
receivedData = JSON.parse(event.data);
|
||||
} catch (error) {
|
||||
this.workflowsStore.finishActiveExecution(pushData);
|
||||
|
||||
if (!this.uiStore.isActionActive('workflowRunning')) {
|
||||
// No workflow is running so ignore the messages
|
||||
return false;
|
||||
}
|
||||
|
||||
if (receivedData.type === 'sendConsoleMessage') {
|
||||
const pushData = receivedData.data;
|
||||
console.log(pushData.source, ...pushData.messages); // eslint-disable-line no-console
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!['testWebhookReceived'].includes(receivedData.type) && isRetry !== true && this.pushMessageQueue.length) {
|
||||
// If there are already messages in the queue add the new one that all of them
|
||||
// get executed in order
|
||||
this.queuePushMessage(event, retryAttempts);
|
||||
if (this.workflowsStore.activeExecutionId !== pushData.executionId) {
|
||||
// The workflow which did finish execution did either not get started
|
||||
// by this session or we do not have the execution id yet.
|
||||
if (isRetry !== true) {
|
||||
this.queuePushMessage(event, retryAttempts);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (receivedData.type === 'nodeExecuteAfter' || receivedData.type === 'nodeExecuteBefore') {
|
||||
if (!this.uiStore.isActionActive('workflowRunning')) {
|
||||
// No workflow is running so ignore the messages
|
||||
return false;
|
||||
}
|
||||
const pushData = receivedData.data;
|
||||
if (this.workflowsStore.activeExecutionId !== pushData.executionId) {
|
||||
// The data is not for the currently active execution or
|
||||
// we do not have the execution id yet.
|
||||
if (isRetry !== true) {
|
||||
this.queuePushMessage(event, retryAttempts);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const runDataExecuted = pushData.data;
|
||||
|
||||
if (receivedData.type === 'executionFinished') {
|
||||
// The workflow finished executing
|
||||
const pushData = receivedData.data;
|
||||
const runDataExecutedErrorMessage = this.$getExecutionError(runDataExecuted.data);
|
||||
|
||||
this.workflowsStore.finishActiveExecution(pushData);
|
||||
const lineNumber =
|
||||
runDataExecuted &&
|
||||
runDataExecuted.data &&
|
||||
runDataExecuted.data.resultData &&
|
||||
runDataExecuted.data.resultData.error &&
|
||||
runDataExecuted.data.resultData.error.lineNumber;
|
||||
|
||||
if (!this.uiStore.isActionActive('workflowRunning')) {
|
||||
// No workflow is running so ignore the messages
|
||||
return false;
|
||||
codeNodeEditorEventBus.$emit('error-line-number', lineNumber || 'final');
|
||||
|
||||
const workflow = this.getCurrentWorkflow();
|
||||
if (runDataExecuted.waitTill !== undefined) {
|
||||
const activeExecutionId = this.workflowsStore.activeExecutionId;
|
||||
const workflowSettings = this.workflowsStore.workflowSettings;
|
||||
const saveManualExecutions = this.rootStore.saveManualExecutions;
|
||||
|
||||
const isSavingExecutions =
|
||||
workflowSettings.saveManualExecutions === undefined
|
||||
? saveManualExecutions
|
||||
: workflowSettings.saveManualExecutions;
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
action =
|
||||
'<a data-action="open-settings">Turn on saving manual executions</a> and run again to see what happened after this node.';
|
||||
} else {
|
||||
action = `<a href="/workflow/${workflow.id}/executions/${activeExecutionId}">View the execution</a> to see what happened after this node.`;
|
||||
}
|
||||
|
||||
if (this.workflowsStore.activeExecutionId !== pushData.executionId) {
|
||||
// The workflow which did finish execution did either not get started
|
||||
// by this session or we do not have the execution id yet.
|
||||
if (isRetry !== true) {
|
||||
this.queuePushMessage(event, retryAttempts);
|
||||
}
|
||||
return false;
|
||||
// Workflow did start but had been put to wait
|
||||
this.$titleSet(workflow.name as string, 'IDLE');
|
||||
this.$showToast({
|
||||
title: 'Workflow started waiting',
|
||||
message: `${action} <a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait/" target="_blank">More info</a>`,
|
||||
type: 'success',
|
||||
duration: 0,
|
||||
});
|
||||
} else if (runDataExecuted.finished !== true) {
|
||||
this.$titleSet(workflow.name as string, 'ERROR');
|
||||
|
||||
if (
|
||||
runDataExecuted.data.resultData.error?.name === 'ExpressionError' &&
|
||||
(runDataExecuted.data.resultData.error as ExpressionError).context.functionality ===
|
||||
'pairedItem'
|
||||
) {
|
||||
const error = runDataExecuted.data.resultData.error as ExpressionError;
|
||||
|
||||
this.getWorkflowDataToSave().then((workflowData) => {
|
||||
const eventData: IDataObject = {
|
||||
caused_by_credential: false,
|
||||
error_message: error.description,
|
||||
error_title: error.message,
|
||||
error_type: error.context.type,
|
||||
node_graph_string: JSON.stringify(
|
||||
TelemetryHelpers.generateNodesGraph(
|
||||
workflowData as IWorkflowBase,
|
||||
this.getNodeTypes(),
|
||||
).nodeGraph,
|
||||
),
|
||||
workflow_id: this.workflowsStore.workflowId,
|
||||
};
|
||||
|
||||
if (
|
||||
error.context.nodeCause &&
|
||||
['no pairing info', 'invalid pairing info'].includes(error.context.type as string)
|
||||
) {
|
||||
const node = workflow.getNode(error.context.nodeCause as string);
|
||||
|
||||
if (node) {
|
||||
eventData.is_pinned = !!workflow.getPinDataOfNode(node.name);
|
||||
eventData.mode = node.parameters.mode;
|
||||
eventData.node_type = node.type;
|
||||
eventData.operation = node.parameters.operation;
|
||||
eventData.resource = node.parameters.resource;
|
||||
}
|
||||
}
|
||||
|
||||
this.$telemetry.track('Instance FE emitted paired item error', eventData);
|
||||
});
|
||||
}
|
||||
|
||||
const runDataExecuted = pushData.data;
|
||||
if (runDataExecuted.data.resultData.error?.name === 'SubworkflowOperationError') {
|
||||
const error = runDataExecuted.data.resultData.error as SubworkflowOperationError;
|
||||
|
||||
const runDataExecutedErrorMessage = this.$getExecutionError(runDataExecuted.data);
|
||||
this.workflowsStore.subWorkflowExecutionError = error;
|
||||
|
||||
const lineNumber = runDataExecuted &&
|
||||
runDataExecuted.data &&
|
||||
runDataExecuted.data.resultData &&
|
||||
runDataExecuted.data.resultData.error &&
|
||||
runDataExecuted.data.resultData.error.lineNumber;
|
||||
|
||||
codeNodeEditorEventBus.$emit('error-line-number', lineNumber || 'final');
|
||||
|
||||
const workflow = this.getCurrentWorkflow();
|
||||
if (runDataExecuted.waitTill !== undefined) {
|
||||
const activeExecutionId = this.workflowsStore.activeExecutionId;
|
||||
const workflowSettings = this.workflowsStore.workflowSettings;
|
||||
const saveManualExecutions = this.rootStore.saveManualExecutions;
|
||||
|
||||
const isSavingExecutions= workflowSettings.saveManualExecutions === undefined ? saveManualExecutions : workflowSettings.saveManualExecutions;
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
action = '<a data-action="open-settings">Turn on saving manual executions</a> and run again to see what happened after this node.';
|
||||
}
|
||||
else {
|
||||
action = `<a href="/workflow/${workflow.id}/executions/${activeExecutionId}">View the execution</a> to see what happened after this node.`;
|
||||
}
|
||||
|
||||
// Workflow did start but had been put to wait
|
||||
this.$titleSet(workflow.name as string, 'IDLE');
|
||||
this.$showToast({
|
||||
title: 'Workflow started waiting',
|
||||
message: `${action} <a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait/" target="_blank">More info</a>`,
|
||||
type: 'success',
|
||||
this.$showMessage({
|
||||
title: error.message,
|
||||
message: error.description,
|
||||
type: 'error',
|
||||
duration: 0,
|
||||
});
|
||||
} else if (runDataExecuted.finished !== true) {
|
||||
this.$titleSet(workflow.name as string, 'ERROR');
|
||||
|
||||
if (
|
||||
runDataExecuted.data.resultData.error?.name === 'ExpressionError' &&
|
||||
(runDataExecuted.data.resultData.error as ExpressionError).context.functionality === 'pairedItem'
|
||||
) {
|
||||
const error = runDataExecuted.data.resultData.error as ExpressionError;
|
||||
|
||||
this.getWorkflowDataToSave().then((workflowData) => {
|
||||
const eventData: IDataObject = {
|
||||
caused_by_credential: false,
|
||||
error_message: error.description,
|
||||
error_title: error.message,
|
||||
error_type: error.context.type,
|
||||
node_graph_string: JSON.stringify(TelemetryHelpers.generateNodesGraph(workflowData as IWorkflowBase, this.getNodeTypes()).nodeGraph),
|
||||
workflow_id: this.workflowsStore.workflowId,
|
||||
};
|
||||
|
||||
if (error.context.nodeCause && ['no pairing info', 'invalid pairing info'].includes(error.context.type as string)) {
|
||||
const node = workflow.getNode(error.context.nodeCause as string);
|
||||
|
||||
if (node) {
|
||||
eventData.is_pinned = !!workflow.getPinDataOfNode(node.name);
|
||||
eventData.mode = node.parameters.mode;
|
||||
eventData.node_type = node.type;
|
||||
eventData.operation = node.parameters.operation;
|
||||
eventData.resource = node.parameters.resource;
|
||||
}
|
||||
}
|
||||
|
||||
this.$telemetry.track('Instance FE emitted paired item error', eventData);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (runDataExecuted.data.resultData.error?.name === 'SubworkflowOperationError') {
|
||||
const error = runDataExecuted.data.resultData.error as SubworkflowOperationError;
|
||||
|
||||
this.workflowsStore.subWorkflowExecutionError = error;
|
||||
|
||||
this.$showMessage({
|
||||
title: error.message,
|
||||
message: error.description,
|
||||
type: 'error',
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
} else {
|
||||
let title: string;
|
||||
if (runDataExecuted.data.resultData.lastNodeExecuted) {
|
||||
title = `Problem in node ‘${runDataExecuted.data.resultData.lastNodeExecuted}‘`;
|
||||
} else {
|
||||
title = 'Problem executing workflow';
|
||||
}
|
||||
|
||||
this.$showMessage({
|
||||
title,
|
||||
message: runDataExecutedErrorMessage,
|
||||
type: 'error',
|
||||
duration: 0,
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
// Workflow did execute without a problem
|
||||
this.$titleSet(workflow.name as string, 'IDLE');
|
||||
|
||||
const execution = this.workflowsStore.getWorkflowExecution;
|
||||
if (execution && execution.executedNode) {
|
||||
const node = this.workflowsStore.getNodeByName(execution.executedNode);
|
||||
const nodeType = node && this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||
const nodeOutput = execution && execution.executedNode && execution.data && execution.data.resultData && execution.data.resultData.runData && execution.data.resultData.runData[execution.executedNode];
|
||||
if (node && nodeType && !nodeOutput) {
|
||||
this.$showMessage({
|
||||
title: this.$locale.baseText('pushConnection.pollingNode.dataNotFound', {
|
||||
interpolate: {
|
||||
service: getTriggerNodeServiceName(nodeType),
|
||||
},
|
||||
}),
|
||||
message: this.$locale.baseText('pushConnection.pollingNode.dataNotFound.message', {
|
||||
interpolate: {
|
||||
service: getTriggerNodeServiceName(nodeType),
|
||||
},
|
||||
}),
|
||||
type: 'success',
|
||||
});
|
||||
} else {
|
||||
this.$showMessage({
|
||||
title: this.$locale.baseText('pushConnection.nodeExecutedSuccessfully'),
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
let title: string;
|
||||
if (runDataExecuted.data.resultData.lastNodeExecuted) {
|
||||
title = `Problem in node ‘${runDataExecuted.data.resultData.lastNodeExecuted}‘`;
|
||||
} else {
|
||||
title = 'Problem executing workflow';
|
||||
}
|
||||
else {
|
||||
|
||||
this.$showMessage({
|
||||
title,
|
||||
message: runDataExecutedErrorMessage,
|
||||
type: 'error',
|
||||
duration: 0,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Workflow did execute without a problem
|
||||
this.$titleSet(workflow.name as string, 'IDLE');
|
||||
|
||||
const execution = this.workflowsStore.getWorkflowExecution;
|
||||
if (execution && execution.executedNode) {
|
||||
const node = this.workflowsStore.getNodeByName(execution.executedNode);
|
||||
const nodeType = node && this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||
const nodeOutput =
|
||||
execution &&
|
||||
execution.executedNode &&
|
||||
execution.data &&
|
||||
execution.data.resultData &&
|
||||
execution.data.resultData.runData &&
|
||||
execution.data.resultData.runData[execution.executedNode];
|
||||
if (node && nodeType && !nodeOutput) {
|
||||
this.$showMessage({
|
||||
title: this.$locale.baseText('pushConnection.workflowExecutedSuccessfully'),
|
||||
title: this.$locale.baseText('pushConnection.pollingNode.dataNotFound', {
|
||||
interpolate: {
|
||||
service: getTriggerNodeServiceName(nodeType),
|
||||
},
|
||||
}),
|
||||
message: this.$locale.baseText('pushConnection.pollingNode.dataNotFound.message', {
|
||||
interpolate: {
|
||||
service: getTriggerNodeServiceName(nodeType),
|
||||
},
|
||||
}),
|
||||
type: 'success',
|
||||
});
|
||||
} else {
|
||||
this.$showMessage({
|
||||
title: this.$locale.baseText('pushConnection.nodeExecutedSuccessfully'),
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// It does not push the runData as it got already pushed with each
|
||||
// node that did finish. For that reason copy in here the data
|
||||
// which we already have.
|
||||
if (this.workflowsStore.getWorkflowRunData) {
|
||||
runDataExecuted.data.resultData.runData = this.workflowsStore.getWorkflowRunData;
|
||||
}
|
||||
|
||||
this.workflowsStore.executingNode = null;
|
||||
this.workflowsStore.setWorkflowExecutionData(runDataExecuted as IExecutionResponse);
|
||||
this.uiStore.removeActiveAction('workflowRunning');
|
||||
|
||||
// Set the node execution issues on all the nodes which produced an error so that
|
||||
// it can be displayed in the node-view
|
||||
this.updateNodesExecutionIssues();
|
||||
|
||||
const lastNodeExecuted: string | undefined = runDataExecuted.data.resultData.lastNodeExecuted;
|
||||
let itemsCount = 0;
|
||||
if(lastNodeExecuted && runDataExecuted.data.resultData.runData[lastNodeExecuted as string] && !runDataExecutedErrorMessage) {
|
||||
itemsCount = runDataExecuted.data.resultData.runData[lastNodeExecuted as string][0].data!.main[0]!.length;
|
||||
}
|
||||
|
||||
this.$externalHooks().run('pushConnection.executionFinished', {
|
||||
itemsCount,
|
||||
nodeName: runDataExecuted.data.resultData.lastNodeExecuted,
|
||||
errorMessage: runDataExecutedErrorMessage,
|
||||
runDataExecutedStartData: runDataExecuted.data.startData,
|
||||
resultDataError: runDataExecuted.data.resultData.error,
|
||||
});
|
||||
|
||||
} else if (receivedData.type === 'executionStarted') {
|
||||
const pushData = receivedData.data;
|
||||
|
||||
const executionData: IExecutionsCurrentSummaryExtended = {
|
||||
id: pushData.executionId,
|
||||
finished: false,
|
||||
mode: pushData.mode,
|
||||
startedAt: pushData.startedAt,
|
||||
retryOf: pushData.retryOf,
|
||||
workflowId: pushData.workflowId,
|
||||
workflowName: pushData.workflowName,
|
||||
};
|
||||
|
||||
this.workflowsStore.addActiveExecution(executionData);
|
||||
} else if (receivedData.type === 'nodeExecuteAfter') {
|
||||
// A node finished to execute. Add its data
|
||||
const pushData = receivedData.data;
|
||||
this.workflowsStore.addNodeExecutionData(pushData);
|
||||
} else if (receivedData.type === 'nodeExecuteBefore') {
|
||||
// A node started to be executed. Set it as executing.
|
||||
const pushData = receivedData.data;
|
||||
this.workflowsStore.executingNode = pushData.nodeName;
|
||||
} else if (receivedData.type === 'testWebhookDeleted') {
|
||||
// A test-webhook was deleted
|
||||
const pushData = receivedData.data;
|
||||
|
||||
if (pushData.workflowId === this.workflowsStore.workflowId) {
|
||||
this.workflowsStore.executionWaitingForWebhook = false;
|
||||
this.uiStore.removeActiveAction('workflowRunning');
|
||||
}
|
||||
} else if (receivedData.type === 'testWebhookReceived') {
|
||||
// A test-webhook did get called
|
||||
const pushData = receivedData.data;
|
||||
|
||||
if (pushData.workflowId === this.workflowsStore.workflowId) {
|
||||
this.workflowsStore.executionWaitingForWebhook = false;
|
||||
this.workflowsStore.activeExecutionId = pushData.executionId;
|
||||
}
|
||||
|
||||
this.processWaitingPushMessages();
|
||||
} else if (receivedData.type === 'reloadNodeType') {
|
||||
this.nodeTypesStore.getNodeTypes();
|
||||
this.nodeTypesStore.getFullNodesProperties([receivedData.data]);
|
||||
} else if (receivedData.type === 'removeNodeType') {
|
||||
const pushData = receivedData.data;
|
||||
|
||||
const nodesToBeRemoved: INodeTypeNameVersion[] = [pushData];
|
||||
|
||||
// Force reload of all credential types
|
||||
this.credentialsStore.fetchCredentialTypes(false)
|
||||
.then(() => {
|
||||
this.nodeTypesStore.removeNodeTypes(nodesToBeRemoved);
|
||||
} else {
|
||||
this.$showMessage({
|
||||
title: this.$locale.baseText('pushConnection.workflowExecutedSuccessfully'),
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
// It does not push the runData as it got already pushed with each
|
||||
// node that did finish. For that reason copy in here the data
|
||||
// which we already have.
|
||||
if (this.workflowsStore.getWorkflowRunData) {
|
||||
runDataExecuted.data.resultData.runData = this.workflowsStore.getWorkflowRunData;
|
||||
}
|
||||
|
||||
this.workflowsStore.executingNode = null;
|
||||
this.workflowsStore.setWorkflowExecutionData(runDataExecuted as IExecutionResponse);
|
||||
this.uiStore.removeActiveAction('workflowRunning');
|
||||
|
||||
// Set the node execution issues on all the nodes which produced an error so that
|
||||
// it can be displayed in the node-view
|
||||
this.updateNodesExecutionIssues();
|
||||
|
||||
const lastNodeExecuted: string | undefined =
|
||||
runDataExecuted.data.resultData.lastNodeExecuted;
|
||||
let itemsCount = 0;
|
||||
if (
|
||||
lastNodeExecuted &&
|
||||
runDataExecuted.data.resultData.runData[lastNodeExecuted as string] &&
|
||||
!runDataExecutedErrorMessage
|
||||
) {
|
||||
itemsCount =
|
||||
runDataExecuted.data.resultData.runData[lastNodeExecuted as string][0].data!.main[0]!
|
||||
.length;
|
||||
}
|
||||
|
||||
this.$externalHooks().run('pushConnection.executionFinished', {
|
||||
itemsCount,
|
||||
nodeName: runDataExecuted.data.resultData.lastNodeExecuted,
|
||||
errorMessage: runDataExecutedErrorMessage,
|
||||
runDataExecutedStartData: runDataExecuted.data.startData,
|
||||
resultDataError: runDataExecuted.data.resultData.error,
|
||||
});
|
||||
} else if (receivedData.type === 'executionStarted') {
|
||||
const pushData = receivedData.data;
|
||||
|
||||
const executionData: IExecutionsCurrentSummaryExtended = {
|
||||
id: pushData.executionId,
|
||||
finished: false,
|
||||
mode: pushData.mode,
|
||||
startedAt: pushData.startedAt,
|
||||
retryOf: pushData.retryOf,
|
||||
workflowId: pushData.workflowId,
|
||||
workflowName: pushData.workflowName,
|
||||
};
|
||||
|
||||
this.workflowsStore.addActiveExecution(executionData);
|
||||
} else if (receivedData.type === 'nodeExecuteAfter') {
|
||||
// A node finished to execute. Add its data
|
||||
const pushData = receivedData.data;
|
||||
this.workflowsStore.addNodeExecutionData(pushData);
|
||||
} else if (receivedData.type === 'nodeExecuteBefore') {
|
||||
// A node started to be executed. Set it as executing.
|
||||
const pushData = receivedData.data;
|
||||
this.workflowsStore.executingNode = pushData.nodeName;
|
||||
} else if (receivedData.type === 'testWebhookDeleted') {
|
||||
// A test-webhook was deleted
|
||||
const pushData = receivedData.data;
|
||||
|
||||
if (pushData.workflowId === this.workflowsStore.workflowId) {
|
||||
this.workflowsStore.executionWaitingForWebhook = false;
|
||||
this.uiStore.removeActiveAction('workflowRunning');
|
||||
}
|
||||
} else if (receivedData.type === 'testWebhookReceived') {
|
||||
// A test-webhook did get called
|
||||
const pushData = receivedData.data;
|
||||
|
||||
if (pushData.workflowId === this.workflowsStore.workflowId) {
|
||||
this.workflowsStore.executionWaitingForWebhook = false;
|
||||
this.workflowsStore.activeExecutionId = pushData.executionId;
|
||||
}
|
||||
|
||||
this.processWaitingPushMessages();
|
||||
} else if (receivedData.type === 'reloadNodeType') {
|
||||
this.nodeTypesStore.getNodeTypes();
|
||||
this.nodeTypesStore.getFullNodesProperties([receivedData.data]);
|
||||
} else if (receivedData.type === 'removeNodeType') {
|
||||
const pushData = receivedData.data;
|
||||
|
||||
const nodesToBeRemoved: INodeTypeNameVersion[] = [pushData];
|
||||
|
||||
// Force reload of all credential types
|
||||
this.credentialsStore.fetchCredentialTypes(false).then(() => {
|
||||
this.nodeTypesStore.removeNodeTypes(nodesToBeRemoved);
|
||||
});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -36,7 +36,7 @@ import { useRootStore } from '@/stores/n8nRootStore';
|
||||
*
|
||||
* @param {IExecutionFlattedResponse} fullExecutionData The data to unflatten
|
||||
*/
|
||||
function unflattenExecutionData (fullExecutionData: IExecutionFlattedResponse): IExecutionResponse {
|
||||
function unflattenExecutionData(fullExecutionData: IExecutionFlattedResponse): IExecutionResponse {
|
||||
// Unflatten the data
|
||||
const returnData: IExecutionResponse = {
|
||||
...fullExecutionData,
|
||||
@@ -55,15 +55,18 @@ function unflattenExecutionData (fullExecutionData: IExecutionFlattedResponse):
|
||||
|
||||
export const restApi = Vue.extend({
|
||||
computed: {
|
||||
...mapStores(
|
||||
useRootStore,
|
||||
),
|
||||
...mapStores(useRootStore),
|
||||
},
|
||||
methods: {
|
||||
restApi (): IRestApi {
|
||||
restApi(): IRestApi {
|
||||
const self = this;
|
||||
return {
|
||||
async makeRestApiRequest (method: Method, endpoint: string, data?: IDataObject): Promise<any> { // tslint:disable-line:no-any
|
||||
async makeRestApiRequest(
|
||||
method: Method,
|
||||
endpoint: string,
|
||||
data?: IDataObject,
|
||||
// tslint:disable-next-line:no-any
|
||||
): Promise<any> {
|
||||
return makeRestApiRequest(self.rootStore.getRestApiContext, method, endpoint, data);
|
||||
},
|
||||
getActiveWorkflows: (): Promise<string[]> => {
|
||||
@@ -82,11 +85,15 @@ export const restApi = Vue.extend({
|
||||
return self.restApi().makeRestApiRequest('GET', `/executions-current`, sendData);
|
||||
},
|
||||
stopCurrentExecution: (executionId: string): Promise<IExecutionsStopData> => {
|
||||
return self.restApi().makeRestApiRequest('POST', `/executions-current/${executionId}/stop`);
|
||||
return self
|
||||
.restApi()
|
||||
.makeRestApiRequest('POST', `/executions-current/${executionId}/stop`);
|
||||
},
|
||||
|
||||
getCredentialTranslation: (credentialType): Promise<object> => {
|
||||
return self.restApi().makeRestApiRequest('GET', '/credential-translation', { credentialType });
|
||||
return self
|
||||
.restApi()
|
||||
.makeRestApiRequest('GET', '/credential-translation', { credentialType });
|
||||
},
|
||||
|
||||
// Removes a test webhook
|
||||
@@ -105,8 +112,18 @@ export const restApi = Vue.extend({
|
||||
},
|
||||
|
||||
// Updates an existing workflow
|
||||
updateWorkflow: (id: string, data: IWorkflowDataUpdate, forceSave = false): Promise<IWorkflowDb> => {
|
||||
return self.restApi().makeRestApiRequest('PATCH', `/workflows/${id}${forceSave ? '?forceSave=true' : ''}`, data);
|
||||
updateWorkflow: (
|
||||
id: string,
|
||||
data: IWorkflowDataUpdate,
|
||||
forceSave = false,
|
||||
): Promise<IWorkflowDb> => {
|
||||
return self
|
||||
.restApi()
|
||||
.makeRestApiRequest(
|
||||
'PATCH',
|
||||
`/workflows/${id}${forceSave ? '?forceSave=true' : ''}`,
|
||||
data,
|
||||
);
|
||||
},
|
||||
|
||||
// Deletes a workflow
|
||||
@@ -159,7 +176,12 @@ export const restApi = Vue.extend({
|
||||
|
||||
// Returns all saved executions
|
||||
// TODO: For sure needs some kind of default filter like last day, with max 10 results, ...
|
||||
getPastExecutions: (filter: object, limit: number, lastId?: string | number, firstId?: string | number): Promise<IExecutionsListResponse> => {
|
||||
getPastExecutions: (
|
||||
filter: object,
|
||||
limit: number,
|
||||
lastId?: string | number,
|
||||
firstId?: string | number,
|
||||
): Promise<IExecutionsListResponse> => {
|
||||
let sendData = {};
|
||||
if (filter) {
|
||||
sendData = {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ElNotificationComponent, ElNotificationOptions } from 'element-ui/types
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
import { externalHooks } from '@/mixins/externalHooks';
|
||||
import {IExecuteContextData, IRunExecutionData} from 'n8n-workflow';
|
||||
import { IExecuteContextData, IRunExecutionData } from 'n8n-workflow';
|
||||
import type { ElMessageBoxOptions } from 'element-ui/types/message-box';
|
||||
import type { ElMessageComponent, ElMessageOptions, MessageType } from 'element-ui/types/message';
|
||||
import { sanitizeHtml } from '@/utils';
|
||||
@@ -14,9 +14,7 @@ let stickyNotificationQueue: ElNotificationComponent[] = [];
|
||||
|
||||
export const showMessage = mixins(externalHooks).extend({
|
||||
computed: {
|
||||
...mapStores(
|
||||
useWorkflowsStore,
|
||||
),
|
||||
...mapStores(useWorkflowsStore),
|
||||
},
|
||||
methods: {
|
||||
$showMessage(
|
||||
@@ -24,7 +22,9 @@ export const showMessage = mixins(externalHooks).extend({
|
||||
track = true,
|
||||
) {
|
||||
messageData.dangerouslyUseHTMLString = true;
|
||||
messageData.message = messageData.message ? sanitizeHtml(messageData.message) : messageData.message;
|
||||
messageData.message = messageData.message
|
||||
? sanitizeHtml(messageData.message)
|
||||
: messageData.message;
|
||||
|
||||
if (messageData.position === undefined) {
|
||||
messageData.position = 'bottom-right';
|
||||
@@ -49,15 +49,15 @@ export const showMessage = mixins(externalHooks).extend({
|
||||
},
|
||||
|
||||
$showToast(config: {
|
||||
title: string,
|
||||
message: string,
|
||||
onClick?: () => void,
|
||||
onClose?: () => void,
|
||||
duration?: number,
|
||||
customClass?: string,
|
||||
closeOnClick?: boolean,
|
||||
type?: MessageType,
|
||||
}) {
|
||||
title: string;
|
||||
message: string;
|
||||
onClick?: () => void;
|
||||
onClose?: () => void;
|
||||
duration?: number;
|
||||
customClass?: string;
|
||||
closeOnClick?: boolean;
|
||||
type?: MessageType;
|
||||
}) {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let notification: ElNotificationComponent;
|
||||
if (config.closeOnClick) {
|
||||
@@ -102,14 +102,10 @@ export const showMessage = mixins(externalHooks).extend({
|
||||
if (error && error.message) {
|
||||
let nodeName: string | undefined;
|
||||
if ('node' in error) {
|
||||
nodeName = typeof error.node === 'string'
|
||||
? error.node
|
||||
: error.node!.name;
|
||||
nodeName = typeof error.node === 'string' ? error.node : error.node!.name;
|
||||
}
|
||||
|
||||
const receivedError = nodeName
|
||||
? `${nodeName}: ${error.message}`
|
||||
: error.message;
|
||||
const receivedError = nodeName ? `${nodeName}: ${error.message}` : error.message;
|
||||
errorMessage = `There was a problem executing the workflow:<br /><strong>"${receivedError}"</strong>`;
|
||||
}
|
||||
}
|
||||
@@ -120,15 +116,18 @@ export const showMessage = mixins(externalHooks).extend({
|
||||
$showError(e: Error | unknown, title: string, message?: string) {
|
||||
const error = e as Error;
|
||||
const messageLine = message ? `${message}<br/>` : '';
|
||||
this.$showMessage({
|
||||
title,
|
||||
message: `
|
||||
this.$showMessage(
|
||||
{
|
||||
title,
|
||||
message: `
|
||||
${messageLine}
|
||||
<i>${error.message}</i>
|
||||
${this.collapsableDetails(error)}`,
|
||||
type: 'error',
|
||||
duration: 0,
|
||||
}, false);
|
||||
type: 'error',
|
||||
duration: 0,
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
this.$externalHooks().run('showMessage.showError', {
|
||||
title,
|
||||
@@ -145,9 +144,15 @@ export const showMessage = mixins(externalHooks).extend({
|
||||
});
|
||||
},
|
||||
|
||||
async confirmMessage (message: string, headline: string, type: MessageType | null = 'warning', confirmButtonText?: string, cancelButtonText?: string): Promise<boolean> {
|
||||
async confirmMessage(
|
||||
message: string,
|
||||
headline: string,
|
||||
type: MessageType | null = 'warning',
|
||||
confirmButtonText?: string,
|
||||
cancelButtonText?: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const options: ElMessageBoxOptions = {
|
||||
const options: ElMessageBoxOptions = {
|
||||
confirmButtonText: confirmButtonText || this.$locale.baseText('showMessage.ok'),
|
||||
cancelButtonText: cancelButtonText || this.$locale.baseText('showMessage.cancel'),
|
||||
dangerouslyUseHTMLString: true,
|
||||
@@ -162,9 +167,16 @@ export const showMessage = mixins(externalHooks).extend({
|
||||
}
|
||||
},
|
||||
|
||||
async confirmModal (message: string, headline: string, type: MessageType | null = 'warning', confirmButtonText?: string, cancelButtonText?: string, showClose = false): Promise<string> {
|
||||
async confirmModal(
|
||||
message: string,
|
||||
headline: string,
|
||||
type: MessageType | null = 'warning',
|
||||
confirmButtonText?: string,
|
||||
cancelButtonText?: string,
|
||||
showClose = false,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const options: ElMessageBoxOptions = {
|
||||
const options: ElMessageBoxOptions = {
|
||||
confirmButtonText: confirmButtonText || this.$locale.baseText('showMessage.ok'),
|
||||
cancelButtonText: cancelButtonText || this.$locale.baseText('showMessage.cancel'),
|
||||
dangerouslyUseHTMLString: true,
|
||||
@@ -195,9 +207,7 @@ export const showMessage = mixins(externalHooks).extend({
|
||||
if (!description) return '';
|
||||
|
||||
const errorDescription =
|
||||
description.length > 500
|
||||
? `${description.slice(0, 500)}...`
|
||||
: description;
|
||||
description.length > 500 ? `${description.slice(0, 500)}...` : description;
|
||||
|
||||
return `
|
||||
<br>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
import {
|
||||
WorkflowTitleStatus,
|
||||
} from '../../Interface';
|
||||
import { WorkflowTitleStatus } from '../../Interface';
|
||||
|
||||
export const titleChange = Vue.extend({
|
||||
methods: {
|
||||
@@ -26,6 +24,5 @@ export const titleChange = Vue.extend({
|
||||
$titleReset() {
|
||||
document.title = `n8n - Workflow Automation`;
|
||||
},
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Route } from 'vue-router';
|
||||
export const userHelpers = Vue.extend({
|
||||
methods: {
|
||||
canUserAccessRouteByName(name: string): boolean {
|
||||
const {route} = this.$router.resolve({name});
|
||||
const { route } = this.$router.resolve({ name });
|
||||
|
||||
return this.canUserAccessRoute(route);
|
||||
},
|
||||
|
||||
@@ -2,114 +2,122 @@ import { externalHooks } from '@/mixins/externalHooks';
|
||||
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
||||
import { showMessage } from '@/mixins/showMessage';
|
||||
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { LOCAL_STORAGE_ACTIVATION_FLAG, PLACEHOLDER_EMPTY_WORKFLOW_ID, WORKFLOW_ACTIVE_MODAL_KEY } from '@/constants';
|
||||
import {
|
||||
LOCAL_STORAGE_ACTIVATION_FLAG,
|
||||
PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
||||
WORKFLOW_ACTIVE_MODAL_KEY,
|
||||
} from '@/constants';
|
||||
import { mapStores } from 'pinia';
|
||||
import { useUIStore } from '@/stores/ui';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { useWorkflowsStore } from '@/stores/workflows';
|
||||
|
||||
export const workflowActivate = mixins(
|
||||
externalHooks,
|
||||
workflowHelpers,
|
||||
showMessage,
|
||||
)
|
||||
.extend({
|
||||
data() {
|
||||
return {
|
||||
updatingWorkflowActivation: false,
|
||||
};
|
||||
export const workflowActivate = mixins(externalHooks, workflowHelpers, showMessage).extend({
|
||||
data() {
|
||||
return {
|
||||
updatingWorkflowActivation: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useSettingsStore, useUIStore, useWorkflowsStore),
|
||||
},
|
||||
methods: {
|
||||
async activateCurrentWorkflow(telemetrySource?: string) {
|
||||
const workflowId = this.workflowsStore.workflowId;
|
||||
return this.updateWorkflowActivation(workflowId, true, telemetrySource);
|
||||
},
|
||||
computed: {
|
||||
...mapStores(
|
||||
useSettingsStore,
|
||||
useUIStore,
|
||||
useWorkflowsStore,
|
||||
),
|
||||
},
|
||||
methods: {
|
||||
async activateCurrentWorkflow(telemetrySource?: string) {
|
||||
const workflowId = this.workflowsStore.workflowId;
|
||||
return this.updateWorkflowActivation(workflowId, true, telemetrySource);
|
||||
},
|
||||
async updateWorkflowActivation(workflowId: string | undefined, newActiveState: boolean, telemetrySource?: string) {
|
||||
this.updatingWorkflowActivation = true;
|
||||
const nodesIssuesExist = this.workflowsStore.nodesIssuesExist as boolean;
|
||||
async updateWorkflowActivation(
|
||||
workflowId: string | undefined,
|
||||
newActiveState: boolean,
|
||||
telemetrySource?: string,
|
||||
) {
|
||||
this.updatingWorkflowActivation = true;
|
||||
const nodesIssuesExist = this.workflowsStore.nodesIssuesExist as boolean;
|
||||
|
||||
let currWorkflowId: string | undefined = workflowId;
|
||||
if (!currWorkflowId || currWorkflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
|
||||
const saved = await this.saveCurrentWorkflow();
|
||||
if (!saved) {
|
||||
this.updatingWorkflowActivation = false;
|
||||
return;
|
||||
}
|
||||
currWorkflowId = this.workflowsStore.workflowId as string;
|
||||
let currWorkflowId: string | undefined = workflowId;
|
||||
if (!currWorkflowId || currWorkflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
|
||||
const saved = await this.saveCurrentWorkflow();
|
||||
if (!saved) {
|
||||
this.updatingWorkflowActivation = false;
|
||||
return;
|
||||
}
|
||||
const isCurrentWorkflow = currWorkflowId === this.workflowsStore.workflowId;
|
||||
currWorkflowId = this.workflowsStore.workflowId as string;
|
||||
}
|
||||
const isCurrentWorkflow = currWorkflowId === this.workflowsStore.workflowId;
|
||||
|
||||
const activeWorkflows = this.workflowsStore.activeWorkflows;
|
||||
const isWorkflowActive = activeWorkflows.includes(currWorkflowId);
|
||||
const activeWorkflows = this.workflowsStore.activeWorkflows;
|
||||
const isWorkflowActive = activeWorkflows.includes(currWorkflowId);
|
||||
|
||||
const telemetryPayload = {
|
||||
workflow_id: currWorkflowId,
|
||||
is_active: newActiveState,
|
||||
previous_status: isWorkflowActive,
|
||||
ndv_input: telemetrySource === 'ndv',
|
||||
};
|
||||
this.$telemetry.track('User set workflow active status', telemetryPayload);
|
||||
this.$externalHooks().run('workflowActivate.updateWorkflowActivation', telemetryPayload);
|
||||
const telemetryPayload = {
|
||||
workflow_id: currWorkflowId,
|
||||
is_active: newActiveState,
|
||||
previous_status: isWorkflowActive,
|
||||
ndv_input: telemetrySource === 'ndv',
|
||||
};
|
||||
this.$telemetry.track('User set workflow active status', telemetryPayload);
|
||||
this.$externalHooks().run('workflowActivate.updateWorkflowActivation', telemetryPayload);
|
||||
|
||||
try {
|
||||
if (isWorkflowActive && newActiveState) {
|
||||
this.$showMessage({
|
||||
title: this.$locale.baseText('workflowActivator.workflowIsActive'),
|
||||
type: 'success',
|
||||
});
|
||||
this.updatingWorkflowActivation = false;
|
||||
try {
|
||||
if (isWorkflowActive && newActiveState) {
|
||||
this.$showMessage({
|
||||
title: this.$locale.baseText('workflowActivator.workflowIsActive'),
|
||||
type: 'success',
|
||||
});
|
||||
this.updatingWorkflowActivation = false;
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCurrentWorkflow && nodesIssuesExist) {
|
||||
this.$showMessage({
|
||||
title: this.$locale.baseText('workflowActivator.showMessage.activeChangedNodesIssuesExistTrue.title'),
|
||||
message: this.$locale.baseText('workflowActivator.showMessage.activeChangedNodesIssuesExistTrue.message'),
|
||||
type: 'error',
|
||||
});
|
||||
if (isCurrentWorkflow && nodesIssuesExist) {
|
||||
this.$showMessage({
|
||||
title: this.$locale.baseText(
|
||||
'workflowActivator.showMessage.activeChangedNodesIssuesExistTrue.title',
|
||||
),
|
||||
message: this.$locale.baseText(
|
||||
'workflowActivator.showMessage.activeChangedNodesIssuesExistTrue.message',
|
||||
),
|
||||
type: 'error',
|
||||
});
|
||||
|
||||
this.updatingWorkflowActivation = false;
|
||||
return;
|
||||
}
|
||||
|
||||
await this.updateWorkflow({workflowId: currWorkflowId, active: newActiveState});
|
||||
} catch (error) {
|
||||
const newStateName = newActiveState === true ? 'activated' : 'deactivated';
|
||||
this.$showError(
|
||||
error,
|
||||
this.$locale.baseText(
|
||||
'workflowActivator.showError.title',
|
||||
{ interpolate: { newStateName } },
|
||||
) + ':',
|
||||
);
|
||||
this.updatingWorkflowActivation = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const activationEventName = isCurrentWorkflow ? 'workflow.activeChangeCurrent' : 'workflow.activeChange';
|
||||
this.$externalHooks().run(activationEventName, { workflowId: currWorkflowId, active: newActiveState });
|
||||
|
||||
this.$emit('workflowActiveChanged', { id: currWorkflowId, active: newActiveState });
|
||||
await this.updateWorkflow({ workflowId: currWorkflowId, active: newActiveState });
|
||||
} catch (error) {
|
||||
const newStateName = newActiveState === true ? 'activated' : 'deactivated';
|
||||
this.$showError(
|
||||
error,
|
||||
this.$locale.baseText('workflowActivator.showError.title', {
|
||||
interpolate: { newStateName },
|
||||
}) + ':',
|
||||
);
|
||||
this.updatingWorkflowActivation = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCurrentWorkflow) {
|
||||
if (newActiveState && window.localStorage.getItem(LOCAL_STORAGE_ACTIVATION_FLAG) !== 'true') {
|
||||
this.uiStore.openModal(WORKFLOW_ACTIVE_MODAL_KEY);
|
||||
}
|
||||
else {
|
||||
this.settingsStore.fetchPromptsData();
|
||||
}
|
||||
const activationEventName = isCurrentWorkflow
|
||||
? 'workflow.activeChangeCurrent'
|
||||
: 'workflow.activeChange';
|
||||
this.$externalHooks().run(activationEventName, {
|
||||
workflowId: currWorkflowId,
|
||||
active: newActiveState,
|
||||
});
|
||||
|
||||
this.$emit('workflowActiveChanged', { id: currWorkflowId, active: newActiveState });
|
||||
this.updatingWorkflowActivation = false;
|
||||
|
||||
if (isCurrentWorkflow) {
|
||||
if (
|
||||
newActiveState &&
|
||||
window.localStorage.getItem(LOCAL_STORAGE_ACTIVATION_FLAG) !== 'true'
|
||||
) {
|
||||
this.uiStore.openModal(WORKFLOW_ACTIVE_MODAL_KEY);
|
||||
} else {
|
||||
this.settingsStore.fetchPromptsData();
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
IExecutionPushResponse,
|
||||
IExecutionResponse,
|
||||
IStartRunData,
|
||||
} from '@/Interface';
|
||||
import { IExecutionPushResponse, IExecutionResponse, IStartRunData } from '@/Interface';
|
||||
|
||||
import {
|
||||
IRunData,
|
||||
@@ -32,21 +28,15 @@ export const workflowRun = mixins(
|
||||
titleChange,
|
||||
).extend({
|
||||
computed: {
|
||||
...mapStores(
|
||||
useRootStore,
|
||||
useUIStore,
|
||||
useWorkflowsStore,
|
||||
),
|
||||
...mapStores(useRootStore, useUIStore, useWorkflowsStore),
|
||||
},
|
||||
methods: {
|
||||
// Starts to executes a workflow on server.
|
||||
async runWorkflowApi (runData: IStartRunData): Promise<IExecutionPushResponse> {
|
||||
async runWorkflowApi(runData: IStartRunData): Promise<IExecutionPushResponse> {
|
||||
if (this.rootStore.pushConnectionActive === false) {
|
||||
// Do not start if the connection to server is not active
|
||||
// because then it can not receive the data as it executes.
|
||||
throw new Error(
|
||||
this.$locale.baseText('workflowRun.noActiveConnectionToTheServer'),
|
||||
);
|
||||
throw new Error(this.$locale.baseText('workflowRun.noActiveConnectionToTheServer'));
|
||||
}
|
||||
|
||||
this.workflowsStore.subWorkflowExecutionError = null;
|
||||
@@ -72,7 +62,10 @@ export const workflowRun = mixins(
|
||||
|
||||
return response;
|
||||
},
|
||||
async runWorkflow (nodeName?: string, source?: string): Promise<IExecutionPushResponse | undefined> {
|
||||
async runWorkflow(
|
||||
nodeName?: string,
|
||||
source?: string,
|
||||
): Promise<IExecutionPushResponse | undefined> {
|
||||
const workflow = this.getCurrentWorkflow();
|
||||
|
||||
if (this.uiStore.isActionActive('workflowRunning')) {
|
||||
@@ -134,7 +127,12 @@ export const workflowRun = mixins(
|
||||
workflow_id: workflow.id,
|
||||
workflow_name: workflow.name,
|
||||
execution_type: nodeName ? 'node' : 'workflow',
|
||||
node_graph_string: JSON.stringify(TelemetryHelpers.generateNodesGraph(workflowData as IWorkflowBase, this.getNodeTypes()).nodeGraph),
|
||||
node_graph_string: JSON.stringify(
|
||||
TelemetryHelpers.generateNodesGraph(
|
||||
workflowData as IWorkflowBase,
|
||||
this.getNodeTypes(),
|
||||
).nodeGraph,
|
||||
),
|
||||
error_node_types: JSON.stringify(trackErrorNodeTypes),
|
||||
errors: JSON.stringify(trackNodeIssues),
|
||||
});
|
||||
@@ -245,13 +243,10 @@ export const workflowRun = mixins(
|
||||
|
||||
this.$externalHooks().run('workflowRun.runWorkflow', { nodeName, source });
|
||||
|
||||
return runWorkflowApiResponse;
|
||||
return runWorkflowApiResponse;
|
||||
} catch (error) {
|
||||
this.$titleSet(workflow.name as string, 'ERROR');
|
||||
this.$showError(
|
||||
error,
|
||||
this.$locale.baseText('workflowRun.showError.title'),
|
||||
);
|
||||
this.$showError(error, this.$locale.baseText('workflowRun.showError.title'));
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user