refactor(editor): Apply Prettier (no-changelog) (#4920)

*  Adjust `format` script

* 🔥 Remove exemption for `editor-ui`

* 🎨 Prettify

* 👕 Fix lint
This commit is contained in:
Iván Ovejero
2022-12-14 10:04:10 +01:00
committed by GitHub
parent bcde07e032
commit 5ca2148c7e
284 changed files with 19247 additions and 15540 deletions

View File

@@ -7,15 +7,17 @@ import { useWorkflowsStore } from '@/stores/workflows';
import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useUIStore } from '@/stores/ui';
import { INodeUi, XYPosition } from '@/Interface';
import {
scaleBigger,
scaleReset,
scaleSmaller,
} from '@/utils';
import { scaleBigger, scaleReset, scaleSmaller } from '@/utils';
import { START_NODE_TYPE } from '@/constants';
import '@/plugins/N8nCustomConnectorType';
import '@/plugins/PlusEndpointType';
import { DEFAULT_PLACEHOLDER_TRIGGER_BUTTON, getMidCanvasPosition, getNewNodePosition, getZoomToFit, PLACEHOLDER_TRIGGER_NODE_SIZE } from '@/utils/nodeViewUtils';
import {
DEFAULT_PLACEHOLDER_TRIGGER_BUTTON,
getMidCanvasPosition,
getNewNodePosition,
getZoomToFit,
PLACEHOLDER_TRIGGER_NODE_SIZE,
} from '@/utils/nodeViewUtils';
export const useCanvasStore = defineStore('canvas', () => {
const workflowStore = useWorkflowsStore();
@@ -24,10 +26,10 @@ export const useCanvasStore = defineStore('canvas', () => {
const jsPlumbInstance = jsPlumb.getInstance();
const nodes = computed<INodeUi[]>(() => workflowStore.allNodes);
const triggerNodes = computed<INodeUi[]>(
() => nodes.value.filter(
node => node.type === START_NODE_TYPE || nodeTypesStore.isTriggerNode(node.type),
),
const triggerNodes = computed<INodeUi[]>(() =>
nodes.value.filter(
(node) => node.type === START_NODE_TYPE || nodeTypesStore.isTriggerNode(node.type),
),
);
const isDemo = ref<boolean>(false);
const nodeViewScale = ref<number>(1);
@@ -62,7 +64,7 @@ export const useCanvasStore = defineStore('canvas', () => {
};
const resetZoom = () => {
const {scale, offset} = scaleReset({
const { scale, offset } = scaleReset({
scale: nodeViewScale.value,
offset: uiStore.nodeViewOffsetPosition,
});
@@ -70,7 +72,7 @@ export const useCanvasStore = defineStore('canvas', () => {
};
const zoomIn = () => {
const {scale, offset} = scaleBigger({
const { scale, offset } = scaleBigger({
scale: nodeViewScale.value,
offset: uiStore.nodeViewOffsetPosition,
});
@@ -78,7 +80,7 @@ export const useCanvasStore = defineStore('canvas', () => {
};
const zoomOut = () => {
const {scale, offset} = scaleSmaller({
const { scale, offset } = scaleSmaller({
scale: nodeViewScale.value,
offset: uiStore.nodeViewOffsetPosition,
});
@@ -87,18 +89,21 @@ export const useCanvasStore = defineStore('canvas', () => {
const zoomToFit = () => {
const nodes = getNodesWithPlaceholderNode();
if (!nodes.length) { // some unknown workflow executions
if (!nodes.length) {
// some unknown workflow executions
return;
}
const {zoomLevel, offset} = getZoomToFit(nodes, !isDemo.value);
const { zoomLevel, offset } = getZoomToFit(nodes, !isDemo.value);
setZoomLevel(zoomLevel, offset);
};
const wheelMoveWorkflow = (e: WheelEvent) => {
const normalized = normalizeWheel(e);
const offsetPosition = uiStore.nodeViewOffsetPosition;
const nodeViewOffsetPositionX = offsetPosition[0] - (e.shiftKey ? normalized.pixelY : normalized.pixelX);
const nodeViewOffsetPositionY = offsetPosition[1] - (e.shiftKey ? normalized.pixelX : normalized.pixelY);
const nodeViewOffsetPositionX =
offsetPosition[0] - (e.shiftKey ? normalized.pixelY : normalized.pixelX);
const nodeViewOffsetPositionY =
offsetPosition[1] - (e.shiftKey ? normalized.pixelX : normalized.pixelY);
uiStore.nodeViewOffsetPosition = [nodeViewOffsetPositionX, nodeViewOffsetPositionY];
};

View File

@@ -1,11 +1,16 @@
import { getInstalledCommunityNodes, installNewPackage, uninstallPackage, updatePackage } from "@/api/communityNodes";
import { getAvailableCommunityPackageCount } from "@/api/settings";
import { defineStore } from "pinia";
import { useRootStore } from "./n8nRootStore";
import {
getInstalledCommunityNodes,
installNewPackage,
uninstallPackage,
updatePackage,
} from '@/api/communityNodes';
import { getAvailableCommunityPackageCount } from '@/api/settings';
import { defineStore } from 'pinia';
import { useRootStore } from './n8nRootStore';
import { PublicInstalledPackage } from 'n8n-workflow';
import Vue from "vue";
import { CommunityNodesState, CommunityPackageMap } from "@/Interface";
import { STORES } from "@/constants";
import Vue from 'vue';
import { CommunityNodesState, CommunityPackageMap } from '@/Interface';
import { STORES } from '@/constants';
const LOADER_DELAY = 300;
@@ -16,8 +21,10 @@ export const useCommunityNodesStore = defineStore(STORES.COMMUNITY_NODES, {
installedPackages: {},
}),
getters: {
getInstalledPackages() : PublicInstalledPackage[] {
return Object.values(this.installedPackages).sort((a, b) => a.packageName.localeCompare(b.packageName));
getInstalledPackages(): PublicInstalledPackage[] {
return Object.values(this.installedPackages).sort((a, b) =>
a.packageName.localeCompare(b.packageName),
);
},
getInstalledPackageByName() {
return (name: string): PublicInstalledPackage => this.installedPackages[name];
@@ -33,7 +40,7 @@ export const useCommunityNodesStore = defineStore(STORES.COMMUNITY_NODES, {
const rootStore = useRootStore();
const installedPackages = await getInstalledCommunityNodes(rootStore.getRestApiContext);
this.setInstalledPackages(installedPackages);
const timeout = installedPackages.length > 0 ? 0: LOADER_DELAY;
const timeout = installedPackages.length > 0 ? 0 : LOADER_DELAY;
setTimeout(() => {
return;
}, timeout);
@@ -44,7 +51,7 @@ export const useCommunityNodesStore = defineStore(STORES.COMMUNITY_NODES, {
await installNewPackage(rootStore.getRestApiContext, packageName);
await this.fetchInstalledPackages();
} catch (error) {
throw (error);
throw error;
}
},
async uninstallPackage(packageName: string): Promise<void> {
@@ -53,24 +60,30 @@ export const useCommunityNodesStore = defineStore(STORES.COMMUNITY_NODES, {
await uninstallPackage(rootStore.getRestApiContext, packageName);
this.removePackageByName(packageName);
} catch (error) {
throw (error);
throw error;
}
},
async updatePackage(packageName: string): Promise<void> {
try {
const rootStore = useRootStore();
const packageToUpdate: PublicInstalledPackage = this.getInstalledPackageByName(packageName);
const updatedPackage: PublicInstalledPackage = await updatePackage(rootStore.getRestApiContext, packageToUpdate.packageName);
const updatedPackage: PublicInstalledPackage = await updatePackage(
rootStore.getRestApiContext,
packageToUpdate.packageName,
);
this.updatePackageObject(updatedPackage);
} catch (error) {
throw (error);
throw error;
}
},
setInstalledPackages(packages: PublicInstalledPackage[]) {
this.installedPackages = packages.reduce((packageMap: CommunityPackageMap, pack: PublicInstalledPackage) => {
packageMap[pack.packageName] = pack;
return packageMap;
}, {});
this.installedPackages = packages.reduce(
(packageMap: CommunityPackageMap, pack: PublicInstalledPackage) => {
packageMap[pack.packageName] = pack;
return packageMap;
},
{},
);
},
removePackageByName(name: string): void {
Vue.delete(this.installedPackages, name);

View File

@@ -1,16 +1,40 @@
import { createNewCredential, deleteCredential, getAllCredentials, getCredentialData, getCredentialsNewName, getCredentialTypes, oAuth1CredentialAuthorize, oAuth2CredentialAuthorize, testCredential, updateCredential } from "@/api/credentials";
import { setCredentialSharedWith } from "@/api/credentials.ee";
import { getAppNameFromCredType } from "@/utils";
import { EnterpriseEditionFeature, STORES } from "@/constants";
import { ICredentialMap, ICredentialsDecryptedResponse, ICredentialsResponse, ICredentialsState, ICredentialTypeMap } from "@/Interface";
import { i18n } from "@/plugins/i18n";
import { ICredentialsDecrypted, ICredentialType, INodeCredentialTestResult, INodeProperties, INodeTypeDescription, IUser } from "n8n-workflow";
import { defineStore } from "pinia";
import Vue from "vue";
import { useRootStore } from "./n8nRootStore";
import { useNodeTypesStore } from "./nodeTypes";
import { useSettingsStore } from "./settings";
import { useUsersStore } from "./users";
import {
createNewCredential,
deleteCredential,
getAllCredentials,
getCredentialData,
getCredentialsNewName,
getCredentialTypes,
oAuth1CredentialAuthorize,
oAuth2CredentialAuthorize,
testCredential,
updateCredential,
} from '@/api/credentials';
import { setCredentialSharedWith } from '@/api/credentials.ee';
import { getAppNameFromCredType } from '@/utils';
import { EnterpriseEditionFeature, STORES } from '@/constants';
import {
ICredentialMap,
ICredentialsDecryptedResponse,
ICredentialsResponse,
ICredentialsState,
ICredentialTypeMap,
} from '@/Interface';
import { i18n } from '@/plugins/i18n';
import {
ICredentialsDecrypted,
ICredentialType,
INodeCredentialTestResult,
INodeProperties,
INodeTypeDescription,
IUser,
} from 'n8n-workflow';
import { defineStore } from 'pinia';
import Vue from 'vue';
import { useRootStore } from './n8nRootStore';
import { useNodeTypesStore } from './nodeTypes';
import { useSettingsStore } from './settings';
import { useUsersStore } from './users';
const DEFAULT_CREDENTIAL_NAME = 'Unnamed credential';
const DEFAULT_CREDENTIAL_POSTFIX = 'account';
@@ -26,22 +50,27 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
return this.credentialTypes;
},
allCredentialTypes(): ICredentialType[] {
return Object.values(this.credentialTypes)
.sort((a, b) => a.displayName.localeCompare(b.displayName));
return Object.values(this.credentialTypes).sort((a, b) =>
a.displayName.localeCompare(b.displayName),
);
},
allCredentials(): ICredentialsResponse[] {
return Object.values(this.credentials)
.sort((a, b) => a.name.localeCompare(b.name));
return Object.values(this.credentials).sort((a, b) => a.name.localeCompare(b.name));
},
allCredentialsByType(): {[type: string]: ICredentialsResponse[]} {
allCredentialsByType(): { [type: string]: ICredentialsResponse[] } {
const credentials = this.allCredentials;
const types = this.allCredentialTypes;
return types.reduce((accu: {[type: string]: ICredentialsResponse[]}, type: ICredentialType) => {
accu[type.name] = credentials.filter((cred: ICredentialsResponse) => cred.type === type.name);
return types.reduce(
(accu: { [type: string]: ICredentialsResponse[] }, type: ICredentialType) => {
accu[type.name] = credentials.filter(
(cred: ICredentialsResponse) => cred.type === type.name,
);
return accu;
}, {});
return accu;
},
{},
);
},
getCredentialTypeByName() {
return (type: string): ICredentialType => this.credentialTypes[type];
@@ -57,7 +86,7 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
},
getCredentialsByType() {
return (credentialType: string): ICredentialsResponse[] => {
return (this.allCredentialsByType[credentialType] || []);
return this.allCredentialsByType[credentialType] || [];
};
},
getNodesWithAccess() {
@@ -71,7 +100,7 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
}
for (const credentialTypeDescription of nodeType.credentials) {
if (credentialTypeDescription.name === credentialTypeName ) {
if (credentialTypeDescription.name === credentialTypeName) {
return true;
}
}
@@ -120,11 +149,14 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
},
actions: {
setCredentialTypes(credentialTypes: ICredentialType[]): void {
this.credentialTypes = credentialTypes.reduce((accu: ICredentialTypeMap, cred: ICredentialType) => {
accu[cred.name] = cred;
this.credentialTypes = credentialTypes.reduce(
(accu: ICredentialTypeMap, cred: ICredentialType) => {
accu[cred.name] = cred;
return accu;
}, {});
return accu;
},
{},
);
},
setCredentials(credentials: ICredentialsResponse[]): void {
this.credentials = credentials.reduce((accu: ICredentialMap, cred: ICredentialsResponse) => {
@@ -143,7 +175,10 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
},
upsertCredential(credential: ICredentialsResponse): void {
if (credential.id) {
Vue.set(this.credentials, credential.id, { ...this.credentials[credential.id], ...credential });
Vue.set(this.credentials, credential.id, {
...this.credentials[credential.id],
...credential,
});
}
},
enableOAuthCredential(credential: ICredentialsResponse): void {
@@ -163,7 +198,11 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
this.setCredentials(credentials);
return credentials;
},
async getCredentialData({ id }: {id: string}): Promise<ICredentialsResponse | ICredentialsDecryptedResponse | undefined> {
async getCredentialData({
id,
}: {
id: string;
}): Promise<ICredentialsResponse | ICredentialsDecryptedResponse | undefined> {
const rootStore = useRootStore();
return await getCredentialData(rootStore.getRestApiContext, id);
},
@@ -194,7 +233,10 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
}
return credential;
},
async updateCredential(params: {data: ICredentialsDecrypted, id: string}): Promise<ICredentialsResponse> {
async updateCredential(params: {
data: ICredentialsDecrypted;
id: string;
}): Promise<ICredentialsResponse> {
const { id, data } = params;
const rootStore = useRootStore();
const settingsStore = useSettingsStore();
@@ -223,7 +265,7 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
return credential;
},
async deleteCredential({ id }: {id: string}) {
async deleteCredential({ id }: { id: string }) {
const rootStore = useRootStore();
const deleted = await deleteCredential(rootStore.getRestApiContext, id);
if (deleted) {
@@ -249,7 +291,10 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
if (!TYPES_WITH_DEFAULT_NAME.includes(credentialTypeName)) {
const { displayName } = this.getCredentialTypeByName(credentialTypeName);
newName = getAppNameFromCredType(displayName);
newName = newName.length > 0 ? `${newName} ${DEFAULT_CREDENTIAL_POSTFIX}` : DEFAULT_CREDENTIAL_NAME;
newName =
newName.length > 0
? `${newName} ${DEFAULT_CREDENTIAL_POSTFIX}`
: DEFAULT_CREDENTIAL_NAME;
}
const rootStore = useRootStore();
const res = await getCredentialsNewName(rootStore.getRestApiContext, newName);
@@ -260,34 +305,31 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
},
// Enterprise edition actions
setCredentialOwnedBy(payload: { credentialId: string, ownedBy: Partial<IUser> }) {
setCredentialOwnedBy(payload: { credentialId: string; ownedBy: Partial<IUser> }) {
Vue.set(this.credentials[payload.credentialId], 'ownedBy', payload.ownedBy);
},
async setCredentialSharedWith(payload: { sharedWith: IUser[]; credentialId: string; }) {
if(useSettingsStore().isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
await setCredentialSharedWith(
useRootStore().getRestApiContext,
payload.credentialId,
{
shareWithIds: payload.sharedWith.map((sharee) => sharee.id),
},
);
async setCredentialSharedWith(payload: { sharedWith: IUser[]; credentialId: string }) {
if (useSettingsStore().isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
await setCredentialSharedWith(useRootStore().getRestApiContext, payload.credentialId, {
shareWithIds: payload.sharedWith.map((sharee) => sharee.id),
});
Vue.set(this.credentials[payload.credentialId], 'sharedWith', payload.sharedWith);
}
},
addCredentialSharee(payload: { credentialId: string, sharee: Partial<IUser> }): void {
addCredentialSharee(payload: { credentialId: string; sharee: Partial<IUser> }): void {
Vue.set(
this.credentials[payload.credentialId],
'sharedWith',
(this.credentials[payload.credentialId].sharedWith || []).concat([payload.sharee]),
);
},
removeCredentialSharee(payload: { credentialId: string, sharee: Partial<IUser> }): void {
removeCredentialSharee(payload: { credentialId: string; sharee: Partial<IUser> }): void {
Vue.set(
this.credentials[payload.credentialId],
'sharedWith',
(this.credentials[payload.credentialId].sharedWith || [])
.filter((sharee) => sharee.id !== payload.sharee.id),
(this.credentials[payload.credentialId].sharedWith || []).filter(
(sharee) => sharee.id !== payload.sharee.id,
),
);
},
},

View File

@@ -1,8 +1,8 @@
import { AddConnectionCommand, COMMANDS, RemoveConnectionCommand } from './../models/history';
import { BulkCommand, Command, Undoable, MoveNodeCommand } from "@/models/history";
import { STORES } from "@/constants";
import { HistoryState } from "@/Interface";
import { defineStore } from "pinia";
import { BulkCommand, Command, Undoable, MoveNodeCommand } from '@/models/history';
import { STORES } from '@/constants';
import { HistoryState } from '@/Interface';
import { defineStore } from 'pinia';
const STACK_LIMIT = 100;
@@ -24,7 +24,8 @@ export const useHistoryStore = defineStore(STORES.HISTORY, {
pushCommandToUndo(undoable: Command, clearRedo = true): void {
if (!this.bulkInProgress) {
if (this.currentBulkAction) {
const alreadyIn = this.currentBulkAction.commands.find(c => c.isEqualTo(undoable)) !== undefined;
const alreadyIn =
this.currentBulkAction.commands.find((c) => c.isEqualTo(undoable)) !== undefined;
if (!alreadyIn) {
this.currentBulkAction.commands.push(undoable);
}

View File

@@ -8,7 +8,11 @@ import { useNodeTypesStore } from './nodeTypes';
export const useRootStore = defineStore(STORES.ROOT, {
state: (): RootState => ({
// @ts-ignore
baseUrl: import.meta.env.VUE_APP_URL_BASE_API ? import.meta.env.VUE_APP_URL_BASE_API : (window.BASE_PATH === '/%BASE_PATH%/' ? '/' : window.BASE_PATH),
baseUrl: import.meta.env.VUE_APP_URL_BASE_API
? import.meta.env.VUE_APP_URL_BASE_API
: window.BASE_PATH === '/%BASE_PATH%/'
? '/'
: window.BASE_PATH,
defaultLocale: 'en',
endpointWebhook: 'webhook',
endpointWebhookTest: 'webhook-test',
@@ -59,7 +63,7 @@ export const useRootStore = defineStore(STORES.ROOT, {
/**
* Getter for node default names ending with a number: `'S3'`, `'Magento 2'`, etc.
*/
nativelyNumberSuffixedDefaults: (): string[] => {
nativelyNumberSuffixedDefaults: (): string[] => {
return useNodeTypesStore().allNodeTypes.reduce<string[]>((acc, cur) => {
if (/\d$/.test(cur.defaults.name as string)) acc.push(cur.defaults.name as string);
return acc;

View File

@@ -1,17 +1,17 @@
import { STORES } from "@/constants";
import { INodeUi, IRunDataDisplayMode, NDVState, NodePanelType, XYPosition } from "@/Interface";
import { IRunData } from "n8n-workflow";
import { defineStore } from "pinia";
import Vue from "vue";
import { useWorkflowsStore } from "./workflows";
import { STORES } from '@/constants';
import { INodeUi, IRunDataDisplayMode, NDVState, NodePanelType, XYPosition } from '@/Interface';
import { IRunData } from 'n8n-workflow';
import { defineStore } from 'pinia';
import Vue from 'vue';
import { useWorkflowsStore } from './workflows';
export const useNDVStore = defineStore(STORES.NDV, {
export const useNDVStore = defineStore(STORES.NDV, {
state: (): NDVState => ({
activeNodeName: null,
mainPanelDimensions: {},
sessionId: '',
input: {
displayMode: window.posthog?.isFeatureEnabled?.('schema-view') ? 'schema': 'table',
displayMode: window.posthog?.isFeatureEnabled?.('schema-view') ? 'schema' : 'table',
nodeName: undefined,
run: undefined,
branch: undefined,
@@ -51,13 +51,19 @@ export const useNDVStore = defineStore(STORES.NDV, {
const executionData = workflowsStore.getWorkflowExecution;
const inputNodeName: string | undefined = this.input.nodeName;
const inputRunIndex: number = this.input.run ?? 0;
const inputBranchIndex: number = this.input.branch?? 0;
const inputBranchIndex: number = this.input.branch ?? 0;
if (!executionData || !inputNodeName || inputRunIndex === undefined || inputBranchIndex === undefined) {
if (
!executionData ||
!inputNodeName ||
inputRunIndex === undefined ||
inputBranchIndex === undefined
) {
return [];
}
return executionData.data?.resultData?.runData?.[inputNodeName]?.[inputRunIndex]?.data?.main?.[inputBranchIndex];
return executionData.data?.resultData?.runData?.[inputNodeName]?.[inputRunIndex]?.data
?.main?.[inputBranchIndex];
},
getPanelDisplayMode() {
return (panel: NodePanelType) => this[panel].displayMode;
@@ -86,7 +92,7 @@ export const useNDVStore = defineStore(STORES.NDV, {
getMainPanelDimensions() {
return (panelType: string) => {
const defaults = { relativeRight: 1, relativeLeft: 1, relativeWidth: 1 };
return {...defaults, ...this.mainPanelDimensions[panelType]};
return { ...defaults, ...this.mainPanelDimensions[panelType] };
};
},
draggableStickyPos(): XYPosition | null {
@@ -109,26 +115,28 @@ export const useNDVStore = defineStore(STORES.NDV, {
setInputNodeName(name: string | undefined): void {
Vue.set(this.input, 'nodeName', name);
},
setInputRunIndex(run?: string): void {
setInputRunIndex(run?: string): void {
Vue.set(this.input, 'run', run);
},
setMainPanelDimensions(params: { panelType:string, dimensions: { relativeLeft?: number, relativeRight?: number, relativeWidth?: number }}): void {
Vue.set(
this.mainPanelDimensions,
params.panelType,
{...this.mainPanelDimensions[params.panelType], ...params.dimensions },
);
setMainPanelDimensions(params: {
panelType: string;
dimensions: { relativeLeft?: number; relativeRight?: number; relativeWidth?: number };
}): void {
Vue.set(this.mainPanelDimensions, params.panelType, {
...this.mainPanelDimensions[params.panelType],
...params.dimensions,
});
},
setNDVSessionId(): void {
setNDVSessionId(): void {
Vue.set(this, 'sessionId', `ndv-${Math.random().toString(36).slice(-8)}`);
},
resetNDVSessionId(): void {
resetNDVSessionId(): void {
Vue.set(this, 'sessionId', '');
},
setPanelDisplayMode(params: {pane: NodePanelType, mode: IRunDataDisplayMode}): void {
setPanelDisplayMode(params: { pane: NodePanelType; mode: IRunDataDisplayMode }): void {
Vue.set(this[params.pane], 'displayMode', params.mode);
},
setOutputPanelEditModeEnabled(isEnabled: boolean): void {
setOutputPanelEditModeEnabled(isEnabled: boolean): void {
Vue.set(this.output.editMode, 'enabled', isEnabled);
},
setOutputPanelEditModeValue(payload: string): void {
@@ -137,7 +145,7 @@ export const useNDVStore = defineStore(STORES.NDV, {
setMappableNDVInputFocus(paramName: string): void {
Vue.set(this, 'focusedMappableInput', paramName);
},
draggableStartDragging({type, data}: {type: string, data: string}): void {
draggableStartDragging({ type, data }: { type: string; data: string }): void {
this.draggable = {
isDragging: true,
type,
@@ -161,8 +169,8 @@ export const useNDVStore = defineStore(STORES.NDV, {
setDraggableCanDrop(canDrop: boolean): void {
Vue.set(this.draggable, 'canDrop', canDrop);
},
setMappingTelemetry(telemetry: {[key: string]: string | number | boolean}): void {
this.mappingTelemetry = {...this.mappingTelemetry, ...telemetry};
setMappingTelemetry(telemetry: { [key: string]: string | number | boolean }): void {
this.mappingTelemetry = { ...this.mappingTelemetry, ...telemetry };
},
resetMappingTelemetry(): void {
this.mappingTelemetry = {};
@@ -170,10 +178,10 @@ export const useNDVStore = defineStore(STORES.NDV, {
setHoveringItem(item: null | NDVState['hoveringItem']): void {
Vue.set(this, 'hoveringItem', item);
},
setNDVBranchIndex(e: {pane: 'input' | 'output', branchIndex: number}): void {
setNDVBranchIndex(e: { pane: 'input' | 'output'; branchIndex: number }): void {
Vue.set(this[e.pane], 'branch', e.branchIndex);
},
setNDVPanelDataIsEmpty(payload: {panel: 'input' | 'output', isEmpty: boolean}): void {
setNDVPanelDataIsEmpty(payload: { panel: 'input' | 'output'; isEmpty: boolean }): void {
Vue.set(this[payload.panel].data, 'isEmpty', payload.isEmpty);
},
},

View File

@@ -1,7 +1,23 @@
import startCase from 'lodash.startCase';
import { defineStore } from "pinia";
import { INodePropertyCollection, INodePropertyOptions, IDataObject, INodeProperties, INodeTypeDescription, deepCopy, INodeParameters, INodeActionTypeDescription } from 'n8n-workflow';
import { STORES, MANUAL_TRIGGER_NODE_TYPE, CORE_NODES_CATEGORY, CALENDLY_TRIGGER_NODE_TYPE, TRIGGER_NODE_FILTER, WEBHOOK_NODE_TYPE } from "@/constants";
import { defineStore } from 'pinia';
import {
INodePropertyCollection,
INodePropertyOptions,
IDataObject,
INodeProperties,
INodeTypeDescription,
deepCopy,
INodeParameters,
INodeActionTypeDescription,
} from 'n8n-workflow';
import {
STORES,
MANUAL_TRIGGER_NODE_TYPE,
CORE_NODES_CATEGORY,
CALENDLY_TRIGGER_NODE_TYPE,
TRIGGER_NODE_FILTER,
WEBHOOK_NODE_TYPE,
} from '@/constants';
import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useWorkflowsStore } from './workflows';
import { CUSTOM_API_CALL_KEY, ALL_NODE_FILTER } from '@/constants';
@@ -12,28 +28,40 @@ import { Telemetry } from '@/plugins/telemetry';
const PLACEHOLDER_RECOMMENDED_ACTION_KEY = 'placeholder_recommended';
const customNodeActionsParsers: {[key: string]: (matchedProperty: INodeProperties, nodeTypeDescription: INodeTypeDescription) => INodeActionTypeDescription[] | undefined} = {
const customNodeActionsParsers: {
[key: string]: (
matchedProperty: INodeProperties,
nodeTypeDescription: INodeTypeDescription,
) => INodeActionTypeDescription[] | undefined;
} = {
['n8n-nodes-base.hubspotTrigger']: (matchedProperty, nodeTypeDescription) => {
const collection = matchedProperty?.options?.[0] as INodePropertyCollection;
return (collection?.values[0]?.options as INodePropertyOptions[])?.map((categoryItem): INodeActionTypeDescription => ({
...getNodeTypeBase(nodeTypeDescription, i18n.baseText('nodeCreator.actionsCategory.recommended')),
actionKey: categoryItem.value as string,
displayName: i18n.baseText('nodeCreator.actionsCategory.onEvent', {
interpolate: {event: startCase(categoryItem.name)},
return (collection?.values[0]?.options as INodePropertyOptions[])?.map(
(categoryItem): INodeActionTypeDescription => ({
...getNodeTypeBase(
nodeTypeDescription,
i18n.baseText('nodeCreator.actionsCategory.recommended'),
),
actionKey: categoryItem.value as string,
displayName: i18n.baseText('nodeCreator.actionsCategory.onEvent', {
interpolate: { event: startCase(categoryItem.name) },
}),
description: categoryItem.description || '',
displayOptions: matchedProperty.displayOptions,
values: { eventsUi: { eventValues: [{ name: categoryItem.value }] } },
}),
description: categoryItem.description || '',
displayOptions: matchedProperty.displayOptions,
values: { eventsUi: { eventValues: [{ name: categoryItem.value }] } },
}));
);
},
};
function filterSinglePlaceholderAction(actions: INodeActionTypeDescription[]) {
return actions.filter((action: INodeActionTypeDescription, _: number, arr: INodeActionTypeDescription[]) => {
const isPlaceholderTriggerAction = action.actionKey === PLACEHOLDER_RECOMMENDED_ACTION_KEY;
return !isPlaceholderTriggerAction || (isPlaceholderTriggerAction && arr.length > 1);
});
return actions.filter(
(action: INodeActionTypeDescription, _: number, arr: INodeActionTypeDescription[]) => {
const isPlaceholderTriggerAction = action.actionKey === PLACEHOLDER_RECOMMENDED_ACTION_KEY;
return !isPlaceholderTriggerAction || (isPlaceholderTriggerAction && arr.length > 1);
},
);
}
function getNodeTypeBase(nodeTypeDescription: INodeTypeDescription, category: string) {
@@ -56,11 +84,14 @@ function getNodeTypeBase(nodeTypeDescription: INodeTypeDescription, category: st
};
}
function operationsCategory(nodeTypeDescription: INodeTypeDescription): INodeActionTypeDescription[] {
function operationsCategory(
nodeTypeDescription: INodeTypeDescription,
): INodeActionTypeDescription[] {
if (!!nodeTypeDescription.properties.find((property) => property.name === 'resource')) return [];
const matchedProperty = nodeTypeDescription.properties
.find((property) =>property.name?.toLowerCase() === 'operation');
const matchedProperty = nodeTypeDescription.properties.find(
(property) => property.name?.toLowerCase() === 'operation',
);
if (!matchedProperty || !matchedProperty.options) return [];
@@ -69,7 +100,10 @@ function operationsCategory(nodeTypeDescription: INodeTypeDescription): INodeAct
);
const items = filteredOutItems.map((item: INodePropertyOptions) => ({
...getNodeTypeBase(nodeTypeDescription, i18n.baseText('nodeCreator.actionsCategory.operations')),
...getNodeTypeBase(
nodeTypeDescription,
i18n.baseText('nodeCreator.actionsCategory.operations'),
),
actionKey: item.value as string,
displayName: item.action ?? startCase(item.name),
description: item.description ?? '',
@@ -85,7 +119,9 @@ function operationsCategory(nodeTypeDescription: INodeTypeDescription): INodeAct
return items;
}
function recommendedCategory(nodeTypeDescription: INodeTypeDescription): INodeActionTypeDescription[] {
function recommendedCategory(
nodeTypeDescription: INodeTypeDescription,
): INodeActionTypeDescription[] {
const matchingKeys = ['event', 'events', 'trigger on'];
const isTrigger = nodeTypeDescription.displayName?.toLowerCase().includes('trigger');
const matchedProperty = nodeTypeDescription.properties.find((property) =>
@@ -97,29 +133,40 @@ function recommendedCategory(nodeTypeDescription: INodeTypeDescription): INodeAc
// Inject placeholder action if no events are available
// so user is able to add node to the canvas from the actions panel
if (!matchedProperty || !matchedProperty.options) {
return [{
...getNodeTypeBase(nodeTypeDescription, i18n.baseText('nodeCreator.actionsCategory.recommended')),
actionKey: PLACEHOLDER_RECOMMENDED_ACTION_KEY,
displayName: i18n.baseText('nodeCreator.actionsCategory.onNewEvent', {
interpolate: {event: nodeTypeDescription.displayName.replace('Trigger', '').trimEnd()},
}),
description: '',
}];
return [
{
...getNodeTypeBase(
nodeTypeDescription,
i18n.baseText('nodeCreator.actionsCategory.recommended'),
),
actionKey: PLACEHOLDER_RECOMMENDED_ACTION_KEY,
displayName: i18n.baseText('nodeCreator.actionsCategory.onNewEvent', {
interpolate: { event: nodeTypeDescription.displayName.replace('Trigger', '').trimEnd() },
}),
description: '',
},
];
}
const filteredOutItems = (matchedProperty.options as INodePropertyOptions[]).filter(
(categoryItem: INodePropertyOptions) => !['*', '', ' '].includes(categoryItem.name),
);
const customParsedItem = customNodeActionsParsers[nodeTypeDescription.name]?.(matchedProperty, nodeTypeDescription);
const customParsedItem = customNodeActionsParsers[nodeTypeDescription.name]?.(
matchedProperty,
nodeTypeDescription,
);
const items =
customParsedItem ??
filteredOutItems.map((categoryItem: INodePropertyOptions) => ({
...getNodeTypeBase(nodeTypeDescription, i18n.baseText('nodeCreator.actionsCategory.recommended')),
...getNodeTypeBase(
nodeTypeDescription,
i18n.baseText('nodeCreator.actionsCategory.recommended'),
),
actionKey: categoryItem.value as string,
displayName: i18n.baseText('nodeCreator.actionsCategory.onEvent', {
interpolate: {event: startCase(categoryItem.name)},
interpolate: { event: startCase(categoryItem.name) },
}),
description: categoryItem.description || '',
displayOptions: matchedProperty.displayOptions,
@@ -132,12 +179,16 @@ function recommendedCategory(nodeTypeDescription: INodeTypeDescription): INodeAc
return items;
}
function resourceCategories(nodeTypeDescription: INodeTypeDescription): INodeActionTypeDescription[] {
function resourceCategories(
nodeTypeDescription: INodeTypeDescription,
): INodeActionTypeDescription[] {
const transformedNodes: INodeActionTypeDescription[] = [];
const matchedProperties = nodeTypeDescription.properties.filter((property) =>property.displayName?.toLowerCase() === 'resource');
const matchedProperties = nodeTypeDescription.properties.filter(
(property) => property.displayName?.toLowerCase() === 'resource',
);
matchedProperties.forEach((property) => {
(property.options as INodePropertyOptions[] || [])
((property.options as INodePropertyOptions[]) || [])
.filter((option) => option.value !== CUSTOM_API_CALL_KEY)
.forEach((resourceOption, i, options) => {
const isSingleResource = options.length === 1;
@@ -152,11 +203,10 @@ function resourceCategories(nodeTypeDescription: INodeTypeDescription): INodeAct
if (!operations?.options) return;
const items = (operations.options as INodePropertyOptions[] || []).map(
const items = ((operations.options as INodePropertyOptions[]) || []).map(
(operationOption) => {
const displayName =
operationOption.action ??
`${resourceOption.name} ${startCase(operationOption.name)}`;
operationOption.action ?? `${resourceOption.name} ${startCase(operationOption.name)}`;
// We need to manually populate displayOptions as they are not present in the node description
// if the resource has only one option
@@ -208,20 +258,22 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, {
setFilter(search: string) {
this.itemsFilter = search;
},
setAddedNodeActionParameters (action: IUpdateInformation, telemetry?: Telemetry, track = true) {
setAddedNodeActionParameters(action: IUpdateInformation, telemetry?: Telemetry, track = true) {
const { $onAction: onWorkflowStoreAction } = useWorkflowsStore();
const storeWatcher = onWorkflowStoreAction(({ name, after, store: { setLastNodeParameters }, args }) => {
if (name !== 'addNode' || args[0].type !== action.key) return;
after(() => {
setLastNodeParameters(action);
if(track) this.trackActionSelected(action, telemetry);
storeWatcher();
});
});
const storeWatcher = onWorkflowStoreAction(
({ name, after, store: { setLastNodeParameters }, args }) => {
if (name !== 'addNode' || args[0].type !== action.key) return;
after(() => {
setLastNodeParameters(action);
if (track) this.trackActionSelected(action, telemetry);
storeWatcher();
});
},
);
return storeWatcher;
},
trackActionSelected (action: IUpdateInformation, telemetry?: Telemetry) {
trackActionSelected(action: IUpdateInformation, telemetry?: Telemetry) {
const { $externalHooks } = new externalHooks();
const payload = {
@@ -240,7 +292,7 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, {
const isCoreNode = node.codex?.categories?.includes(CORE_NODES_CATEGORY);
// Core nodes shouldn't support actions
node.actions = [];
if(isCoreNode) return node;
if (isCoreNode) return node;
node.actions.push(
...recommendedCategory(node),
@@ -253,54 +305,70 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, {
return nodesWithActions;
},
mergedAppNodes(): INodeTypeDescription[] {
const mergedNodes = this.visibleNodesWithActions.reduce((acc: Record<string, INodeTypeDescription>, node: INodeTypeDescription) => {
const mergedNodes = this.visibleNodesWithActions.reduce(
(acc: Record<string, INodeTypeDescription>, node: INodeTypeDescription) => {
const clonedNode = deepCopy(node);
const isCoreNode = node.codex?.categories?.includes(CORE_NODES_CATEGORY);
const actions = node.actions || [];
// Do not merge core nodes
const normalizedName = isCoreNode
? node.name
: node.name.toLowerCase().replace('trigger', '');
const existingNode = acc[normalizedName];
const clonedNode = deepCopy(node);
const isCoreNode = node.codex?.categories?.includes(CORE_NODES_CATEGORY);
const actions = node.actions || [];
// Do not merge core nodes
const normalizedName = isCoreNode ? node.name : node.name.toLowerCase().replace('trigger', '');
const existingNode = acc[normalizedName];
if (existingNode) existingNode.actions?.push(...actions);
else acc[normalizedName] = clonedNode;
if(existingNode) existingNode.actions?.push(...actions);
else acc[normalizedName] = clonedNode;
if (!isCoreNode) {
acc[normalizedName].displayName = node.displayName.replace('Trigger', '');
}
if(!isCoreNode) acc[normalizedName].displayName = node.displayName.replace('Trigger', '');
acc[normalizedName].actions = filterSinglePlaceholderAction(acc[normalizedName].actions || []);
return acc;
}, {});
acc[normalizedName].actions = filterSinglePlaceholderAction(
acc[normalizedName].actions || [],
);
return acc;
},
{},
);
return Object.values(mergedNodes);
},
getNodeTypesWithManualTrigger: () => (nodeType?: string): string[] => {
if(!nodeType) return [];
getNodeTypesWithManualTrigger:
() =>
(nodeType?: string): string[] => {
if (!nodeType) return [];
const { workflowTriggerNodes } = useWorkflowsStore();
const isTrigger = nodeType.toLocaleLowerCase().includes('trigger') || nodeType === WEBHOOK_NODE_TYPE;
const workflowContainsTrigger = workflowTriggerNodes.length > 0;
const isTriggerPanel = useNodeCreatorStore().selectedType === TRIGGER_NODE_FILTER;
const { workflowTriggerNodes } = useWorkflowsStore();
const isTrigger =
nodeType.toLocaleLowerCase().includes('trigger') || nodeType === WEBHOOK_NODE_TYPE;
const workflowContainsTrigger = workflowTriggerNodes.length > 0;
const isTriggerPanel = useNodeCreatorStore().selectedType === TRIGGER_NODE_FILTER;
const nodeTypes = !isTrigger && !workflowContainsTrigger && isTriggerPanel
? [MANUAL_TRIGGER_NODE_TYPE, nodeType]
: [nodeType];
const nodeTypes =
!isTrigger && !workflowContainsTrigger && isTriggerPanel
? [MANUAL_TRIGGER_NODE_TYPE, nodeType]
: [nodeType];
return nodeTypes;
},
return nodeTypes;
},
getActionData: () => (actionItem: INodeActionTypeDescription): IUpdateInformation => {
const displayOptions = actionItem.displayOptions ;
getActionData:
() =>
(actionItem: INodeActionTypeDescription): IUpdateInformation => {
const displayOptions = actionItem.displayOptions;
const displayConditions = Object.keys(displayOptions?.show || {})
.reduce((acc: IDataObject, showCondition: string) => {
acc[showCondition] = displayOptions?.show?.[showCondition]?.[0];
return acc;
}, {});
const displayConditions = Object.keys(displayOptions?.show || {}).reduce(
(acc: IDataObject, showCondition: string) => {
acc[showCondition] = displayOptions?.show?.[showCondition]?.[0];
return acc;
},
{},
);
return {
name: actionItem.displayName,
key: actionItem.name as string,
value: { ...actionItem.values , ...displayConditions} as INodeParameters,
};
},
return {
name: actionItem.displayName,
key: actionItem.name as string,
value: { ...actionItem.values, ...displayConditions } as INodeParameters,
};
},
},
});

View File

@@ -1,41 +1,66 @@
import { getNodeParameterOptions, getNodesInformation, getNodeTranslationHeaders, getNodeTypes, getResourceLocatorResults } from "@/api/nodeTypes";
import { DEFAULT_NODETYPE_VERSION, STORES } from "@/constants";
import { ICategoriesWithNodes, INodeCreateElement, INodeTypesState, IResourceLocatorReqParams } from "@/Interface";
import { addHeaders, addNodeTranslation } from "@/plugins/i18n";
import { omit, getCategoriesWithNodes, getCategorizedList } from "@/utils";
import { ILoadOptions, INodeCredentials, INodeListSearchResult, INodeParameters, INodePropertyOptions, INodeTypeDescription, INodeTypeNameVersion } from 'n8n-workflow';
import { defineStore } from "pinia";
import Vue from "vue";
import { useCredentialsStore } from "./credentials";
import { useRootStore } from "./n8nRootStore";
import { useUsersStore } from "./users";
import {
getNodeParameterOptions,
getNodesInformation,
getNodeTranslationHeaders,
getNodeTypes,
getResourceLocatorResults,
} from '@/api/nodeTypes';
import { DEFAULT_NODETYPE_VERSION, STORES } from '@/constants';
import {
ICategoriesWithNodes,
INodeCreateElement,
INodeTypesState,
IResourceLocatorReqParams,
} from '@/Interface';
import { addHeaders, addNodeTranslation } from '@/plugins/i18n';
import { omit, getCategoriesWithNodes, getCategorizedList } from '@/utils';
import {
ILoadOptions,
INodeCredentials,
INodeListSearchResult,
INodeParameters,
INodePropertyOptions,
INodeTypeDescription,
INodeTypeNameVersion,
} from 'n8n-workflow';
import { defineStore } from 'pinia';
import Vue from 'vue';
import { useCredentialsStore } from './credentials';
import { useRootStore } from './n8nRootStore';
import { useUsersStore } from './users';
import { useNodeCreatorStore } from './nodeCreator';
function getNodeVersions(nodeType: INodeTypeDescription) {
return Array.isArray(nodeType.version) ? nodeType.version : [nodeType.version];
}
export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
state: (): INodeTypesState => ({
nodeTypes: {},
}),
getters: {
allNodeTypes(): INodeTypeDescription[] {
return Object.values(this.nodeTypes).reduce<INodeTypeDescription[]>((allNodeTypes, nodeType) => {
const versionNumbers = Object.keys(nodeType).map(Number);
const allNodeVersions = versionNumbers.map(version => nodeType[version]);
return Object.values(this.nodeTypes).reduce<INodeTypeDescription[]>(
(allNodeTypes, nodeType) => {
const versionNumbers = Object.keys(nodeType).map(Number);
const allNodeVersions = versionNumbers.map((version) => nodeType[version]);
return [...allNodeTypes, ...allNodeVersions];
}, []);
return [...allNodeTypes, ...allNodeVersions];
},
[],
);
},
allLatestNodeTypes(): INodeTypeDescription[] {
return Object.values(this.nodeTypes).reduce<INodeTypeDescription[]>((allLatestNodeTypes, nodeVersions) => {
const versionNumbers = Object.keys(nodeVersions).map(Number);
const latestNodeVersion = nodeVersions[Math.max(...versionNumbers)];
return Object.values(this.nodeTypes).reduce<INodeTypeDescription[]>(
(allLatestNodeTypes, nodeVersions) => {
const versionNumbers = Object.keys(nodeVersions).map(Number);
const latestNodeVersion = nodeVersions[Math.max(...versionNumbers)];
if (!latestNodeVersion) return allLatestNodeTypes;
if (!latestNodeVersion) return allLatestNodeTypes;
return [...allLatestNodeTypes, latestNodeVersion];
}, []);
return [...allLatestNodeTypes, latestNodeVersion];
},
[],
);
},
getNodeType() {
return (nodeTypeName: string, version?: number): INodeTypeDescription | null => {
@@ -68,27 +93,35 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
},
actions: {
setNodeTypes(newNodeTypes: INodeTypeDescription[] = []): void {
const nodeTypes = newNodeTypes.reduce<Record<string, Record<string, INodeTypeDescription>>>((acc, newNodeType) => {
const newNodeVersions = getNodeVersions(newNodeType);
const nodeTypes = newNodeTypes.reduce<Record<string, Record<string, INodeTypeDescription>>>(
(acc, newNodeType) => {
const newNodeVersions = getNodeVersions(newNodeType);
if (newNodeVersions.length === 0) {
const singleVersion = { [DEFAULT_NODETYPE_VERSION]: newNodeType };
if (newNodeVersions.length === 0) {
const singleVersion = { [DEFAULT_NODETYPE_VERSION]: newNodeType };
acc[newNodeType.name] = singleVersion;
return acc;
}
for (const version of newNodeVersions) {
// Node exists with the same name
if (acc[newNodeType.name]) {
acc[newNodeType.name][version] = Object.assign(acc[newNodeType.name][version] ?? {}, newNodeType);
} else {
acc[newNodeType.name] = Object.assign(acc[newNodeType.name] ?? {}, { [version]: newNodeType });
acc[newNodeType.name] = singleVersion;
return acc;
}
}
return acc;
}, { ...this.nodeTypes });
for (const version of newNodeVersions) {
// Node exists with the same name
if (acc[newNodeType.name]) {
acc[newNodeType.name][version] = Object.assign(
acc[newNodeType.name][version] ?? {},
newNodeType,
);
} else {
acc[newNodeType.name] = Object.assign(acc[newNodeType.name] ?? {}, {
[version]: newNodeType,
});
}
}
return acc;
},
{ ...this.nodeTypes },
);
Vue.set(this, 'nodeTypes', nodeTypes);
// Trigger compute of mergedAppNodes getter so it's ready when user opens the node creator
@@ -101,21 +134,21 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
this.nodeTypes,
);
},
async getNodesInformation(nodeInfos: INodeTypeNameVersion[], replace = true): Promise<INodeTypeDescription[]> {
async getNodesInformation(
nodeInfos: INodeTypeNameVersion[],
replace = true,
): Promise<INodeTypeDescription[]> {
const rootStore = useRootStore();
const nodesInformation = await getNodesInformation(rootStore.getRestApiContext, nodeInfos);
nodesInformation.forEach(nodeInformation => {
nodesInformation.forEach((nodeInformation) => {
if (nodeInformation.translation) {
const nodeType = nodeInformation.name.replace('n8n-nodes-base.', '');
addNodeTranslation(
{ [nodeType]: nodeInformation.translation },
rootStore.defaultLocale,
);
addNodeTranslation({ [nodeType]: nodeInformation.translation }, rootStore.defaultLocale);
}
});
if(replace) this.setNodeTypes(nodesInformation);
if (replace) this.setNodeTypes(nodesInformation);
return nodesInformation;
},
@@ -139,16 +172,14 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
addHeaders(headers, rootStore.defaultLocale);
}
},
async getNodeParameterOptions(
sendData: {
nodeTypeAndVersion: INodeTypeNameVersion,
path: string,
methodName?: string,
loadOptions?: ILoadOptions,
currentNodeParameters: INodeParameters,
credentials?: INodeCredentials,
},
): Promise<INodePropertyOptions[]> {
async getNodeParameterOptions(sendData: {
nodeTypeAndVersion: INodeTypeNameVersion;
path: string;
methodName?: string;
loadOptions?: ILoadOptions;
currentNodeParameters: INodeParameters;
credentials?: INodeCredentials;
}): Promise<INodePropertyOptions[]> {
const rootStore = useRootStore();
return getNodeParameterOptions(rootStore.getRestApiContext, sendData);
},

View File

@@ -1,16 +1,29 @@
import { createApiKey, deleteApiKey, getApiKey } from "@/api/api-keys";
import { getPromptsData, getSettings, submitContactInfo, submitValueSurvey } from "@/api/settings";
import { testHealthEndpoint } from "@/api/templates";
import { CONTACT_PROMPT_MODAL_KEY, EnterpriseEditionFeature, STORES, VALUE_SURVEY_MODAL_KEY } from "@/constants";
import { ILogLevel, IN8nPromptResponse, IN8nPrompts, IN8nUISettings, IN8nValueSurveyData, ISettingsState, WorkflowCallerPolicyDefaultOption } from "@/Interface";
import { store } from "@/store";
import { ITelemetrySettings } from "n8n-workflow";
import { defineStore } from "pinia";
import Vue from "vue";
import { useRootStore } from "./n8nRootStore";
import { useUIStore } from "./ui";
import { useUsersStore } from "./users";
import { useVersionsStore } from "./versions";
import { createApiKey, deleteApiKey, getApiKey } from '@/api/api-keys';
import { getPromptsData, getSettings, submitContactInfo, submitValueSurvey } from '@/api/settings';
import { testHealthEndpoint } from '@/api/templates';
import {
CONTACT_PROMPT_MODAL_KEY,
EnterpriseEditionFeature,
STORES,
VALUE_SURVEY_MODAL_KEY,
} from '@/constants';
import {
ILogLevel,
IN8nPromptResponse,
IN8nPrompts,
IN8nUISettings,
IN8nValueSurveyData,
ISettingsState,
WorkflowCallerPolicyDefaultOption,
} from '@/Interface';
import { store } from '@/store';
import { ITelemetrySettings } from 'n8n-workflow';
import { defineStore } from 'pinia';
import Vue from 'vue';
import { useRootStore } from './n8nRootStore';
import { useUIStore } from './ui';
import { useUsersStore } from './users';
import { useVersionsStore } from './versions';
export const useSettingsStore = defineStore(STORES.SETTINGS, {
state: (): ISettingsState => ({
@@ -34,7 +47,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
}),
getters: {
isEnterpriseFeatureEnabled() {
return (feature: EnterpriseEditionFeature) : boolean => this.settings.enterprise[feature];
return (feature: EnterpriseEditionFeature): boolean => this.settings.enterprise[feature];
},
versionCli(): string {
return this.settings.versionCli;
@@ -51,58 +64,64 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
publicApiPath(): string {
return this.api.path;
},
showSetupPage() : boolean {
showSetupPage(): boolean {
return this.userManagement.showSetupOnFirstLoad === true;
},
isDesktopDeployment() : boolean {
isDesktopDeployment(): boolean {
if (!this.settings.deployment) {
return false;
}
return this.settings.deployment?.type.startsWith('desktop_');
},
isCloudDeployment() : boolean {
isCloudDeployment(): boolean {
if (!this.settings.deployment) {
return false;
}
return this.settings.deployment.type === 'cloud';
},
isSmtpSetup() : boolean {
isSmtpSetup(): boolean {
return this.userManagement.smtpSetup;
},
isPersonalizationSurveyEnabled() : boolean {
return (this.settings.telemetry && this.settings.telemetry.enabled) && this.settings.personalizationSurveyEnabled;
isPersonalizationSurveyEnabled(): boolean {
return (
this.settings.telemetry &&
this.settings.telemetry.enabled &&
this.settings.personalizationSurveyEnabled
);
},
telemetry() : ITelemetrySettings {
telemetry(): ITelemetrySettings {
return this.settings.telemetry;
},
logLevel() : ILogLevel {
logLevel(): ILogLevel {
return this.settings.logLevel;
},
isTelemetryEnabled() : boolean {
isTelemetryEnabled(): boolean {
return this.settings.telemetry && this.settings.telemetry.enabled;
},
areTagsEnabled() : boolean {
return this.settings.workflowTagsDisabled !== undefined ? !this.settings.workflowTagsDisabled : true;
areTagsEnabled(): boolean {
return this.settings.workflowTagsDisabled !== undefined
? !this.settings.workflowTagsDisabled
: true;
},
isHiringBannerEnabled() : boolean {
isHiringBannerEnabled(): boolean {
return this.settings.hiringBannerEnabled;
},
isTemplatesEnabled(): boolean {
return Boolean(this.settings.templates && this.settings.templates.enabled);
},
isTemplatesEndpointReachable() : boolean {
isTemplatesEndpointReachable(): boolean {
return this.templatesEndpointHealthy;
},
templatesHost() : string {
templatesHost(): string {
return this.settings.templates.host;
},
isCommunityNodesFeatureEnabled() : boolean {
isCommunityNodesFeatureEnabled(): boolean {
return this.settings.communityNodesEnabled;
},
isNpmAvailable() : boolean {
isNpmAvailable(): boolean {
return this.settings.isNpmAvailable;
},
allowedModules() : { builtIn?: string[]; external?: string[] } {
allowedModules(): { builtIn?: string[]; external?: string[] } {
return this.settings.allowedModules;
},
isQueueModeEnabled(): boolean {
@@ -117,8 +136,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
},
actions: {
setSettings(settings: IN8nUISettings): void {
this.settings = settings;
this.settings = settings;
this.userManagement.enabled = settings.userManagement.enabled;
this.userManagement.showSetupOnFirstLoad = !!settings.userManagement.showSetupOnFirstLoad;
this.userManagement.smtpSetup = settings.userManagement.smtpSetup;
@@ -133,7 +151,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
this.setSettings(settings);
this.settings.communityNodesEnabled = settings.communityNodesEnabled;
this.setAllowedModules(settings.allowedModules as { builtIn?: string, external?: string });
this.setAllowedModules(settings.allowedModules as { builtIn?: string; external?: string });
this.setSaveDataErrorExecution(settings.saveDataErrorExecution);
this.setSaveDataSuccessExecution(settings.saveDataSuccessExecution);
this.setSaveManualExecutions(settings.saveManualExecutions);
@@ -155,12 +173,11 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
},
stopShowingSetupPage(): void {
Vue.set(this.userManagement, 'showSetupOnFirstLoad', false);
},
setPromptsData(promptsData: IN8nPrompts): void {
Vue.set(this, 'promptsData', promptsData);
},
setAllowedModules(allowedModules: { builtIn?: string, external?: string }): void {
setAllowedModules(allowedModules: { builtIn?: string; external?: string }): void {
this.settings.allowedModules = {
...(allowedModules.builtIn && { builtIn: allowedModules.builtIn.split(',') }),
...(allowedModules.external && { external: allowedModules.external.split(',') }),
@@ -173,11 +190,14 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
try {
const uiStore = useUIStore();
const usersStore = useUsersStore();
const promptsData: IN8nPrompts = await getPromptsData(this.settings.instanceId, usersStore.currentUserId || '');
const promptsData: IN8nPrompts = await getPromptsData(
this.settings.instanceId,
usersStore.currentUserId || '',
);
if (promptsData && promptsData.showContactPrompt) {
uiStore.openModal(CONTACT_PROMPT_MODAL_KEY);
} else if (promptsData && promptsData.showValueSurvey) {
} else if (promptsData && promptsData.showValueSurvey) {
uiStore.openModal(VALUE_SURVEY_MODAL_KEY);
}
@@ -190,7 +210,11 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
async submitContactInfo(email: string): Promise<IN8nPromptResponse | undefined> {
try {
const usersStore = useUsersStore();
return await submitContactInfo(this.settings.instanceId, usersStore.currentUserId || '', email);
return await submitContactInfo(
this.settings.instanceId,
usersStore.currentUserId || '',
email,
);
} catch (error) {
return;
}
@@ -198,7 +222,11 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
async submitValueSurvey(params: IN8nValueSurveyData): Promise<IN8nPromptResponse | undefined> {
try {
const usersStore = useUsersStore();
return await submitValueSurvey(this.settings.instanceId, usersStore.currentUserId || '', params);
return await submitValueSurvey(
this.settings.instanceId,
usersStore.currentUserId || '',
params,
);
} catch (error) {
return;
}

View File

@@ -1,10 +1,10 @@
import { createTag, deleteTag, getTags, updateTag } from "@/api/tags";
import { STORES } from "@/constants";
import { ITag, ITagsState } from "@/Interface";
import { defineStore } from "pinia";
import Vue from "vue";
import { useRootStore } from "./n8nRootStore";
import { useWorkflowsStore } from "./workflows";
import { createTag, deleteTag, getTags, updateTag } from '@/api/tags';
import { STORES } from '@/constants';
import { ITag, ITagsState } from '@/Interface';
import { defineStore } from 'pinia';
import Vue from 'vue';
import { useRootStore } from './n8nRootStore';
import { useWorkflowsStore } from './workflows';
export const useTagsStore = defineStore(STORES.TAGS, {
state: (): ITagsState => ({
@@ -15,8 +15,7 @@ export const useTagsStore = defineStore(STORES.TAGS, {
}),
getters: {
allTags(): ITag[] {
return Object.values(this.tags)
.sort((a, b) => a.name.localeCompare(b.name));
return Object.values(this.tags).sort((a, b) => a.name.localeCompare(b.name));
},
isLoading(): boolean {
return this.loading;
@@ -30,12 +29,11 @@ export const useTagsStore = defineStore(STORES.TAGS, {
},
actions: {
setAllTags(tags: ITag[]): void {
this.tags = tags
.reduce((accu: { [id: string]: ITag }, tag: ITag) => {
accu[tag.id] = tag;
this.tags = tags.reduce((accu: { [id: string]: ITag }, tag: ITag) => {
accu[tag.id] = tag;
return accu;
}, {});
return accu;
}, {});
this.fetchedAll = true;
},
upsertTags(tags: ITag[]): void {
@@ -48,8 +46,7 @@ export const useTagsStore = defineStore(STORES.TAGS, {
...tag,
};
Vue.set(this.tags, tagId, newTag);
}
else {
} else {
Vue.set(this.tags, tagId, tag);
}
});
@@ -58,7 +55,7 @@ export const useTagsStore = defineStore(STORES.TAGS, {
Vue.delete(this.tags, id);
},
async fetchAll(params?: { force?: boolean, withUsageCount?: boolean }): Promise<ITag[]> {
async fetchAll(params?: { force?: boolean; withUsageCount?: boolean }): Promise<ITag[]> {
const { force = false, withUsageCount = false } = params || {};
if (!force && this.fetchedAll && this.fetchedUsageCount === withUsageCount) {
return Object.values(this.tags);
@@ -79,7 +76,7 @@ export const useTagsStore = defineStore(STORES.TAGS, {
return tag;
},
async rename({ id, name }: { id: string, name: string }) {
async rename({ id, name }: { id: string; name: string }) {
const rootStore = useRootStore();
const tag = await updateTag(rootStore.getRestApiContext, id, { name });
this.upsertTags([tag]);

View File

@@ -1,9 +1,25 @@
import { defineStore } from "pinia";
import { defineStore } from 'pinia';
import { STORES } from '@/constants';
import { ITemplatesCategory, ITemplatesCollection, ITemplatesCollectionFull, ITemplatesQuery, ITemplateState, ITemplatesWorkflow, ITemplatesWorkflowFull, IWorkflowTemplate } from "@/Interface";
import Vue from "vue";
import { useSettingsStore } from "./settings";
import { getCategories, getCollectionById, getCollections, getTemplateById, getWorkflows, getWorkflowTemplate } from "@/api/templates";
import {
ITemplatesCategory,
ITemplatesCollection,
ITemplatesCollectionFull,
ITemplatesQuery,
ITemplateState,
ITemplatesWorkflow,
ITemplatesWorkflowFull,
IWorkflowTemplate,
} from '@/Interface';
import Vue from 'vue';
import { useSettingsStore } from './settings';
import {
getCategories,
getCollectionById,
getCollections,
getTemplateById,
getWorkflows,
getWorkflowTemplate,
} from '@/api/templates';
const TEMPLATES_PAGE_SIZE = 10;
@@ -12,256 +28,283 @@ function getSearchKey(query: ITemplatesQuery): string {
}
export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
state: (): ITemplateState => ({
categories: {},
collections: {},
workflows: {},
collectionSearches: {},
workflowSearches: {},
currentSessionId: '',
previousSessionId: '',
}),
getters: {
allCategories(): ITemplatesCategory[] {
return Object.values(this.categories).sort((a: ITemplatesCategory, b: ITemplatesCategory) => a.name > b.name ? 1: -1);
},
getTemplateById() {
return (id: string): null | ITemplatesWorkflow => this.workflows[id];
},
getCollectionById() {
return (id: string): null | ITemplatesCollection => this.collections[id];
},
getCategoryById() {
return (id: string): null | ITemplatesCategory => this.categories[id];
},
getSearchedCollections() {
return (query: ITemplatesQuery) => {
const searchKey = getSearchKey(query);
const search = this.collectionSearches[searchKey];
if (!search) {
return null;
}
return search.collectionIds.map((collectionId: string) => this.collections[collectionId]);
};
},
getSearchedWorkflows() {
return (query: ITemplatesQuery) => {
const searchKey = getSearchKey(query);
const search = this.workflowSearches[searchKey];
if (!search) {
return null;
}
return search.workflowIds.map((workflowId: string) => this.workflows[workflowId]);
};
},
getSearchedWorkflowsTotal() {
return (query: ITemplatesQuery) => {
const searchKey = getSearchKey(query);
const search = this.workflowSearches[searchKey];
return search ? search.totalWorkflows : 0;
};
},
isSearchLoadingMore() {
return (query: ITemplatesQuery) => {
const searchKey = getSearchKey(query);
const search = this.workflowSearches[searchKey];
return Boolean(search && search.loadingMore);
};
},
isSearchFinished() {
return (query: ITemplatesQuery) => {
const searchKey = getSearchKey(query);
const search = this.workflowSearches[searchKey];
return Boolean(search && !search.loadingMore && search.totalWorkflows === search.workflowIds.length);
};
},
state: (): ITemplateState => ({
categories: {},
collections: {},
workflows: {},
collectionSearches: {},
workflowSearches: {},
currentSessionId: '',
previousSessionId: '',
}),
getters: {
allCategories(): ITemplatesCategory[] {
return Object.values(this.categories).sort((a: ITemplatesCategory, b: ITemplatesCategory) =>
a.name > b.name ? 1 : -1,
);
},
actions: {
addCategories(categories: ITemplatesCategory[]): void {
categories.forEach((category: ITemplatesCategory) => {
Vue.set(this.categories, category.id, category);
});
},
addCollections(collections: Array<ITemplatesCollection | ITemplatesCollectionFull>): void {
collections.forEach((collection) => {
const workflows = (collection.workflows || []).map((workflow) => ({id: workflow.id}));
const cachedCollection = this.collections[collection.id] || {};
Vue.set(this.collections, collection.id, {
...cachedCollection,
...collection,
workflows,
});
});
},
addWorkflows(workflows: Array<ITemplatesWorkflow | ITemplatesWorkflowFull>): void {
workflows.forEach((workflow: ITemplatesWorkflow) => {
const cachedWorkflow = this.workflows[workflow.id] || {};
Vue.set(this.workflows, workflow.id, {
...cachedWorkflow,
...workflow,
});
});
},
addCollectionSearch(data: {collections: ITemplatesCollection[], query: ITemplatesQuery}): void {
const collectionIds = data.collections.map((collection) => collection.id);
const searchKey = getSearchKey(data.query);
Vue.set(this.collectionSearches, searchKey, {
collectionIds,
});
},
addWorkflowsSearch(data: {totalWorkflows: number; workflows: ITemplatesWorkflow[], query: ITemplatesQuery}): void {
const workflowIds = data.workflows.map((workflow) => workflow.id);
const searchKey = getSearchKey(data.query);
const cachedResults = this.workflowSearches[searchKey];
if (!cachedResults) {
Vue.set(this.workflowSearches, searchKey, {
workflowIds,
totalWorkflows: data.totalWorkflows,
});
return;
getTemplateById() {
return (id: string): null | ITemplatesWorkflow => this.workflows[id];
},
getCollectionById() {
return (id: string): null | ITemplatesCollection => this.collections[id];
},
getCategoryById() {
return (id: string): null | ITemplatesCategory => this.categories[id];
},
getSearchedCollections() {
return (query: ITemplatesQuery) => {
const searchKey = getSearchKey(query);
const search = this.collectionSearches[searchKey];
if (!search) {
return null;
}
return search.collectionIds.map((collectionId: string) => this.collections[collectionId]);
};
},
getSearchedWorkflows() {
return (query: ITemplatesQuery) => {
const searchKey = getSearchKey(query);
const search = this.workflowSearches[searchKey];
if (!search) {
return null;
}
return search.workflowIds.map((workflowId: string) => this.workflows[workflowId]);
};
},
getSearchedWorkflowsTotal() {
return (query: ITemplatesQuery) => {
const searchKey = getSearchKey(query);
const search = this.workflowSearches[searchKey];
return search ? search.totalWorkflows : 0;
};
},
isSearchLoadingMore() {
return (query: ITemplatesQuery) => {
const searchKey = getSearchKey(query);
const search = this.workflowSearches[searchKey];
return Boolean(search && search.loadingMore);
};
},
isSearchFinished() {
return (query: ITemplatesQuery) => {
const searchKey = getSearchKey(query);
const search = this.workflowSearches[searchKey];
return Boolean(
search && !search.loadingMore && search.totalWorkflows === search.workflowIds.length,
);
};
},
},
actions: {
addCategories(categories: ITemplatesCategory[]): void {
categories.forEach((category: ITemplatesCategory) => {
Vue.set(this.categories, category.id, category);
});
},
addCollections(collections: Array<ITemplatesCollection | ITemplatesCollectionFull>): void {
collections.forEach((collection) => {
const workflows = (collection.workflows || []).map((workflow) => ({ id: workflow.id }));
const cachedCollection = this.collections[collection.id] || {};
Vue.set(this.collections, collection.id, {
...cachedCollection,
...collection,
workflows,
});
});
},
addWorkflows(workflows: Array<ITemplatesWorkflow | ITemplatesWorkflowFull>): void {
workflows.forEach((workflow: ITemplatesWorkflow) => {
const cachedWorkflow = this.workflows[workflow.id] || {};
Vue.set(this.workflows, workflow.id, {
...cachedWorkflow,
...workflow,
});
});
},
addCollectionSearch(data: {
collections: ITemplatesCollection[];
query: ITemplatesQuery;
}): void {
const collectionIds = data.collections.map((collection) => collection.id);
const searchKey = getSearchKey(data.query);
Vue.set(this.collectionSearches, searchKey, {
collectionIds,
});
},
addWorkflowsSearch(data: {
totalWorkflows: number;
workflows: ITemplatesWorkflow[];
query: ITemplatesQuery;
}): void {
const workflowIds = data.workflows.map((workflow) => workflow.id);
const searchKey = getSearchKey(data.query);
const cachedResults = this.workflowSearches[searchKey];
if (!cachedResults) {
Vue.set(this.workflowSearches, searchKey, {
workflowIds: [...cachedResults.workflowIds, ...workflowIds],
workflowIds,
totalWorkflows: data.totalWorkflows,
});
},
setWorkflowSearchLoading(query: ITemplatesQuery): void {
const searchKey = getSearchKey(query);
const cachedResults = this.workflowSearches[searchKey];
if (!cachedResults) {
return;
}
Vue.set(this.workflowSearches[searchKey], 'loadingMore', true);
},
setWorkflowSearchLoaded(query: ITemplatesQuery): void {
const searchKey = getSearchKey(query);
const cachedResults = this.workflowSearches[searchKey];
if (!cachedResults) {
return;
}
return;
}
Vue.set(this.workflowSearches[searchKey], 'loadingMore', false);
},
resetSessionId(): void {
this.previousSessionId = this.currentSessionId;
this.currentSessionId = '';
},
setSessionId(): void {
if (!this.currentSessionId) {
this.currentSessionId = `templates-${Date.now()}`;
}
},
async fetchTemplateById(templateId: string): Promise<ITemplatesWorkflow | ITemplatesWorkflowFull> {
const settingsStore = useSettingsStore();
const apiEndpoint: string = settingsStore.templatesHost;
const versionCli: string = settingsStore.versionCli;
const response = await getTemplateById(apiEndpoint, templateId, { 'n8n-version': versionCli });
const template: ITemplatesWorkflowFull = {
...response.workflow,
full: true,
};
this.addWorkflows([template]);
return template;
},
async fetchCollectionById(collectionId: string): Promise<ITemplatesCollection | null> {
const settingsStore = useSettingsStore();
const apiEndpoint: string = settingsStore.templatesHost;
const versionCli: string = settingsStore.versionCli;
const response = await getCollectionById(apiEndpoint, collectionId, { 'n8n-version': versionCli });
const collection: ITemplatesCollectionFull = {
...response.collection,
full: true,
};
this.addCollections([collection]);
this.addWorkflows(response.collection.workflows);
return this.getCollectionById(collectionId);
},
async getCategories(): Promise<ITemplatesCategory[]> {
const cachedCategories = this.allCategories;
if (cachedCategories.length) {
return cachedCategories;
}
const settingsStore = useSettingsStore();
const apiEndpoint: string = settingsStore.templatesHost;
const versionCli: string = settingsStore.versionCli;
const response = await getCategories(apiEndpoint, { 'n8n-version': versionCli });
const categories = response.categories;
this.addCategories(categories);
return categories;
},
async getCollections(query: ITemplatesQuery): Promise<ITemplatesCollection[]> {
const cachedResults = this.getSearchedCollections(query);
if (cachedResults) {
return cachedResults;
}
const settingsStore = useSettingsStore();
const apiEndpoint: string = settingsStore.templatesHost;
const versionCli: string = settingsStore.versionCli;
const response = await getCollections(apiEndpoint, query, { 'n8n-version': versionCli });
const collections = response.collections;
this.addCollections(collections);
this.addCollectionSearch({query, collections});
collections.forEach(collection => this.addWorkflows(collection.workflows as ITemplatesWorkflowFull[]));
return collections;
},
async getWorkflows(query: ITemplatesQuery): Promise<ITemplatesWorkflow[]> {
const cachedResults = this.getSearchedWorkflows(query);
if (cachedResults) {
return cachedResults;
}
const settingsStore = useSettingsStore();
const apiEndpoint: string = settingsStore.templatesHost;
const versionCli: string = settingsStore.versionCli;
const payload = await getWorkflows(apiEndpoint, {...query, skip: 0, limit: TEMPLATES_PAGE_SIZE}, { 'n8n-version': versionCli });
this.addWorkflows(payload.workflows);
this.addWorkflowsSearch({...payload, query});
return this.getSearchedWorkflows(query) || [];
},
async getMoreWorkflows(query: ITemplatesQuery): Promise<ITemplatesWorkflow[]> {
if (this.isSearchLoadingMore(query) && !this.isSearchFinished(query)) {
return [];
}
const cachedResults = this.getSearchedWorkflows(query) || [];
const settingsStore = useSettingsStore();
const apiEndpoint: string = settingsStore.templatesHost;
this.setWorkflowSearchLoading(query);
try {
const payload = await getWorkflows(apiEndpoint, {...query, skip: cachedResults.length, limit: TEMPLATES_PAGE_SIZE});
this.setWorkflowSearchLoaded(query);
this.addWorkflows(payload.workflows);
this.addWorkflowsSearch({...payload, query});
return this.getSearchedWorkflows(query) || [];
} catch (e) {
this.setWorkflowSearchLoaded(query);
throw e;
}
},
async getWorkflowTemplate(templateId: string): Promise<IWorkflowTemplate> {
const settingsStore = useSettingsStore();
const apiEndpoint: string = settingsStore.templatesHost;
const versionCli: string = settingsStore.versionCli;
return await getWorkflowTemplate(apiEndpoint, templateId, { 'n8n-version': versionCli });
},
Vue.set(this.workflowSearches, searchKey, {
workflowIds: [...cachedResults.workflowIds, ...workflowIds],
totalWorkflows: data.totalWorkflows,
});
},
setWorkflowSearchLoading(query: ITemplatesQuery): void {
const searchKey = getSearchKey(query);
const cachedResults = this.workflowSearches[searchKey];
if (!cachedResults) {
return;
}
Vue.set(this.workflowSearches[searchKey], 'loadingMore', true);
},
setWorkflowSearchLoaded(query: ITemplatesQuery): void {
const searchKey = getSearchKey(query);
const cachedResults = this.workflowSearches[searchKey];
if (!cachedResults) {
return;
}
Vue.set(this.workflowSearches[searchKey], 'loadingMore', false);
},
resetSessionId(): void {
this.previousSessionId = this.currentSessionId;
this.currentSessionId = '';
},
setSessionId(): void {
if (!this.currentSessionId) {
this.currentSessionId = `templates-${Date.now()}`;
}
},
async fetchTemplateById(
templateId: string,
): Promise<ITemplatesWorkflow | ITemplatesWorkflowFull> {
const settingsStore = useSettingsStore();
const apiEndpoint: string = settingsStore.templatesHost;
const versionCli: string = settingsStore.versionCli;
const response = await getTemplateById(apiEndpoint, templateId, {
'n8n-version': versionCli,
});
const template: ITemplatesWorkflowFull = {
...response.workflow,
full: true,
};
this.addWorkflows([template]);
return template;
},
async fetchCollectionById(collectionId: string): Promise<ITemplatesCollection | null> {
const settingsStore = useSettingsStore();
const apiEndpoint: string = settingsStore.templatesHost;
const versionCli: string = settingsStore.versionCli;
const response = await getCollectionById(apiEndpoint, collectionId, {
'n8n-version': versionCli,
});
const collection: ITemplatesCollectionFull = {
...response.collection,
full: true,
};
this.addCollections([collection]);
this.addWorkflows(response.collection.workflows);
return this.getCollectionById(collectionId);
},
async getCategories(): Promise<ITemplatesCategory[]> {
const cachedCategories = this.allCategories;
if (cachedCategories.length) {
return cachedCategories;
}
const settingsStore = useSettingsStore();
const apiEndpoint: string = settingsStore.templatesHost;
const versionCli: string = settingsStore.versionCli;
const response = await getCategories(apiEndpoint, { 'n8n-version': versionCli });
const categories = response.categories;
this.addCategories(categories);
return categories;
},
async getCollections(query: ITemplatesQuery): Promise<ITemplatesCollection[]> {
const cachedResults = this.getSearchedCollections(query);
if (cachedResults) {
return cachedResults;
}
const settingsStore = useSettingsStore();
const apiEndpoint: string = settingsStore.templatesHost;
const versionCli: string = settingsStore.versionCli;
const response = await getCollections(apiEndpoint, query, { 'n8n-version': versionCli });
const collections = response.collections;
this.addCollections(collections);
this.addCollectionSearch({ query, collections });
collections.forEach((collection) =>
this.addWorkflows(collection.workflows as ITemplatesWorkflowFull[]),
);
return collections;
},
async getWorkflows(query: ITemplatesQuery): Promise<ITemplatesWorkflow[]> {
const cachedResults = this.getSearchedWorkflows(query);
if (cachedResults) {
return cachedResults;
}
const settingsStore = useSettingsStore();
const apiEndpoint: string = settingsStore.templatesHost;
const versionCli: string = settingsStore.versionCli;
const payload = await getWorkflows(
apiEndpoint,
{ ...query, skip: 0, limit: TEMPLATES_PAGE_SIZE },
{ 'n8n-version': versionCli },
);
this.addWorkflows(payload.workflows);
this.addWorkflowsSearch({ ...payload, query });
return this.getSearchedWorkflows(query) || [];
},
async getMoreWorkflows(query: ITemplatesQuery): Promise<ITemplatesWorkflow[]> {
if (this.isSearchLoadingMore(query) && !this.isSearchFinished(query)) {
return [];
}
const cachedResults = this.getSearchedWorkflows(query) || [];
const settingsStore = useSettingsStore();
const apiEndpoint: string = settingsStore.templatesHost;
this.setWorkflowSearchLoading(query);
try {
const payload = await getWorkflows(apiEndpoint, {
...query,
skip: cachedResults.length,
limit: TEMPLATES_PAGE_SIZE,
});
this.setWorkflowSearchLoaded(query);
this.addWorkflows(payload.workflows);
this.addWorkflowsSearch({ ...payload, query });
return this.getSearchedWorkflows(query) || [];
} catch (e) {
this.setWorkflowSearchLoaded(query);
throw e;
}
},
async getWorkflowTemplate(templateId: string): Promise<IWorkflowTemplate> {
const settingsStore = useSettingsStore();
const apiEndpoint: string = settingsStore.templatesHost;
const versionCli: string = settingsStore.versionCli;
return await getWorkflowTemplate(apiEndpoint, templateId, { 'n8n-version': versionCli });
},
},
});

View File

@@ -2,7 +2,7 @@ import {
applyForOnboardingCall,
fetchNextOnboardingPrompt,
submitEmailOnSignup,
} from "@/api/workflow-webhooks";
} from '@/api/workflow-webhooks';
import {
ABOUT_MODAL_KEY,
CHANGE_PASSWORD_MODAL_KEY,
@@ -26,8 +26,9 @@ import {
VERSIONS_MODAL_KEY,
VIEWS,
WORKFLOW_ACTIVE_MODAL_KEY,
WORKFLOW_SETTINGS_MODAL_KEY, WORKFLOW_SHARE_MODAL_KEY,
} from "@/constants";
WORKFLOW_SETTINGS_MODAL_KEY,
WORKFLOW_SHARE_MODAL_KEY,
} from '@/constants';
import {
CurlToJSONResponse,
IFakeDoorLocation,
@@ -37,12 +38,12 @@ import {
IUser,
UIState,
XYPosition,
} from "@/Interface";
import Vue from "vue";
import { defineStore } from "pinia";
import { useRootStore } from "./n8nRootStore";
import { getCurlToJson } from "@/api/curlHelper";
import { useWorkflowsStore } from "./workflows";
} from '@/Interface';
import Vue from 'vue';
import { defineStore } from 'pinia';
import { useRootStore } from './n8nRootStore';
import { getCurlToJson } from '@/api/curlHelper';
import { useWorkflowsStore } from './workflows';
export const useUIStore = defineStore(STORES.UI, {
state: (): UIState => ({
@@ -202,16 +203,16 @@ export const useUIStore = defineStore(STORES.UI, {
}
return null;
},
getCurlCommand() : string|undefined {
getCurlCommand(): string | undefined {
return this.modals[IMPORT_CURL_MODAL_KEY].curlCommand;
},
getHttpNodeParameters() : string|undefined {
getHttpNodeParameters(): string | undefined {
return this.modals[IMPORT_CURL_MODAL_KEY].httpNodeParameters;
},
areExpressionsDisabled() : boolean {
areExpressionsDisabled(): boolean {
return this.currentView === VIEWS.DEMO;
},
isVersionsOpen() : boolean {
isVersionsOpen(): boolean {
return this.modals[VERSIONS_MODAL_KEY].open;
},
isModalOpen() {
@@ -230,18 +231,24 @@ export const useUIStore = defineStore(STORES.UI, {
return (name: string) => this.modals[name].data;
},
getFakeDoorByLocation() {
return (location: IFakeDoorLocation) => this.fakeDoorFeatures.filter(fakeDoor => fakeDoor.uiLocations.includes(location));
return (location: IFakeDoorLocation) =>
this.fakeDoorFeatures.filter((fakeDoor) => fakeDoor.uiLocations.includes(location));
},
getFakeDoorById() {
return (id: string) => this.fakeDoorFeatures.find(fakeDoor => fakeDoor.id.toString() === id);
return (id: string) =>
this.fakeDoorFeatures.find((fakeDoor) => fakeDoor.id.toString() === id);
},
isNodeView() : boolean {
return [VIEWS.NEW_WORKFLOW.toString(), VIEWS.WORKFLOW.toString(), VIEWS.EXECUTION.toString()].includes(this.currentView);
isNodeView(): boolean {
return [
VIEWS.NEW_WORKFLOW.toString(),
VIEWS.WORKFLOW.toString(),
VIEWS.EXECUTION.toString(),
].includes(this.currentView);
},
isActionActive() {
return (action: string) => this.activeActions.includes(action);
},
getSelectedNodes() : INodeUi[] {
getSelectedNodes(): INodeUi[] {
const seen = new Set();
return this.selectedNodes.filter((node: INodeUi) => {
// dedupe for instances when same node is selected in different ways
@@ -265,20 +272,20 @@ export const useUIStore = defineStore(STORES.UI, {
},
},
actions: {
setMode(name: string, mode: string): void {
setMode(name: string, mode: string): void {
Vue.set(this.modals[name], 'mode', mode);
},
setActiveId(name: string, id: string): void {
Vue.set(this.modals[name], 'activeId', id);
},
setModalData (payload: { name: string, data: Record<string, unknown> }) {
setModalData(payload: { name: string; data: Record<string, unknown> }) {
Vue.set(this.modals[payload.name], 'data', payload.data);
},
openModal(name: string): void {
Vue.set(this.modals[name], 'open', true);
this.modalStack = [name].concat(this.modalStack);
},
openModalWithData (payload: { name: string, data: Record<string, unknown> }): void {
openModalWithData(payload: { name: string; data: Record<string, unknown> }): void {
this.setModalData(payload);
this.openModal(payload.name);
},
@@ -395,16 +402,16 @@ export const useUIStore = defineStore(STORES.UI, {
const updated = this.sidebarMenuItems.concat(menuItems);
Vue.set(this, 'sidebarMenuItems', updated);
},
setCurlCommand (payload: { name: string, command: string }): void {
setCurlCommand(payload: { name: string; command: string }): void {
Vue.set(this.modals[payload.name], 'curlCommand', payload.command);
},
setHttpNodeParameters (payload: { name: string, parameters: string }): void {
setHttpNodeParameters(payload: { name: string; parameters: string }): void {
Vue.set(this.modals[payload.name], 'httpNodeParameters', payload.parameters);
},
toggleSidebarMenuCollapse (): void {
toggleSidebarMenuCollapse(): void {
this.sidebarMenuCollapsed = !this.sidebarMenuCollapsed;
},
async getCurlToJson (curlCommand: string): Promise<CurlToJSONResponse> {
async getCurlToJson(curlCommand: string): Promise<CurlToJSONResponse> {
const rootStore = useRootStore();
return await getCurlToJson(rootStore.getRestApiContext, curlCommand);
},

View File

@@ -1,14 +1,40 @@
import { changePassword, deleteUser, getCurrentUser, getUsers, inviteUsers, login, loginCurrentUser, logout, reinvite, sendForgotPasswordEmail, setupOwner, signup, skipOwnerSetup, submitPersonalizationSurvey, updateCurrentUser, updateCurrentUserPassword, validatePasswordToken, validateSignupToken } from "@/api/users";
import { PERSONALIZATION_MODAL_KEY, STORES } from "@/constants";
import { IInviteResponse, IPersonalizationLatestVersion, IUser, IUserResponse, IUsersState } from "@/Interface";
import { getPersonalizedNodeTypes, isAuthorized, PERMISSIONS, ROLE } from "@/utils";
import { defineStore } from "pinia";
import Vue from "vue";
import { useRootStore } from "./n8nRootStore";
import { useSettingsStore } from "./settings";
import { useUIStore } from "./ui";
import {
changePassword,
deleteUser,
getCurrentUser,
getUsers,
inviteUsers,
login,
loginCurrentUser,
logout,
reinvite,
sendForgotPasswordEmail,
setupOwner,
signup,
skipOwnerSetup,
submitPersonalizationSurvey,
updateCurrentUser,
updateCurrentUserPassword,
validatePasswordToken,
validateSignupToken,
} from '@/api/users';
import { PERSONALIZATION_MODAL_KEY, STORES } from '@/constants';
import {
IInviteResponse,
IPersonalizationLatestVersion,
IUser,
IUserResponse,
IUsersState,
} from '@/Interface';
import { getPersonalizedNodeTypes, isAuthorized, PERMISSIONS, ROLE } from '@/utils';
import { defineStore } from 'pinia';
import Vue from 'vue';
import { useRootStore } from './n8nRootStore';
import { useSettingsStore } from './settings';
import { useUIStore } from './ui';
const isDefaultUser = (user: IUserResponse | null) => Boolean(user && user.isPending && user.globalRole && user.globalRole.name === ROLE.Owner);
const isDefaultUser = (user: IUserResponse | null) =>
Boolean(user && user.isPending && user.globalRole && user.globalRole.name === ROLE.Owner);
const isPendingUser = (user: IUserResponse | null) => Boolean(user && user.isPending);
export const useUsersStore = defineStore(STORES.USERS, {
@@ -72,7 +98,9 @@ export const useUsersStore = defineStore(STORES.USERS, {
};
const user: IUser = {
...updatedUser,
fullName: userResponse.firstName? `${updatedUser.firstName} ${updatedUser.lastName || ''}`: undefined,
fullName: userResponse.firstName
? `${updatedUser.firstName} ${updatedUser.lastName || ''}`
: undefined,
isDefaultUser: isDefaultUser(updatedUser),
isPendingUser: isPendingUser(updatedUser),
isOwner: Boolean(updatedUser.globalRole && updatedUser.globalRole.name === ROLE.Owner),
@@ -107,7 +135,7 @@ export const useUsersStore = defineStore(STORES.USERS, {
this.currentUserId = user.id;
}
},
async loginWithCreds(params: {email: string, password: string}): Promise<void> {
async loginWithCreds(params: { email: string; password: string }): Promise<void> {
const rootStore = useRootStore();
const user = await login(rootStore.getRestApiContext, params);
if (user) {
@@ -120,7 +148,12 @@ export const useUsersStore = defineStore(STORES.USERS, {
await logout(rootStore.getRestApiContext);
this.currentUserId = null;
},
async createOwner(params: { firstName: string; lastName: string; email: string; password: string;}): Promise<void> {
async createOwner(params: {
firstName: string;
lastName: string;
email: string;
password: string;
}): Promise<void> {
const rootStore = useRootStore();
const user = await setupOwner(rootStore.getRestApiContext, params);
const settingsStore = useSettingsStore();
@@ -130,11 +163,20 @@ export const useUsersStore = defineStore(STORES.USERS, {
settingsStore.stopShowingSetupPage();
}
},
async validateSignupToken(params: {inviteeId: string, inviterId: string}): Promise<{ inviter: { firstName: string, lastName: string } }> {
async validateSignupToken(params: {
inviteeId: string;
inviterId: string;
}): Promise<{ inviter: { firstName: string; lastName: string } }> {
const rootStore = useRootStore();
return await validateSignupToken(rootStore.getRestApiContext, params);
},
async signup(params: { inviteeId: string; inviterId: string; firstName: string; lastName: string; password: string;}): Promise<void> {
async signup(params: {
inviteeId: string;
inviterId: string;
firstName: string;
lastName: string;
password: string;
}): Promise<void> {
const rootStore = useRootStore();
const user = await signup(rootStore.getRestApiContext, params);
if (user) {
@@ -142,28 +184,46 @@ export const useUsersStore = defineStore(STORES.USERS, {
this.currentUserId = user.id;
}
},
async sendForgotPasswordEmail( params: {email: string}): Promise<void> {
async sendForgotPasswordEmail(params: { email: string }): Promise<void> {
const rootStore = useRootStore();
await sendForgotPasswordEmail(rootStore.getRestApiContext, params);
},
async validatePasswordToken(params: {token: string, userId: string}): Promise<void> {
async validatePasswordToken(params: { token: string; userId: string }): Promise<void> {
const rootStore = useRootStore();
await validatePasswordToken(rootStore.getRestApiContext, params);
},
async changePassword(params: {token: string, password: string, userId: string}): Promise<void> {
async changePassword(params: {
token: string;
password: string;
userId: string;
}): Promise<void> {
const rootStore = useRootStore();
await changePassword(rootStore.getRestApiContext, params);
},
async updateUser(params: {id: string, firstName: string, lastName: string, email: string}): Promise<void> {
async updateUser(params: {
id: string;
firstName: string;
lastName: string;
email: string;
}): Promise<void> {
const rootStore = useRootStore();
const user = await updateCurrentUser(rootStore.getRestApiContext, params);
this.addUsers([user]);
},
async updateCurrentUserPassword({password, currentPassword}: {password: string, currentPassword: string}): Promise<void> {
async updateCurrentUserPassword({
password,
currentPassword,
}: {
password: string;
currentPassword: string;
}): Promise<void> {
const rootStore = useRootStore();
await updateCurrentUserPassword(rootStore.getRestApiContext, {newPassword: password, currentPassword});
await updateCurrentUserPassword(rootStore.getRestApiContext, {
newPassword: password,
currentPassword,
});
},
async deleteUser(params: { id: string, transferId?: string}): Promise<void> {
async deleteUser(params: { id: string; transferId?: string }): Promise<void> {
const rootStore = useRootStore();
await deleteUser(rootStore.getRestApiContext, params);
this.deleteUserById(params.id);
@@ -173,13 +233,13 @@ export const useUsersStore = defineStore(STORES.USERS, {
const users = await getUsers(rootStore.getRestApiContext);
this.addUsers(users);
},
async inviteUsers(params: Array<{email: string}>): Promise<IInviteResponse[]> {
async inviteUsers(params: Array<{ email: string }>): Promise<IInviteResponse[]> {
const rootStore = useRootStore();
const users = await inviteUsers(rootStore.getRestApiContext, params);
this.addUsers(users.map(({user}) => ({ isPending: true, ...user })));
this.addUsers(users.map(({ user }) => ({ isPending: true, ...user })));
return users;
},
async reinviteUser(params: {id: string}): Promise<void> {
async reinviteUser(params: { id: string }): Promise<void> {
const rootStore = useRootStore();
await reinvite(rootStore.getRestApiContext, params);
},

View File

@@ -1,8 +1,8 @@
import { getNextVersions } from "@/api/versions";
import { STORES } from "@/constants";
import { IVersion, IVersionNotificationSettings, IVersionsState } from "@/Interface";
import { defineStore } from "pinia";
import { useRootStore } from "./n8nRootStore";
import { getNextVersions } from '@/api/versions';
import { STORES } from '@/constants';
import { IVersion, IVersionNotificationSettings, IVersionsState } from '@/Interface';
import { defineStore } from 'pinia';
import { useRootStore } from './n8nRootStore';
export const useVersionsStore = defineStore(STORES.VERSIONS, {
state: (): IVersionsState => ({
@@ -26,7 +26,7 @@ export const useVersionsStore = defineStore(STORES.VERSIONS, {
},
},
actions: {
setVersions({versions, currentVersion}: {versions: IVersion[], currentVersion: string}) {
setVersions({ versions, currentVersion }: { versions: IVersion[]; currentVersion: string }) {
this.nextVersions = versions.filter((version) => version.name !== currentVersion);
this.currentVersion = versions.find((version) => version.name === currentVersion);
},
@@ -43,8 +43,7 @@ export const useVersionsStore = defineStore(STORES.VERSIONS, {
const versions = await getNextVersions(endpoint, currentVersion, instanceId);
this.setVersions({ versions, currentVersion });
}
} catch (e) {
}
} catch (e) {}
},
},
});

View File

@@ -1,27 +1,27 @@
import { STORES } from "@/constants";
import {IFakeDoor, INodeUi, IRootState, NestedRecord} from "@/Interface";
import { IMenuItem } from "n8n-design-system";
import { IWorkflowSettings } from "n8n-workflow";
import { defineStore } from "pinia";
import { useRootStore } from "./n8nRootStore";
import { useNDVStore } from "./ndv";
import { useSettingsStore } from "./settings";
import { useUIStore } from "./ui";
import { useUsersStore } from "./users";
import { useWorkflowsStore } from "./workflows";
import { STORES } from '@/constants';
import { IFakeDoor, INodeUi, IRootState, NestedRecord } from '@/Interface';
import { IMenuItem } from 'n8n-design-system';
import { IWorkflowSettings } from 'n8n-workflow';
import { defineStore } from 'pinia';
import { useRootStore } from './n8nRootStore';
import { useNDVStore } from './ndv';
import { useSettingsStore } from './settings';
import { useUIStore } from './ui';
import { useUsersStore } from './users';
import { useWorkflowsStore } from './workflows';
export const useWebhooksStore = defineStore(STORES.WEBHOOKS, {
getters: {
globalRoleName(): string {
return useUsersStore().globalRoleName;
},
getDynamicTranslations () {
getDynamicTranslations() {
return useUIStore().dynamicTranslations;
},
getFakeDoorFeatures () {
getFakeDoorFeatures() {
return useUIStore().fakeDoorFeatures;
},
isUserManagementEnabled () {
isUserManagementEnabled() {
return useSettingsStore().isUserManagementEnabled;
},
getFakeDoorItems(): IFakeDoor[] {
@@ -48,9 +48,11 @@ export const useWebhooksStore = defineStore(STORES.WEBHOOKS, {
activeExecutionId(): string {
return useWorkflowsStore().activeExecutionId || '';
},
nodeByName: (state: IRootState) => (nodeName: string): INodeUi | null => {
return useWorkflowsStore().getNodeByName(nodeName);
},
nodeByName:
(state: IRootState) =>
(nodeName: string): INodeUi | null => {
return useWorkflowsStore().getNodeByName(nodeName);
},
allNodes(): INodeUi[] {
return useWorkflowsStore().allNodes;
},
@@ -59,7 +61,7 @@ export const useWebhooksStore = defineStore(STORES.WEBHOOKS, {
addSidebarMenuItems(menuItems: IMenuItem[]) {
const uiStore = useUIStore();
const updated = uiStore.sidebarMenuItems.concat(menuItems);
uiStore.sidebarMenuItems = updated;
uiStore.sidebarMenuItems = updated;
},
setFakeDoorFeatures(fakeDoors: IFakeDoor[]): void {
useUIStore().fakeDoorFeatures = fakeDoors;

View File

@@ -1,17 +1,17 @@
import Vue from 'vue';
import {
IUser,
} from '../Interface';
import {setWorkflowSharedWith} from "@/api/workflows.ee";
import {EnterpriseEditionFeature, STORES} from "@/constants";
import {useRootStore} from "@/stores/n8nRootStore";
import {useSettingsStore} from "@/stores/settings";
import {defineStore} from "pinia";
import {useWorkflowsStore} from "@/stores/workflows";
import {i18n} from "@/plugins/i18n";
import { IUser } from '../Interface';
import { setWorkflowSharedWith } from '@/api/workflows.ee';
import { EnterpriseEditionFeature, STORES } from '@/constants';
import { useRootStore } from '@/stores/n8nRootStore';
import { useSettingsStore } from '@/stores/settings';
import { defineStore } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { i18n } from '@/plugins/i18n';
export const useWorkflowsEEStore = defineStore(STORES.WORKFLOWS_EE, {
state() { return {}; },
state() {
return {};
},
getters: {
getWorkflowOwnerName() {
return (workflowId: string): string => {
@@ -23,49 +23,54 @@ export const useWorkflowsEEStore = defineStore(STORES.WORKFLOWS_EE, {
},
},
actions: {
setWorkflowOwnedBy(payload: { workflowId: string, ownedBy: Partial<IUser> }): void {
setWorkflowOwnedBy(payload: { workflowId: string; ownedBy: Partial<IUser> }): void {
const workflowsStore = useWorkflowsStore();
Vue.set(workflowsStore.workflowsById[payload.workflowId], 'ownedBy', payload.ownedBy);
Vue.set(workflowsStore.workflow, 'ownedBy', payload.ownedBy);
},
setWorkflowSharedWith(payload: { workflowId: string, sharedWith: Array<Partial<IUser>> }): void {
setWorkflowSharedWith(payload: {
workflowId: string;
sharedWith: Array<Partial<IUser>>;
}): void {
const workflowsStore = useWorkflowsStore();
Vue.set(workflowsStore.workflowsById[payload.workflowId], 'sharedWith', payload.sharedWith);
Vue.set(workflowsStore.workflow, 'sharedWith', payload.sharedWith);
},
addWorkflowSharee(payload: { workflowId: string, sharee: Partial<IUser> }): void {
addWorkflowSharee(payload: { workflowId: string; sharee: Partial<IUser> }): void {
const workflowsStore = useWorkflowsStore();
Vue.set(
workflowsStore.workflowsById[payload.workflowId],
'sharedWith',
(workflowsStore.workflowsById[payload.workflowId].sharedWith || []).concat([payload.sharee]),
(workflowsStore.workflowsById[payload.workflowId].sharedWith || []).concat([
payload.sharee,
]),
);
},
removeWorkflowSharee(payload: { workflowId: string, sharee: Partial<IUser> }): void {
removeWorkflowSharee(payload: { workflowId: string; sharee: Partial<IUser> }): void {
const workflowsStore = useWorkflowsStore();
Vue.set(
workflowsStore.workflowsById[payload.workflowId],
'sharedWith',
(workflowsStore.workflowsById[payload.workflowId].sharedWith || [])
.filter((sharee) => sharee.id !== payload.sharee.id),
(workflowsStore.workflowsById[payload.workflowId].sharedWith || []).filter(
(sharee) => sharee.id !== payload.sharee.id,
),
);
},
async saveWorkflowSharedWith(payload: { sharedWith: Array<Partial<IUser>>; workflowId: string; }): Promise<void> {
async saveWorkflowSharedWith(payload: {
sharedWith: Array<Partial<IUser>>;
workflowId: string;
}): Promise<void> {
const rootStore = useRootStore();
const settingsStore = useSettingsStore();
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.WorkflowSharing)) {
await setWorkflowSharedWith(
rootStore.getRestApiContext,
payload.workflowId,
{
shareWithIds: payload.sharedWith.map((sharee) => sharee.id as string),
},
);
await setWorkflowSharedWith(rootStore.getRestApiContext, payload.workflowId, {
shareWithIds: payload.sharedWith.map((sharee) => sharee.id as string),
});
this.setWorkflowSharedWith(payload);
}

View File

@@ -5,7 +5,7 @@ import {
MAX_WORKFLOW_NAME_LENGTH,
PLACEHOLDER_EMPTY_WORKFLOW_ID,
STORES,
} from "@/constants";
} from '@/constants';
import {
IExecutionResponse,
IExecutionsCurrentSummaryExtended,
@@ -20,8 +20,8 @@ import {
IWorkflowDb,
IWorkflowsMap,
WorkflowsState,
} from "@/Interface";
import { defineStore } from "pinia";
} from '@/Interface';
import { defineStore } from 'pinia';
import {
deepCopy,
IConnection,
@@ -38,24 +38,29 @@ import {
ITaskData,
IWorkflowSettings,
} from 'n8n-workflow';
import Vue from "vue";
import Vue from 'vue';
import { useRootStore } from "./n8nRootStore";
import { useRootStore } from './n8nRootStore';
import {
getActiveWorkflows,
getCurrentExecutions,
getFinishedExecutions,
getNewWorkflow,
getWorkflows,
} from "@/api/workflows";
import {useUIStore} from "./ui";
import {dataPinningEventBus} from "@/event-bus/data-pinning-event-bus";
import {isJsonKeyObject, getPairedItemsMapping, stringSizeInBytes, isObjectLiteral} from "@/utils";
import {useNDVStore} from "./ndv";
import {useNodeTypesStore} from "./nodeTypes";
import {useWorkflowsEEStore} from "@/stores/workflows.ee";
import {useUsersStore} from "@/stores/users";
import {useSettingsStore} from "@/stores/settings";
} from '@/api/workflows';
import { useUIStore } from './ui';
import { dataPinningEventBus } from '@/event-bus/data-pinning-event-bus';
import {
isJsonKeyObject,
getPairedItemsMapping,
stringSizeInBytes,
isObjectLiteral,
} from '@/utils';
import { useNDVStore } from './ndv';
import { useNodeTypesStore } from './nodeTypes';
import { useWorkflowsEEStore } from '@/stores/workflows.ee';
import { useUsersStore } from '@/stores/users';
import { useSettingsStore } from '@/stores/settings';
const createEmptyWorkflow = (): IWorkflowDb => ({
id: PLACEHOLDER_EMPTY_WORKFLOW_ID,
@@ -101,29 +106,31 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
workflowVersionId(): string | undefined {
return this.workflow.versionId;
},
workflowSettings() : IWorkflowSettings {
workflowSettings(): IWorkflowSettings {
if (this.workflow.settings === undefined) {
return {};
}
return this.workflow.settings;
},
workflowTags() : string[] {
workflowTags(): string[] {
return this.workflow.tags as string[];
},
allWorkflows() : IWorkflowDb[] {
return Object.values(this.workflowsById)
.sort((a, b) => a.name.localeCompare(b.name));
allWorkflows(): IWorkflowDb[] {
return Object.values(this.workflowsById).sort((a, b) => a.name.localeCompare(b.name));
},
isNewWorkflow() : boolean {
isNewWorkflow(): boolean {
return this.workflow.id === PLACEHOLDER_EMPTY_WORKFLOW_ID;
},
isWorkflowActive(): boolean {
return this.workflow.active;
},
workflowTriggerNodes() : INodeUi[] {
workflowTriggerNodes(): INodeUi[] {
return this.workflow.nodes.filter((node: INodeUi) => {
const nodeTypesStore = useNodeTypesStore();
const nodeType = nodeTypesStore.getNodeType(node.type as string, node.typeVersion as number);
const nodeType = nodeTypesStore.getNodeType(
node.type as string,
node.typeVersion as number,
);
return nodeType && nodeType.group.includes('trigger');
});
},
@@ -131,7 +138,11 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
return !!this.workflow.nodes.find((node: INodeUi) => !!node.webhookId);
},
getWorkflowRunData(): IRunData | null {
if (!this.workflowExecutionData || !this.workflowExecutionData.data || !this.workflowExecutionData.data.resultData) {
if (
!this.workflowExecutionData ||
!this.workflowExecutionData.data ||
!this.workflowExecutionData.data.resultData
) {
return null;
}
return this.workflowExecutionData.data.resultData.runData;
@@ -154,10 +165,10 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
},
// Node getters
allConnections() : IConnections {
allConnections(): IConnections {
return this.workflow.connections;
},
outgoingConnectionsByNodeName() {
outgoingConnectionsByNodeName() {
return (nodeName: string): INodeConnections => {
if (this.workflow.connections.hasOwnProperty(nodeName)) {
return this.workflow.connections[nodeName];
@@ -165,10 +176,10 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
return {};
};
},
allNodes() : INodeUi[] {
allNodes(): INodeUi[] {
return this.workflow.nodes;
},
nodesByName() : { [name: string]: INodeUi } {
nodesByName(): { [name: string]: INodeUi } {
return this.workflow.nodes.reduce((accu: { [name: string]: INodeUi }, node) => {
accu[node.name] = node;
return accu;
@@ -178,7 +189,8 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
return (nodeName: string): INodeUi | null => this.nodesByName[nodeName] || null;
},
getNodeById() {
return (nodeId: string): INodeUi | undefined => this.workflow.nodes.find((node: INodeUi) => node.id === nodeId);
return (nodeId: string): INodeUi | undefined =>
this.workflow.nodes.find((node: INodeUi) => node.id === nodeId);
},
nodesIssuesExist(): boolean {
for (const node of this.workflow.nodes) {
@@ -195,28 +207,31 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
pinDataSize(): number {
const ndvStore = useNDVStore();
const activeNode = ndvStore.activeNodeName;
return this.workflow.nodes
.reduce((acc, node) => {
if (typeof node.pinData !== 'undefined' && node.name !== activeNode) {
acc += stringSizeInBytes(node.pinData);
}
return this.workflow.nodes.reduce((acc, node) => {
if (typeof node.pinData !== 'undefined' && node.name !== activeNode) {
acc += stringSizeInBytes(node.pinData);
}
return acc;
}, 0);
return acc;
}, 0);
},
executedNode(): string | undefined {
return this.workflowExecutionData ? this.workflowExecutionData.executedNode : undefined;
},
getParametersLastUpdate(): ((name: string) => number | undefined) {
return (nodeName: string) => this.nodeMetadata[nodeName] && this.nodeMetadata[nodeName].parametersLastUpdatedAt;
getParametersLastUpdate(): (name: string) => number | undefined {
return (nodeName: string) =>
this.nodeMetadata[nodeName] && this.nodeMetadata[nodeName].parametersLastUpdatedAt;
},
// Executions getters
getExecutionDataById(): (id: string) => IExecutionsSummary | undefined {
return (id: string): IExecutionsSummary | undefined => this.currentWorkflowExecutions.find(execution => execution.id === id);
return (id: string): IExecutionsSummary | undefined =>
this.currentWorkflowExecutions.find((execution) => execution.id === id);
},
getAllLoadedFinishedExecutions(): IExecutionsSummary[] {
return this.currentWorkflowExecutions.filter(ex => ex.finished === true || ex.stoppedAt !== undefined);
return this.currentWorkflowExecutions.filter(
(ex) => ex.finished === true || ex.stoppedAt !== undefined,
);
},
getWorkflowExecution(): IExecutionResponse | null {
return this.workflowExecutionData;
@@ -226,7 +241,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
},
},
actions: {
// Workflow actions
async fetchAllWorkflows(): Promise<IWorkflowDb[]> {
@@ -246,8 +260,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
try {
const rootStore = useRootStore();
workflowData = await getNewWorkflow(rootStore.getRestApiContext, name);
}
catch (e) {
} catch (e) {
// in case of error, default to original name
workflowData.name = name || DEFAULT_NEW_WORKFLOW_NAME;
}
@@ -268,26 +281,32 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
}
},
setWorkflowId (id: string): void {
setWorkflowId(id: string): void {
this.workflow.id = id === 'new' ? PLACEHOLDER_EMPTY_WORKFLOW_ID : id;
},
setUsedCredentials(data: IUsedCredential[]) {
this.workflow.usedCredentials = data;
this.usedCredentials = data.reduce<{ [name: string]: IUsedCredential }>((accu, credential) => {
accu[credential.id!] = credential;
return accu;
}, {});
this.usedCredentials = data.reduce<{ [name: string]: IUsedCredential }>(
(accu, credential) => {
accu[credential.id!] = credential;
return accu;
},
{},
);
},
setWorkflowName(data: { newName: string, setStateDirty: boolean }): void {
setWorkflowName(data: { newName: string; setStateDirty: boolean }): void {
if (data.setStateDirty === true) {
const uiStore = useUIStore();
uiStore.stateIsDirty = true;
}
this.workflow.name = data.newName;
if (this.workflow.id !== PLACEHOLDER_EMPTY_WORKFLOW_ID && this.workflowsById[this.workflow.id]) {
if (
this.workflow.id !== PLACEHOLDER_EMPTY_WORKFLOW_ID &&
this.workflowsById[this.workflow.id]
) {
this.workflowsById[this.workflow.id].name = data.newName;
}
},
@@ -297,9 +316,14 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
},
// replace invalid credentials in workflow
replaceInvalidWorkflowCredentials(data: {credentials: INodeCredentialsDetails, invalid: INodeCredentialsDetails, type: string}): void {
this.workflow.nodes.forEach((node : INodeUi) => {
const nodeCredentials: INodeCredentials | undefined = (node as unknown as INode).credentials;
replaceInvalidWorkflowCredentials(data: {
credentials: INodeCredentialsDetails;
invalid: INodeCredentialsDetails;
type: string;
}): void {
this.workflow.nodes.forEach((node: INodeUi) => {
const nodeCredentials: INodeCredentials | undefined = (node as unknown as INode)
.credentials;
if (!nodeCredentials || !nodeCredentials[data.type]) {
return;
@@ -307,7 +331,10 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
const nodeCredentialDetails: INodeCredentialsDetails | string = nodeCredentials[data.type];
if (typeof nodeCredentialDetails === 'string' && nodeCredentialDetails === data.invalid.name) {
if (
typeof nodeCredentialDetails === 'string' &&
nodeCredentialDetails === data.invalid.name
) {
(node.credentials as INodeCredentials)[data.type] = data.credentials;
return;
}
@@ -325,7 +352,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
});
},
setWorkflows(workflows: IWorkflowDb[]) : void {
setWorkflows(workflows: IWorkflowDb[]): void {
this.workflowsById = workflows.reduce<IWorkflowsMap>((acc, workflow: IWorkflowDb) => {
if (workflow.id) {
acc[workflow.id] = workflow;
@@ -335,12 +362,12 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
}, {});
},
deleteWorkflow(id: string) : void {
deleteWorkflow(id: string): void {
const { [id]: deletedWorkflow, ...workflows } = this.workflowsById;
this.workflowsById = workflows;
},
addWorkflow(workflow: IWorkflowDb) : void {
addWorkflow(workflow: IWorkflowDb): void {
Vue.set(this.workflowsById, workflow.id, {
...this.workflowsById[workflow.id],
...deepCopy(workflow),
@@ -357,7 +384,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
if (this.workflowsById[workflowId]) {
this.workflowsById[workflowId].active = true;
}
},
setWorkflowInactive(workflowId: string): void {
@@ -377,27 +403,27 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
return activeWorkflows;
},
setActive(newActive: boolean) : void {
setActive(newActive: boolean): void {
this.workflow.active = newActive;
},
async getDuplicateCurrentWorkflowName(currentWorkflowName: string): Promise<string> {
if (currentWorkflowName && (currentWorkflowName.length + DUPLICATE_POSTFFIX.length) >= MAX_WORKFLOW_NAME_LENGTH) {
if (
currentWorkflowName &&
currentWorkflowName.length + DUPLICATE_POSTFFIX.length >= MAX_WORKFLOW_NAME_LENGTH
) {
return currentWorkflowName;
}
let newName = `${currentWorkflowName}${DUPLICATE_POSTFFIX}`;
try {
const rootStore = useRootStore();
const newWorkflow = await getNewWorkflow(rootStore.getRestApiContext, newName );
const newWorkflow = await getNewWorkflow(rootStore.getRestApiContext, newName);
newName = newWorkflow.name;
}
catch (e) {
}
} catch (e) {}
return newName;
},
// Node actions
setWorkflowExecutionData(workflowResultData: IExecutionResponse | null): void {
this.workflowExecutionData = workflowResultData;
@@ -453,7 +479,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
}
},
pinData(payload: { node: INodeUi, data: INodeExecutionData[] }): void {
pinData(payload: { node: INodeUi; data: INodeExecutionData[] }): void {
if (!this.workflow.pinData) {
Vue.set(this.workflow, 'pinData', {});
}
@@ -462,7 +488,9 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
payload.data = [payload.data];
}
const storedPinData = payload.data.map(item => isJsonKeyObject(item) ? item : { json: item });
const storedPinData = payload.data.map((item) =>
isJsonKeyObject(item) ? item : { json: item },
);
Vue.set(this.workflow.pinData!, payload.node.name, storedPinData);
@@ -486,7 +514,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
dataPinningEventBus.$emit('unpin-data', { [payload.node.name]: undefined });
},
addConnection(data: { connection: IConnection[], setStateDirty: boolean }): void {
addConnection(data: { connection: IConnection[]; setStateDirty: boolean }): void {
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
@@ -507,8 +535,15 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
if (!this.workflow.connections[sourceData.node].hasOwnProperty(sourceData.type)) {
Vue.set(this.workflow.connections[sourceData.node], sourceData.type, []);
}
if (this.workflow.connections[sourceData.node][sourceData.type].length < (sourceData.index + 1)) {
for (let i = this.workflow.connections[sourceData.node][sourceData.type].length; i <= sourceData.index; i++) {
if (
this.workflow.connections[sourceData.node][sourceData.type].length <
sourceData.index + 1
) {
for (
let i = this.workflow.connections[sourceData.node][sourceData.type].length;
i <= sourceData.index;
i++
) {
this.workflow.connections[sourceData.node][sourceData.type].push([]);
}
}
@@ -517,10 +552,14 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
const checkProperties = ['index', 'node', 'type'];
let propertyName: string;
let connectionExists = false;
connectionLoop:
for (const existingConnection of this.workflow.connections[sourceData.node][sourceData.type][sourceData.index]) {
connectionLoop: for (const existingConnection of this.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
if (
// tslint:disable-next-line:no-any
(existingConnection as any)[propertyName] !== (destinationData as any)[propertyName]
) {
continue connectionLoop;
}
}
@@ -529,7 +568,9 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
}
// Add the new connection if it does not exist already
if (connectionExists === false) {
this.workflow.connections[sourceData.node][sourceData.type][sourceData.index].push(destinationData);
this.workflow.connections[sourceData.node][sourceData.type][sourceData.index].push(
destinationData,
);
}
},
@@ -543,15 +584,23 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
if (!this.workflow.connections[sourceData.node].hasOwnProperty(sourceData.type)) {
return;
}
if (this.workflow.connections[sourceData.node][sourceData.type].length < (sourceData.index + 1)) {
if (
this.workflow.connections[sourceData.node][sourceData.type].length <
sourceData.index + 1
) {
return;
}
const uiStore = useUIStore();
uiStore.stateIsDirty = true;
const connections = this.workflow.connections[sourceData.node][sourceData.type][sourceData.index];
const connections =
this.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) {
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);
}
@@ -576,33 +625,50 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
// Remove all destination connections
const indexesToRemove = [];
let sourceNode: string, type: string, sourceIndex: string, connectionIndex: string, connectionData: IConnection;
let sourceNode: string,
type: string,
sourceIndex: string,
connectionIndex: string,
connectionData: IConnection;
for (sourceNode of Object.keys(this.workflow.connections)) {
for (type of Object.keys(this.workflow.connections[sourceNode])) {
for (sourceIndex of Object.keys(this.workflow.connections[sourceNode][type])) {
indexesToRemove.length = 0;
for (connectionIndex of Object.keys(this.workflow.connections[sourceNode][type][parseInt(sourceIndex, 10)])) {
connectionData = this.workflow.connections[sourceNode][type][parseInt(sourceIndex, 10)][parseInt(connectionIndex, 10)];
for (connectionIndex of Object.keys(
this.workflow.connections[sourceNode][type][parseInt(sourceIndex, 10)],
)) {
connectionData =
this.workflow.connections[sourceNode][type][parseInt(sourceIndex, 10)][
parseInt(connectionIndex, 10)
];
if (connectionData.node === node.name) {
indexesToRemove.push(connectionIndex);
}
}
indexesToRemove.forEach((index) => {
this.workflow.connections[sourceNode][type][parseInt(sourceIndex, 10)].splice(parseInt(index, 10), 1);
this.workflow.connections[sourceNode][type][parseInt(sourceIndex, 10)].splice(
parseInt(index, 10),
1,
);
});
}
}
}
},
renameNodeSelectedAndExecution(nameData: { old: string, new: string }): void {
renameNodeSelectedAndExecution(nameData: { old: string; new: string }): void {
const uiStore = useUIStore();
uiStore.stateIsDirty = true;
// If node has any WorkflowResultData rename also that one that the data
// does still get displayed also after node got renamed
if (this.workflowExecutionData !== null && this.workflowExecutionData.data && this.workflowExecutionData.data.resultData.runData.hasOwnProperty(nameData.old)) {
this.workflowExecutionData.data.resultData.runData[nameData.new] = this.workflowExecutionData.data.resultData.runData[nameData.old];
if (
this.workflowExecutionData !== null &&
this.workflowExecutionData.data &&
this.workflowExecutionData.data.resultData.runData.hasOwnProperty(nameData.old)
) {
this.workflowExecutionData.data.resultData.runData[nameData.new] =
this.workflowExecutionData.data.resultData.runData[nameData.old];
delete this.workflowExecutionData.data.resultData.runData[nameData.old];
}
@@ -630,7 +696,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
},
setNodeIssue(nodeIssueData: INodeIssueData): boolean {
const node = this.workflow.nodes.find(node => {
const node = this.workflow.nodes.find((node) => {
return node.name === nodeIssueData.node;
});
if (!node) {
@@ -681,7 +747,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
}
},
removeAllNodes(data: { setStateDirty: boolean, removePinData: boolean }): void {
removeAllNodes(data: { setStateDirty: boolean; removePinData: boolean }): void {
if (data.setStateDirty === true) {
const uiStore = useUIStore();
uiStore.stateIsDirty = true;
@@ -697,7 +763,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
updateNodeProperties(updateInformation: INodeUpdatePropertiesInformation): void {
// Find the node that should be updated
const node = this.workflow.nodes.find(node => {
const node = this.workflow.nodes.find((node) => {
return node.name === updateInformation.name;
});
@@ -712,12 +778,14 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
setNodeValue(updateInformation: IUpdateInformation): void {
// Find the node that should be updated
const node = this.workflow.nodes.find(node => {
const node = this.workflow.nodes.find((node) => {
return node.name === updateInformation.name;
});
if (node === undefined || node === null || !updateInformation.key) {
throw new Error(`Node with the name "${updateInformation.name}" could not be found to set parameter.`);
throw new Error(
`Node with the name "${updateInformation.name}" could not be found to set parameter.`,
);
}
const uiStore = useUIStore();
@@ -727,19 +795,22 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
setNodeParameters(updateInformation: IUpdateInformation, append?: boolean): void {
// Find the node that should be updated
const node = this.workflow.nodes.find(node => {
const node = this.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.`);
throw new Error(
`Node with the name "${updateInformation.name}" could not be found to set parameter.`,
);
}
const uiStore = useUIStore();
uiStore.stateIsDirty = true;
const newParameters = !!append && isObjectLiteral(updateInformation.value)
? {...node.parameters, ...updateInformation.value }
: updateInformation.value;
const newParameters =
!!append && isObjectLiteral(updateInformation.value)
? { ...node.parameters, ...updateInformation.value }
: updateInformation.value;
Vue.set(node, 'parameters', newParameters);
@@ -750,9 +821,11 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
},
setLastNodeParameters(updateInformation: IUpdateInformation) {
const latestNode = this.workflow.nodes.findLast((node) => node.type === updateInformation.key) as INodeUi;
const latestNode = this.workflow.nodes.findLast(
(node) => node.type === updateInformation.key,
) as INodeUi;
if(latestNode) this.setNodeParameters({...updateInformation, name: latestNode.name}, true);
if (latestNode) this.setNodeParameters({ ...updateInformation, name: latestNode.name }, true);
},
addNodeExecutionData(pushData: IPushDataNodeExecuteAfter): void {
@@ -774,7 +847,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
pinDataByNodeName(nodeName: string): INodeExecutionData[] | undefined {
if (!this.workflow.pinData || !this.workflow.pinData[nodeName]) return undefined;
return this.workflow.pinData[nodeName].map(item => item.json) as INodeExecutionData[];
return this.workflow.pinData[nodeName].map((item) => item.json) as INodeExecutionData[];
},
activeNode(): INodeUi | null {
@@ -785,9 +858,9 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
// Executions actions
addActiveExecution(newActiveExecution: IExecutionsCurrentSummaryExtended) : void {
addActiveExecution(newActiveExecution: IExecutionsCurrentSummaryExtended): void {
// Check if the execution exists already
const activeExecution = this.activeExecutions.find(execution => {
const activeExecution = this.activeExecutions.find((execution) => {
return execution.id === newActiveExecution.id;
});
@@ -800,9 +873,9 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
}
this.activeExecutions.unshift(newActiveExecution);
},
finishActiveExecution(finishedActiveExecution: IPushDataExecutionFinished) : void {
finishActiveExecution(finishedActiveExecution: IPushDataExecutionFinished): void {
// Find the execution to set to finished
const activeExecution = this.activeExecutions.find(execution => {
const activeExecution = this.activeExecutions.find((execution) => {
return execution.id === finishedActiveExecution.executionId;
});
@@ -819,11 +892,14 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
Vue.set(activeExecution, 'stoppedAt', finishedActiveExecution.data.stoppedAt);
},
setActiveExecutions(newActiveExecutions: IExecutionsCurrentSummaryExtended[]) : void {
setActiveExecutions(newActiveExecutions: IExecutionsCurrentSummaryExtended[]): void {
Vue.set(this, 'activeExecutions', newActiveExecutions);
},
async loadCurrentWorkflowExecutions (filter: { finished: boolean, status: string }): Promise<IExecutionsSummary[]> {
async loadCurrentWorkflowExecutions(filter: {
finished: boolean;
status: string;
}): Promise<IExecutionsSummary[]> {
let activeExecutions = [];
let finishedExecutions = [];
const requestFilter: IDataObject = { workflowId: this.workflowId };
@@ -833,24 +909,27 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
}
try {
const rootStore = useRootStore();
if (filter.status === ''|| !filter.finished) {
if (filter.status === '' || !filter.finished) {
activeExecutions = await getCurrentExecutions(rootStore.getRestApiContext, requestFilter);
}
if (filter.status === '' || filter.finished) {
if (filter.status === 'waiting') {
requestFilter.waitTill = true;
} else if (filter.status !== '') {
} else if (filter.status !== '') {
requestFilter.finished = filter.status === 'success';
}
finishedExecutions = await getFinishedExecutions(rootStore.getRestApiContext, requestFilter);
finishedExecutions = await getFinishedExecutions(
rootStore.getRestApiContext,
requestFilter,
);
}
// context.commit('setTotalFinishedExecutionsCount', finishedExecutions.count);
return [...activeExecutions, ...finishedExecutions.results || []];
return [...activeExecutions, ...(finishedExecutions.results || [])];
} catch (error) {
throw(error);
throw error;
}
},
deleteExecution (execution: IExecutionsSummary): void {
deleteExecution(execution: IExecutionsSummary): void {
this.currentWorkflowExecutions.splice(this.currentWorkflowExecutions.indexOf(execution), 1);
},
},