n8n/packages/editor-ui/src/store.ts
Oliver Trajceski cfa91cda27
Add Templates (#2720)
* Templates Bugs / Fixed Various Bugs / Multiply Api Request, Carousel Gradient, Core Nodes Filters ...

* Updated MainSidebar Paddings

* N8N-Templates Bugfixing - Remove Unnecesairy Icon (Shape), Refatctor infiniteScrollEnabled Prop + updated infiniterScroll functinality

* N8N-2853 Fixed Carousel Arrows Bug after Cleaning the SearchBar

* fix telemetry init

* fix search tracking issues

* N8N-2853 Created FilterTemplateNode Constant Array, Filter PlayButton and WebhookRespond from Nodes, Added Box for showing more nodes inside TemplateList, Updated NewWorkflowButton to primary, Fixed Markdown issue with Code

* N8N-2853 Removed Placeholder if Workflows Or Collections are not found, Updated the Logic

* fix telemetry events

* clean up session id

* update user inserted event

* N8N-2853 Fixed Categories to Moving if the names are long

* Add todos

* Update Routes on loading

* fix spacing

* Update Border Color

* Update Border Readius

* fix filter fn

* fix constant, console error

* N8N-2853 PR Fixes, Refactoring, Removing unnecesairy code ..

* N8N-2853 PR Fixes - Editor-ui Fixes, Refactoring, Removing Dead Code ...

* N8N-2853 Refactor Card to LongCard

* clean up spacing, replace css var

* clean up spacing

* set categories as optional in node

* replace vars

* refactor store

* remove unnesssary import

* fix error

* fix templates view to start

* add to cache

* fix coll view data

* fix categories

* fix category event

* fix collections carousel

* fix initial load and search

* fix infinite load

* fix query param

* fix scrolling issues

* fix scroll to top

* fix search

* fix collections search

* fix navigation bug

* rename view

* update package lock

* rename workflow view

* rename coll view

* update routes

* add wrapper component

* set session id

* fix search tracking

* fix session tracking

* remove deleted mutation

* remove check for unsupported nodes

* refactor filters

* lazy load template

* clean up types

* refactor infinte scroll

* fix end of search

* Fix spacing

* fix coll loading

* fix types

* fix coll view list

* fix navigation

* rename types

* rename state

* fix search responsiveness

* fix coll view spacing

* fix search view spacing

* clean up views

* set background color

* center page not vert

* fix workflow view

* remove import

* fix background color

* fix background

* clean props

* clean up imports

* refactor button

* update background color

* fix spacing issue

* rename event

* update telemetry event

* update endpoints, add loading view, check for endpoint health

* remove conolse log

* N8N-2853 Fixed Menu Items Padding

* replace endpoints

* fix type issues

* fix categories

* N8N-2853 Fixed ParameterInput Placeholder after ElementUI Upgrade

* update createdAt

*  Fix placeholder in creds config modal

* ✏️ Adjust docstring to `credText` placeholder version

* N8N-2853 Optimized

* N8N-2853 Optimized code

*  Add deployment type to FE settings

*  Add deployment type to interfaces

* N8N-2853 Removed Animated prop from components

*  Add deployment type to store module

*  Create hiring banner

*  Display hiring banner

*  Undo unrelated change

* N8N-2853 Refactor TemplateFilters

*  Fix indentation

* N8N-2853 Reorder items / TemplateList

* 👕 Fix lint

* N8N-2853 Refactor TemplateFilters Component

* N8N-2853 Reorder TemplateList

* refactor template card

* update timeout

* fix removelistener

* fix spacing

* split enabled from offline

* add spacing to go back

* N8N-2853 Fixed Screens for Tablet & Mobile

* N8N-2853 Update Stores Order

* remove image componet

* remove placeholder changes

* N8N-2853 Fixed Chinnese Placeholders for El Select Component that comes from the Library Upgrade

* N8N-2853 Fixed Vue Agile Console Warnings

* N8N-2853 Update Collection Route

* ✏️ Update jobs URL

* 🚚 Move logging to root component

*  Refactor `deploymentType` to `isInternalUser`

*  Improve syntax

* fix cut bug in readonly view

* N8N-3012 Fixed Details section in templates with lots of description, Fixed Mardown Block with overflox-x

* N8N-3012 Increased Font-size, Spacing and Line-height of the Categories Items

* N8N-3012 Fixed Vue-agile client width error on resize

* only delay redirect for root path

* N8N-3012 Fixed Carousel Arrows that Disappear

* N8N-3012 Make Loading Screen same color as Templates

* N8N-3012 Markdown renders inline block as block code

* add offline warning

* hide log from workflow iframe

* update text

* make search button larger

* N8N-3012 Categories / Tags extended all the way in details section

* load data in cred modals

* remove deleted message

* add external hook

* remove import

* update env variable description

* fix markdown width issue

* disable telemetry for demo, add session id to template pages

* fix telemetery bugs

* N8N-3012 Not found Collections/Wokrkflow

* N8N-3012 Checkboxes change order when categories are changed

* N8N-3012 Refactor SortedCategories inside TemplateFilters component

* fix firefox bug

* add telemetry requirements

* add error check

* N8N-3012 Update GoBackButton to check if Route History is present

* N8N-3012 Fixed WF Nodes Icons

* hide workflow screenshots

* remove unnessary mixins

* rename prop

* fix design a bit

* rename data

* clear workspace on destroy

* fix copy paste bug

* fix disabled state

* N8N-3012 Fixed Saving/Leave without saving Modal

* fix telemetry issue

* fix telemetry issues, error bug

* fix error notification

* disable workflow menu items on templates

* fix i18n elementui issue

* Remove Emit - NodeType from HoverableNodeIcon component

* TechnicalFixes: NavigateTo passed down as function should be helper

* TechnicalFixes: Update NavigateTo function

* TechnicalFixes: Add FilterCoreNodes directly as function

* check for empty connecitions

* fix titles

* respect new lines

* increase categories to be sliced

* rename prop

* onUseWorkflow

* refactor click event

* fix bug, refactor

* fix loading story

* add default

* fix styles at right level of abstraction

* add wrapper with width

* remove loading blocks component

* add story

* rename prop

* fix spacing

* refactor tag, add story

* move margin to container

* fix tag redirect, remove unnessary check

* make version optional

* rename view

* move from workflows to templates store

* remove unnessary change

* remove unnessary css

* rename component

* refactor collection card

* add boolean to prevent shrink

* clean up carousel

* fix redirection bug on save

* remove listeners to fix multiple listeners bug

* remove unnessary types

* clean up boolean set

* fix node select bug

* rename component

* remove unnessary class

* fix redirection bug

* remove unnessary error

* fix typo

* fix blockquotes, pre

* refactor markdown rendering

* remove console log

* escape markdown

* fix safari bug

* load active workflows to fix modal bug

* ⬆️ Update package-lock.json file

*  Add n8n version as header

Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com>
Co-authored-by: Mutasem <mutdmour@gmail.com>
Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
2022-02-28 10:57:44 +01:00

913 lines
27 KiB
TypeScript

import Vue from 'vue';
import Vuex from 'vuex';
import { PLACEHOLDER_EMPTY_WORKFLOW_ID, DEFAULT_NODETYPE_VERSION } from '@/constants';
import {
IConnection,
IConnections,
IDataObject,
INodeConnections,
INodeIssueData,
INodeTypeDescription,
IRunData,
ITaskData,
IWorkflowSettings,
} from 'n8n-workflow';
import {
IExecutionResponse,
IExecutionsCurrentSummaryExtended,
IRootState,
IMenuItem,
INodeUi,
INodeUpdatePropertiesInformation,
IPushDataExecutionFinished,
IPushDataNodeExecuteAfter,
IUpdateInformation,
IWorkflowDb,
XYPosition,
IRestApiContext,
} from './Interface';
import credentials from './modules/credentials';
import tags from './modules/tags';
import settings from './modules/settings';
import ui from './modules/ui';
import workflows from './modules/workflows';
import versions from './modules/versions';
import templates from './modules/templates';
Vue.use(Vuex);
const state: IRootState = {
activeExecutions: [],
activeWorkflows: [],
activeActions: [],
activeNode: null,
activeCredentialType: null,
// @ts-ignore
baseUrl: process.env.VUE_APP_URL_BASE_API ? process.env.VUE_APP_URL_BASE_API : (window.BASE_PATH === '/%BASE_PATH%/' ? '/' : window.BASE_PATH),
defaultLocale: 'en',
endpointWebhook: 'webhook',
endpointWebhookTest: 'webhook-test',
executionId: null,
executingNode: '',
executionWaitingForWebhook: false,
pushConnectionActive: true,
saveDataErrorExecution: 'all',
saveDataSuccessExecution: 'all',
saveManualExecutions: false,
timezone: 'America/New_York',
stateIsDirty: false,
executionTimeout: -1,
maxExecutionTimeout: Number.MAX_SAFE_INTEGER,
versionCli: '0.0.0',
oauthCallbackUrls: {},
n8nMetadata: {},
workflowExecutionData: null,
lastSelectedNode: null,
lastSelectedNodeOutputIndex: null,
nodeIndex: [],
nodeTypes: [],
nodeViewOffsetPosition: [0, 0],
nodeViewMoveInProgress: false,
selectedNodes: [],
sessionId: Math.random().toString(36).substring(2, 15),
urlBaseWebhook: 'http://localhost:5678/',
workflow: {
id: PLACEHOLDER_EMPTY_WORKFLOW_ID,
name: '',
active: false,
createdAt: -1,
updatedAt: -1,
connections: {},
nodes: [],
settings: {},
tags: [],
},
sidebarMenuItems: [],
instanceId: '',
};
const modules = {
credentials,
tags,
settings,
templates,
workflows,
versions,
ui,
};
export const store = new Vuex.Store({
strict: process.env.NODE_ENV !== 'production',
modules,
state,
mutations: {
// Active Actions
addActiveAction (state, action: string) {
if (!state.activeActions.includes(action)) {
state.activeActions.push(action);
}
},
removeActiveAction (state, action: string) {
const actionIndex = state.activeActions.indexOf(action);
if (actionIndex !== -1) {
state.activeActions.splice(actionIndex, 1);
}
},
// Active Executions
addActiveExecution (state, newActiveExecution: IExecutionsCurrentSummaryExtended) {
// Check if the execution exists already
const activeExecution = state.activeExecutions.find(execution => {
return execution.id === newActiveExecution.id;
});
if (activeExecution !== undefined) {
// Exists already so no need to add it again
if (activeExecution.workflowName === undefined) {
activeExecution.workflowName = newActiveExecution.workflowName;
}
return;
}
state.activeExecutions.unshift(newActiveExecution);
},
finishActiveExecution (state, finishedActiveExecution: IPushDataExecutionFinished) {
// Find the execution to set to finished
const activeExecution = state.activeExecutions.find(execution => {
return execution.id === finishedActiveExecution.executionId;
});
if (activeExecution === undefined) {
// The execution could not be found
return;
}
if (finishedActiveExecution.executionId !== undefined) {
Vue.set(activeExecution, 'id', finishedActiveExecution.executionId);
}
Vue.set(activeExecution, 'finished', finishedActiveExecution.data.finished);
Vue.set(activeExecution, 'stoppedAt', finishedActiveExecution.data.stoppedAt);
},
setActiveExecutions (state, newActiveExecutions: IExecutionsCurrentSummaryExtended[]) {
Vue.set(state, 'activeExecutions', newActiveExecutions);
},
// Active Workflows
setActiveWorkflows (state, newActiveWorkflows: string[]) {
state.activeWorkflows = newActiveWorkflows;
},
setWorkflowActive (state, workflowId: string) {
state.stateIsDirty = false;
const index = state.activeWorkflows.indexOf(workflowId);
if (index === -1) {
state.activeWorkflows.push(workflowId);
}
},
setWorkflowInactive (state, workflowId: string) {
const index = state.activeWorkflows.indexOf(workflowId);
if (index !== -1) {
state.selectedNodes.splice(index, 1);
}
},
// Set state condition dirty or not
// ** Dirty: if current workflow state has been synchronized with database AKA has it been saved
setStateDirty (state, dirty : boolean) {
state.stateIsDirty = dirty;
},
// Selected Nodes
addSelectedNode (state, node: INodeUi) {
state.selectedNodes.push(node);
},
removeNodeFromSelection (state, node: INodeUi) {
let index;
for (index in state.selectedNodes) {
if (state.selectedNodes[index].name === node.name) {
state.selectedNodes.splice(parseInt(index, 10), 1);
break;
}
}
},
resetSelectedNodes (state) {
Vue.set(state, 'selectedNodes', []);
},
// Active
setActive (state, newActive: boolean) {
state.workflow.active = newActive;
},
// Connections
addConnection (state, data) {
if (data.connection.length !== 2) {
// All connections need two entries
// TODO: Check if there is an error or whatever that is supposed to be returned
return;
}
if (data.setStateDirty === true) {
state.stateIsDirty = true;
}
const sourceData: IConnection = data.connection[0];
const destinationData: IConnection = data.connection[1];
// Check if source node and type exist already and if not add them
if (!state.workflow.connections.hasOwnProperty(sourceData.node)) {
Vue.set(state.workflow.connections, sourceData.node, {});
}
if (!state.workflow.connections[sourceData.node].hasOwnProperty(sourceData.type)) {
Vue.set(state.workflow.connections[sourceData.node], sourceData.type, []);
}
if (state.workflow.connections[sourceData.node][sourceData.type].length < (sourceData.index + 1)) {
for (let i = state.workflow.connections[sourceData.node][sourceData.type].length; i <= sourceData.index; i++) {
state.workflow.connections[sourceData.node][sourceData.type].push([]);
}
}
// Check if the same connection exists already
const checkProperties = ['index', 'node', 'type'];
let propertyName: string;
let connectionExists = false;
connectionLoop:
for (const existingConnection of state.workflow.connections[sourceData.node][sourceData.type][sourceData.index]) {
for (propertyName of checkProperties) {
if ((existingConnection as any)[propertyName] !== (destinationData as any)[propertyName]) { // tslint:disable-line:no-any
continue connectionLoop;
}
}
connectionExists = true;
break;
}
// Add the new connection if it does not exist already
if (connectionExists === false) {
state.workflow.connections[sourceData.node][sourceData.type][sourceData.index].push(destinationData);
}
},
removeConnection (state, data) {
const sourceData = data.connection[0];
const destinationData = data.connection[1];
if (!state.workflow.connections.hasOwnProperty(sourceData.node)) {
return;
}
if (!state.workflow.connections[sourceData.node].hasOwnProperty(sourceData.type)) {
return;
}
if (state.workflow.connections[sourceData.node][sourceData.type].length < (sourceData.index + 1)) {
return;
}
state.stateIsDirty = true;
const connections = state.workflow.connections[sourceData.node][sourceData.type][sourceData.index];
for (const index in connections) {
if (connections[index].node === destinationData.node && connections[index].type === destinationData.type && connections[index].index === destinationData.index) {
// Found the connection to remove
connections.splice(parseInt(index, 10), 1);
}
}
},
removeAllConnections (state, data) {
if (data && data.setStateDirty === true) {
state.stateIsDirty = true;
}
state.workflow.connections = {};
},
removeAllNodeConnection (state, node: INodeUi) {
state.stateIsDirty = true;
// Remove all source connections
if (state.workflow.connections.hasOwnProperty(node.name)) {
delete state.workflow.connections[node.name];
}
// Remove all destination connections
const indexesToRemove = [];
let sourceNode: string, type: string, sourceIndex: string, connectionIndex: string, connectionData: IConnection;
for (sourceNode of Object.keys(state.workflow.connections)) {
for (type of Object.keys(state.workflow.connections[sourceNode])) {
for (sourceIndex of Object.keys(state.workflow.connections[sourceNode][type])) {
indexesToRemove.length = 0;
for (connectionIndex of Object.keys(state.workflow.connections[sourceNode][type][parseInt(sourceIndex, 10)])) {
connectionData = state.workflow.connections[sourceNode][type][parseInt(sourceIndex, 10)][parseInt(connectionIndex, 10)];
if (connectionData.node === node.name) {
indexesToRemove.push(connectionIndex);
}
}
indexesToRemove.forEach((index) => {
state.workflow.connections[sourceNode][type][parseInt(sourceIndex, 10)].splice(parseInt(index, 10), 1);
});
}
}
}
},
renameNodeSelectedAndExecution (state, nameData) {
state.stateIsDirty = true;
// If node has any WorkflowResultData rename also that one that the data
// does still get displayed also after node got renamed
if (state.workflowExecutionData !== null && state.workflowExecutionData.data.resultData.runData.hasOwnProperty(nameData.old)) {
state.workflowExecutionData.data.resultData.runData[nameData.new] = state.workflowExecutionData.data.resultData.runData[nameData.old];
delete state.workflowExecutionData.data.resultData.runData[nameData.old];
}
// In case the renamed node was last selected set it also there with the new name
if (state.lastSelectedNode === nameData.old) {
state.lastSelectedNode = nameData.new;
}
},
resetAllNodesIssues (state) {
state.workflow.nodes.forEach((node) => {
node.issues = undefined;
});
return true;
},
setNodeIssue (state, nodeIssueData: INodeIssueData) {
const node = state.workflow.nodes.find(node => {
return node.name === nodeIssueData.node;
});
if (!node) {
return false;
}
if (nodeIssueData.value === null) {
// Remove the value if one exists
if (node.issues === undefined || node.issues[nodeIssueData.type] === undefined) {
// No values for type exist so nothing has to get removed
return true;
}
// @ts-ignore
Vue.delete(node.issues, nodeIssueData.type);
} else {
if (node.issues === undefined) {
Vue.set(node, 'issues', {});
}
// Set/Overwrite the value
Vue.set(node.issues!, nodeIssueData.type, nodeIssueData.value);
}
return true;
},
// Id
setWorkflowId (state, id: string) {
state.workflow.id = id;
},
// Name
setWorkflowName (state, data) {
if (data.setStateDirty === true) {
state.stateIsDirty = true;
}
state.workflow.name = data.newName;
},
// replace invalid credentials in workflow
replaceInvalidWorkflowCredentials(state, {credentials, invalid, type }) {
state.workflow.nodes.forEach((node) => {
if (!node.credentials || !node.credentials[type]) {
return;
}
const nodeCredentials = node.credentials[type];
if (typeof nodeCredentials === 'string' && nodeCredentials === invalid.name) {
node.credentials[type] = credentials;
return;
}
if (nodeCredentials.id === null) {
if (nodeCredentials.name === invalid.name){
node.credentials[type] = credentials;
}
return;
}
if (nodeCredentials.id === invalid.id) {
node.credentials[type] = credentials;
}
});
},
// Nodes
addNode (state, nodeData: INodeUi) {
if (!nodeData.hasOwnProperty('name')) {
// All nodes have to have a name
// TODO: Check if there is an error or whatever that is supposed to be returned
return;
}
state.workflow.nodes.push(nodeData);
},
removeNode (state, node: INodeUi) {
for (let i = 0; i < state.workflow.nodes.length; i++) {
if (state.workflow.nodes[i].name === node.name) {
state.workflow.nodes.splice(i, 1);
state.stateIsDirty = true;
return;
}
}
},
removeAllNodes (state, data) {
if (data.setStateDirty === true) {
state.stateIsDirty = true;
}
state.workflow.nodes.splice(0, state.workflow.nodes.length);
},
updateNodeProperties (state, updateInformation: INodeUpdatePropertiesInformation) {
// Find the node that should be updated
const node = state.workflow.nodes.find(node => {
return node.name === updateInformation.name;
});
if (node) {
for (const key of Object.keys(updateInformation.properties)) {
state.stateIsDirty = true;
Vue.set(node, key, updateInformation.properties[key]);
}
}
},
setNodeValue (state, updateInformation: IUpdateInformation) {
// Find the node that should be updated
const node = state.workflow.nodes.find(node => {
return node.name === updateInformation.name;
});
if (node === undefined || node === null) {
throw new Error(`Node with the name "${updateInformation.name}" could not be found to set parameter.`);
}
state.stateIsDirty = true;
Vue.set(node, updateInformation.key, updateInformation.value);
},
setNodeParameters (state, updateInformation: IUpdateInformation) {
// Find the node that should be updated
const node = state.workflow.nodes.find(node => {
return node.name === updateInformation.name;
});
if (node === undefined || node === null) {
throw new Error(`Node with the name "${updateInformation.name}" could not be found to set parameter.`);
}
state.stateIsDirty = true;
Vue.set(node, 'parameters', updateInformation.value);
},
// Node-Index
addToNodeIndex (state, nodeName: string) {
state.nodeIndex.push(nodeName);
},
setNodeIndex (state, newData: { index: number, name: string | null}) {
state.nodeIndex[newData.index] = newData.name;
},
resetNodeIndex (state) {
Vue.set(state, 'nodeIndex', []);
},
// Node-View
setNodeViewMoveInProgress (state, value: boolean) {
state.nodeViewMoveInProgress = value;
},
setNodeViewOffsetPosition (state, data) {
state.nodeViewOffsetPosition = data.newOffset;
},
// Node-Types
setNodeTypes (state, nodeTypes: INodeTypeDescription[]) {
Vue.set(state, 'nodeTypes', nodeTypes);
},
// Active Execution
setExecutingNode (state, executingNode: string) {
state.executingNode = executingNode;
},
setExecutionWaitingForWebhook (state, newWaiting: boolean) {
state.executionWaitingForWebhook = newWaiting;
},
setActiveExecutionId (state, executionId: string | null) {
state.executionId = executionId;
},
// Push Connection
setPushConnectionActive (state, newActive: boolean) {
state.pushConnectionActive = newActive;
},
// Webhooks
setUrlBaseWebhook (state, urlBaseWebhook: string) {
Vue.set(state, 'urlBaseWebhook', urlBaseWebhook);
},
setEndpointWebhook (state, endpointWebhook: string) {
Vue.set(state, 'endpointWebhook', endpointWebhook);
},
setEndpointWebhookTest (state, endpointWebhookTest: string) {
Vue.set(state, 'endpointWebhookTest', endpointWebhookTest);
},
setSaveDataErrorExecution (state, newValue: string) {
Vue.set(state, 'saveDataErrorExecution', newValue);
},
setSaveDataSuccessExecution (state, newValue: string) {
Vue.set(state, 'saveDataSuccessExecution', newValue);
},
setSaveManualExecutions (state, saveManualExecutions: boolean) {
Vue.set(state, 'saveManualExecutions', saveManualExecutions);
},
setTimezone (state, timezone: string) {
Vue.set(state, 'timezone', timezone);
},
setExecutionTimeout (state, executionTimeout: number) {
Vue.set(state, 'executionTimeout', executionTimeout);
},
setMaxExecutionTimeout (state, maxExecutionTimeout: number) {
Vue.set(state, 'maxExecutionTimeout', maxExecutionTimeout);
},
setVersionCli(state, version: string) {
Vue.set(state, 'versionCli', version);
},
setInstanceId(state, instanceId: string) {
Vue.set(state, 'instanceId', instanceId);
},
setOauthCallbackUrls(state, urls: IDataObject) {
Vue.set(state, 'oauthCallbackUrls', urls);
},
setN8nMetadata(state, metadata: IDataObject) {
Vue.set(state, 'n8nMetadata', metadata);
},
setDefaultLocale(state, locale: string) {
Vue.set(state, 'defaultLocale', locale);
},
setActiveNode (state, nodeName: string) {
state.activeNode = nodeName;
},
setActiveCredentialType (state, activeCredentialType: string) {
state.activeCredentialType = activeCredentialType;
},
setLastSelectedNode (state, nodeName: string) {
state.lastSelectedNode = nodeName;
},
setLastSelectedNodeOutputIndex (state, outputIndex: number | null) {
state.lastSelectedNodeOutputIndex = outputIndex;
},
setWorkflowExecutionData (state, workflowResultData: IExecutionResponse | null) {
state.workflowExecutionData = workflowResultData;
},
addNodeExecutionData (state, pushData: IPushDataNodeExecuteAfter): void {
if (state.workflowExecutionData === null) {
throw new Error('The "workflowExecutionData" is not initialized!');
}
if (state.workflowExecutionData.data.resultData.runData[pushData.nodeName] === undefined) {
Vue.set(state.workflowExecutionData.data.resultData.runData, pushData.nodeName, []);
}
state.workflowExecutionData.data.resultData.runData[pushData.nodeName].push(pushData.data);
},
clearNodeExecutionData (state, nodeName: string): void {
if (state.workflowExecutionData === null) {
return;
}
Vue.delete(state.workflowExecutionData.data.resultData.runData, nodeName);
},
setWorkflowSettings (state, workflowSettings: IWorkflowSettings) {
Vue.set(state.workflow, 'settings', workflowSettings);
},
setWorkflowTagIds (state, tags: string[]) {
Vue.set(state.workflow, 'tags', tags);
},
removeWorkflowTagId (state, tagId: string) {
const tags = state.workflow.tags as string[];
const updated = tags.filter((id: string) => id !== tagId);
Vue.set(state.workflow, 'tags', updated);
},
// Workflow
setWorkflow (state, workflow: IWorkflowDb) {
Vue.set(state, 'workflow', workflow);
if (!state.workflow.hasOwnProperty('active')) {
Vue.set(state.workflow, 'active', false);
}
if (!state.workflow.hasOwnProperty('connections')) {
Vue.set(state.workflow, 'connections', {});
}
if (!state.workflow.hasOwnProperty('createdAt')) {
Vue.set(state.workflow, 'createdAt', -1);
}
if (!state.workflow.hasOwnProperty('updatedAt')) {
Vue.set(state.workflow, 'updatedAt', -1);
}
if (!state.workflow.hasOwnProperty('id')) {
Vue.set(state.workflow, 'id', PLACEHOLDER_EMPTY_WORKFLOW_ID);
}
if (!state.workflow.hasOwnProperty('nodes')) {
Vue.set(state.workflow, 'nodes', []);
}
if (!state.workflow.hasOwnProperty('settings')) {
Vue.set(state.workflow, 'settings', {});
}
},
updateNodeTypes (state, nodeTypes: INodeTypeDescription[]) {
const oldNodesToKeep = state.nodeTypes.filter(node => !nodeTypes.find(n => n.name === node.name && n.version === node.version));
const newNodesState = [...oldNodesToKeep, ...nodeTypes];
Vue.set(state, 'nodeTypes', newNodesState);
state.nodeTypes = newNodesState;
},
addSidebarMenuItems (state, menuItems: IMenuItem[]) {
const updated = state.sidebarMenuItems.concat(menuItems);
Vue.set(state, 'sidebarMenuItems', updated);
},
},
getters: {
activeCredentialType: (state): string | null => {
return state.activeCredentialType;
},
isActionActive: (state) => (action: string): boolean => {
return state.activeActions.includes(action);
},
isNewWorkflow: (state) => {
return state.workflow.id === PLACEHOLDER_EMPTY_WORKFLOW_ID;
},
currentWorkflowHasWebhookNode: (state: IRootState): boolean => {
return !!state.workflow.nodes.find((node: INodeUi) => !!node.webhookId);
},
getActiveExecutions: (state): IExecutionsCurrentSummaryExtended[] => {
return state.activeExecutions;
},
getBaseUrl: (state): string => {
return state.baseUrl;
},
getRestUrl: (state): string => {
let endpoint = 'rest';
if (process.env.VUE_APP_ENDPOINT_REST) {
endpoint = process.env.VUE_APP_ENDPOINT_REST;
}
return `${state.baseUrl}${endpoint}`;
},
getRestApiContext(state): IRestApiContext {
let endpoint = 'rest';
if (process.env.VUE_APP_ENDPOINT_REST) {
endpoint = process.env.VUE_APP_ENDPOINT_REST;
}
return {
baseUrl: `${state.baseUrl}${endpoint}`,
sessionId: state.sessionId,
};
},
getWebhookBaseUrl: (state): string => {
return state.urlBaseWebhook;
},
getWebhookUrl: (state): string => {
return `${state.urlBaseWebhook}${state.endpointWebhook}`;
},
getWebhookTestUrl: (state): string => {
return `${state.urlBaseWebhook}${state.endpointWebhookTest}`;
},
getStateIsDirty: (state) : boolean => {
return state.stateIsDirty;
},
instanceId: (state): string => {
return state.instanceId;
},
saveDataErrorExecution: (state): string => {
return state.saveDataErrorExecution;
},
saveDataSuccessExecution: (state): string => {
return state.saveDataSuccessExecution;
},
saveManualExecutions: (state): boolean => {
return state.saveManualExecutions;
},
timezone: (state): string => {
return state.timezone;
},
executionTimeout: (state): number => {
return state.executionTimeout;
},
maxExecutionTimeout: (state): number => {
return state.maxExecutionTimeout;
},
versionCli: (state): string => {
return state.versionCli;
},
oauthCallbackUrls: (state): object => {
return state.oauthCallbackUrls;
},
n8nMetadata: (state): object => {
return state.n8nMetadata;
},
defaultLocale: (state): string => {
return state.defaultLocale;
},
// Push Connection
pushConnectionActive: (state): boolean => {
return state.pushConnectionActive;
},
sessionId: (state): string => {
return state.sessionId;
},
// Active Workflows
getActiveWorkflows: (state): string[] => {
return state.activeWorkflows;
},
workflowTriggerNodes: (state, getters) => {
return state.workflow.nodes.filter(node => {
const nodeType = getters.nodeType(node.type, node.typeVersion);
return nodeType && nodeType.group.includes('trigger');
});
},
// Node-Index
getNodeIndex: (state) => (nodeName: string): number => {
return state.nodeIndex.indexOf(nodeName);
},
getNodeNameByIndex: (state) => (index: number): string | null => {
return state.nodeIndex[index];
},
getNodeViewOffsetPosition: (state): XYPosition => {
return state.nodeViewOffsetPosition;
},
isNodeViewMoveInProgress: (state): boolean => {
return state.nodeViewMoveInProgress;
},
// Selected Nodes
getSelectedNodes: (state): INodeUi[] => {
return state.selectedNodes;
},
isNodeSelected: (state) => (nodeName: string): boolean => {
let index;
for (index in state.selectedNodes) {
if (state.selectedNodes[index].name === nodeName) {
return true;
}
}
return false;
},
isActive: (state): boolean => {
return state.workflow.active;
},
allConnections: (state): IConnections => {
return state.workflow.connections;
},
outgoingConnectionsByNodeName: (state) => (nodeName: string): INodeConnections => {
if (state.workflow.connections.hasOwnProperty(nodeName)) {
return state.workflow.connections[nodeName];
}
return {};
},
allNodes: (state): INodeUi[] => {
return state.workflow.nodes;
},
nodesByName: (state: IRootState): {[name: string]: INodeUi} => {
return state.workflow.nodes.reduce((accu: {[name: string]: INodeUi}, node) => {
accu[node.name] = node;
return accu;
}, {});
},
getNodeByName: (state, getters) => (nodeName: string): INodeUi | null => {
return getters.nodesByName[nodeName] || null;
},
nodesIssuesExist: (state): boolean => {
for (const node of state.workflow.nodes) {
if (node.issues === undefined || Object.keys(node.issues).length === 0) {
continue;
}
return true;
}
return false;
},
allNodeTypes: (state): INodeTypeDescription[] => {
return state.nodeTypes;
},
/**
* Getter for node default names ending with a number: `'S3'`, `'Magento 2'`, etc.
*/
nativelyNumberSuffixedDefaults: (_, getters): string[] => {
const { allNodeTypes } = getters as {
allNodeTypes: Array<INodeTypeDescription & { defaults: { name: string } }>;
};
return allNodeTypes.reduce<string[]>((acc, cur) => {
if (/\d$/.test(cur.defaults.name)) acc.push(cur.defaults.name);
return acc;
}, []);
},
nodeType: (state, getters) => (nodeType: string, typeVersion?: number): INodeTypeDescription | null => {
const foundType = state.nodeTypes.find(typeData => {
return typeData.name === nodeType && typeData.version === (typeVersion || typeData.defaultVersion || DEFAULT_NODETYPE_VERSION);
});
if (foundType === undefined) {
return null;
}
return foundType;
},
activeNode: (state, getters): INodeUi | null => {
return getters.getNodeByName(state.activeNode);
},
lastSelectedNode: (state, getters): INodeUi | null => {
return getters.getNodeByName(state.lastSelectedNode);
},
lastSelectedNodeOutputIndex: (state, getters): number | null => {
return state.lastSelectedNodeOutputIndex;
},
// Active Execution
executingNode: (state): string | null => {
return state.executingNode;
},
activeExecutionId: (state): string | null => {
return state.executionId;
},
executionWaitingForWebhook: (state): boolean => {
return state.executionWaitingForWebhook;
},
workflowName: (state): string => {
return state.workflow.name;
},
workflowId: (state): string => {
return state.workflow.id;
},
workflowSettings: (state): IWorkflowSettings => {
if (state.workflow.settings === undefined) {
return {};
}
return state.workflow.settings;
},
workflowTags: (state): string[] => {
return state.workflow.tags as string[];
},
// Workflow Result Data
getWorkflowExecution: (state): IExecutionResponse | null => {
return state.workflowExecutionData;
},
getWorkflowRunData: (state): IRunData | null => {
if (!state.workflowExecutionData || !state.workflowExecutionData.data || !state.workflowExecutionData.data.resultData) {
return null;
}
return state.workflowExecutionData.data.resultData.runData;
},
getWorkflowResultDataByNodeName: (state, getters) => (nodeName: string): ITaskData[] | null => {
const workflowRunData = getters.getWorkflowRunData;
if (workflowRunData === null) {
return null;
}
if (!workflowRunData.hasOwnProperty(nodeName)) {
return null;
}
return workflowRunData[nodeName];
},
sidebarMenuItems: (state): IMenuItem[] => {
return state.sidebarMenuItems;
},
},
});