fix(editor): Fix type errors for various utils files (no-changelog) (#9480)

This commit is contained in:
Alex Grozav
2024-05-22 07:54:55 +03:00
committed by GitHub
parent eef5479e96
commit 0cb977bf2f
16 changed files with 167 additions and 165 deletions

View File

@@ -130,6 +130,14 @@ export type EndpointStyle = {
hoverMessage?: string; hoverMessage?: string;
}; };
export type EndpointMeta = {
__meta?: {
index: number;
totalEndpoints: number;
endpointLabelLength: number;
};
};
export interface IUpdateInformation< export interface IUpdateInformation<
T extends NodeParameterValueType = T extends NodeParameterValueType =
| string | string

View File

@@ -14,6 +14,7 @@ import { uuid } from '@jsplumb/util';
import { defaultMockNodeTypes } from '@/__tests__/defaults'; import { defaultMockNodeTypes } from '@/__tests__/defaults';
import type { INodeUi, ITag, IUsedCredential, IWorkflowDb, WorkflowMetadata } from '@/Interface'; import type { INodeUi, ITag, IUsedCredential, IWorkflowDb, WorkflowMetadata } from '@/Interface';
import type { ProjectSharingData } from '@/features/projects/projects.types'; import type { ProjectSharingData } from '@/features/projects/projects.types';
import type { RouteLocationNormalized } from 'vue-router';
export function createTestNodeTypes(data: INodeTypeData = {}): INodeTypes { export function createTestNodeTypes(data: INodeTypeData = {}): INodeTypes {
const getResolvedKey = (key: string) => { const getResolvedKey = (key: string) => {
@@ -103,3 +104,27 @@ export function createTestNode(
...node, ...node,
}; };
} }
export function createTestRouteLocation({
path = '',
params = {},
fullPath = path,
hash = '',
matched = [],
redirectedFrom = undefined,
name = path,
meta = {},
query = {},
}: Partial<RouteLocationNormalized> = {}): RouteLocationNormalized {
return {
path,
params,
fullPath,
hash,
matched,
redirectedFrom,
name,
meta,
query,
};
}

View File

@@ -16,6 +16,7 @@ export type Resolvable = {
resolved: unknown; resolved: unknown;
state: ResolvableState; state: ResolvableState;
error: Error | null; error: Error | null;
fullError?: Error;
} & Range; } & Range;
export type Resolved = Resolvable; export type Resolved = Resolvable;

View File

@@ -1,20 +1,20 @@
import type { RouteLocationNormalized } from 'vue-router';
import { import {
inferProjectIdFromRoute, inferProjectIdFromRoute,
inferResourceTypeFromRoute, inferResourceTypeFromRoute,
inferResourceIdFromRoute, inferResourceIdFromRoute,
} from '../rbacUtils'; } from '../rbacUtils';
import { createTestRouteLocation } from '@/__tests__/mocks';
describe('rbacUtils', () => { describe('rbacUtils', () => {
describe('inferProjectIdFromRoute()', () => { describe('inferProjectIdFromRoute()', () => {
it('should infer project ID from route correctly', () => { it('should infer project ID from route correctly', () => {
const route = { path: '/dashboard/projects/123/settings' } as RouteLocationNormalized; const route = createTestRouteLocation({ path: '/dashboard/projects/123/settings' });
const projectId = inferProjectIdFromRoute(route); const projectId = inferProjectIdFromRoute(route);
expect(projectId).toBe('123'); expect(projectId).toBe('123');
}); });
it('should return undefined for project ID if not found', () => { it('should return undefined for project ID if not found', () => {
const route = { path: '/dashboard/settings' } as RouteLocationNormalized; const route = createTestRouteLocation({ path: '/dashboard/settings' });
const projectId = inferProjectIdFromRoute(route); const projectId = inferProjectIdFromRoute(route);
expect(projectId).toBeUndefined(); expect(projectId).toBeUndefined();
}); });
@@ -29,15 +29,15 @@ describe('rbacUtils', () => {
['/variables', 'variable'], ['/variables', 'variable'],
['/users', 'user'], ['/users', 'user'],
['/source-control', 'sourceControl'], ['/source-control', 'sourceControl'],
['/external-secrets', 'externalSecretsStore'], ['/external-secrets', 'externalSecret'],
])('should infer resource type from %s correctly to %s', (path, type) => { ])('should infer resource type from %s correctly to %s', (path, type) => {
const route = { path } as RouteLocationNormalized; const route = createTestRouteLocation({ path });
const resourceType = inferResourceTypeFromRoute(route); const resourceType = inferResourceTypeFromRoute(route);
expect(resourceType).toBe(type); expect(resourceType).toBe(type);
}); });
it('should return undefined for resource type if not found', () => { it('should return undefined for resource type if not found', () => {
const route = { path: '/dashboard/settings' } as RouteLocationNormalized; const route = createTestRouteLocation({ path: '/dashboard/settings' });
const resourceType = inferResourceTypeFromRoute(route); const resourceType = inferResourceTypeFromRoute(route);
expect(resourceType).toBeUndefined(); expect(resourceType).toBeUndefined();
}); });
@@ -45,19 +45,19 @@ describe('rbacUtils', () => {
describe('inferResourceIdFromRoute()', () => { describe('inferResourceIdFromRoute()', () => {
it('should infer resource ID from params.id', () => { it('should infer resource ID from params.id', () => {
const route = { params: { id: 'abc123' } } as RouteLocationNormalized; const route = createTestRouteLocation({ params: { id: 'abc123' } });
const resourceId = inferResourceIdFromRoute(route); const resourceId = inferResourceIdFromRoute(route);
expect(resourceId).toBe('abc123'); expect(resourceId).toBe('abc123');
}); });
it('should infer resource ID from params.name if id is not present', () => { it('should infer resource ID from params.name if id is not present', () => {
const route = { params: { name: 'my-resource' } } as RouteLocationNormalized; const route = createTestRouteLocation({ params: { name: 'my-resource' } });
const resourceId = inferResourceIdFromRoute(route); const resourceId = inferResourceIdFromRoute(route);
expect(resourceId).toBe('my-resource'); expect(resourceId).toBe('my-resource');
}); });
it('should return undefined for resource ID if neither id nor name is present', () => { it('should return undefined for resource ID if neither id nor name is present', () => {
const route = { params: {} } as RouteLocationNormalized; const route = createTestRouteLocation({ params: {} });
const resourceId = inferResourceIdFromRoute(route); const resourceId = inferResourceIdFromRoute(route);
expect(resourceId).toBeUndefined(); expect(resourceId).toBeUndefined();
}); });

View File

@@ -1,56 +0,0 @@
import type { SourceControlStatus } from '@/Interface';
import { beforeEach } from 'vitest';
import { aggregateSourceControlFiles } from '@/utils/sourceControlUtils';
describe('sourceControlUtils', () => {
describe('aggregateSourceControlFiles()', () => {
let status: SourceControlStatus;
beforeEach(() => {
status = {
ahead: 0,
behind: 0,
conflicted: [],
created: [],
current: 'main',
deleted: [],
detached: false,
files: [],
modified: [],
not_added: [],
renamed: [],
staged: [],
tracking: null,
};
});
it('should be empty array if no files', () => {
expect(aggregateSourceControlFiles(status)).toEqual([]);
});
it('should contain list of conflicted, created, deleted, modified, and renamed files', () => {
status.files = [
{ path: 'conflicted.json', index: 'A', working_dir: '' },
{ path: 'created.json', index: 'A', working_dir: '' },
{ path: 'deleted.json', index: 'A', working_dir: '' },
{ path: 'modified.json', index: 'A', working_dir: '' },
{ path: 'renamed.json', index: 'A', working_dir: '' },
];
status.conflicted.push('conflicted.json');
status.created.push('created.json');
status.deleted.push('deleted.json');
status.modified.push('modified.json');
status.renamed.push('renamed.json');
status.staged = status.files.map((file) => file.path);
expect(aggregateSourceControlFiles(status)).toEqual([
{ path: 'conflicted.json', status: 'conflicted', staged: true },
{ path: 'created.json', status: 'created', staged: true },
{ path: 'deleted.json', status: 'deleted', staged: true },
{ path: 'modified.json', status: 'modified', staged: true },
{ path: 'renamed.json', status: 'renamed', staged: true },
]);
});
});
});

View File

@@ -3,7 +3,7 @@ import type { IZoomConfig } from '@/Interface';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
import type { ConnectionDetachedParams } from '@jsplumb/core'; import type { ConnectionDetachedParams } from '@jsplumb/core';
import type { IConnection } from 'n8n-workflow'; import type { IConnection } from 'n8n-workflow';
import type { Route } from 'vue-router'; import type { RouteLocation } from 'vue-router';
/* /*
Constants and utility functions mainly used by canvas store Constants and utility functions mainly used by canvas store
@@ -52,7 +52,7 @@ export const scaleReset = (config: IZoomConfig): IZoomConfig => {
return applyScale(1 / config.scale)(config); return applyScale(1 / config.scale)(config);
}; };
export const getNodeViewTab = (route: Route): string | null => { export const getNodeViewTab = (route: RouteLocation): string | null => {
if (route.meta?.nodeView) { if (route.meta?.nodeView) {
return MAIN_HEADER_TABS.WORKFLOW; return MAIN_HEADER_TABS.WORKFLOW;
} else if ( } else if (

View File

@@ -143,8 +143,8 @@ export const getMainAuthField = (nodeType: INodeTypeDescription | null): INodePr
credentialDependencies.find( credentialDependencies.find(
(prop) => (prop) =>
prop.name === MAIN_AUTH_FIELD_NAME && prop.name === MAIN_AUTH_FIELD_NAME &&
!prop.options?.find((option) => option.value === 'none'), !prop.options?.find((option) => 'value' in option && option.value === 'none'),
) || null; ) ?? null;
// If there is a field name `authentication`, use it // If there is a field name `authentication`, use it
// Otherwise, try to find alternative main auth field // Otherwise, try to find alternative main auth field
const mainAuthFiled = const mainAuthFiled =
@@ -166,7 +166,7 @@ const findAlternativeAuthField = (
if (cred.displayOptions?.show) { if (cred.displayOptions?.show) {
for (const fieldName in cred.displayOptions.show) { for (const fieldName in cred.displayOptions.show) {
dependentAuthFieldValues[fieldName] = (dependentAuthFieldValues[fieldName] || []).concat( dependentAuthFieldValues[fieldName] = (dependentAuthFieldValues[fieldName] || []).concat(
(cred.displayOptions.show[fieldName] || []).map((val) => (val ? val.toString() : '')), (cred.displayOptions.show[fieldName] ?? []).map((val) => (val ? val.toString() : '')),
); );
} }
} }
@@ -174,7 +174,11 @@ const findAlternativeAuthField = (
const alternativeAuthField = fields.find((field) => { const alternativeAuthField = fields.find((field) => {
let required = true; let required = true;
field.options?.forEach((option) => { field.options?.forEach((option) => {
if (!dependentAuthFieldValues[field.name].includes(option.value)) { if (
'value' in option &&
typeof option.value === 'string' &&
!dependentAuthFieldValues[field.name].includes(option.value)
) {
required = false; required = false;
} }
}); });
@@ -206,21 +210,24 @@ export const getNodeAuthOptions = (
if (field.options) { if (field.options) {
options = options.concat( options = options.concat(
field.options.map((option) => { field.options.map((option) => {
const optionValue = 'value' in option ? `${option.value}` : '';
// Check if credential type associated with this auth option has overwritten properties // Check if credential type associated with this auth option has overwritten properties
let hasOverrides = false; let hasOverrides = false;
const cred = getNodeCredentialForSelectedAuthType(nodeType, option.value); const cred = getNodeCredentialForSelectedAuthType(nodeType, optionValue);
if (cred) { if (cred) {
hasOverrides = hasOverrides =
useCredentialsStore().getCredentialTypeByName(cred.name)?.__overwrittenProperties !== useCredentialsStore().getCredentialTypeByName(cred.name)?.__overwrittenProperties !==
undefined; undefined;
} }
return { return {
name: name:
// Add recommended suffix if credentials have overrides and option is not already recommended // Add recommended suffix if credentials have overrides and option is not already recommended
hasOverrides && !option.name.endsWith(recommendedSuffix) hasOverrides && !option.name.endsWith(recommendedSuffix)
? `${option.name} ${recommendedSuffix}` ? `${option.name} ${recommendedSuffix}`
: option.name, : option.name,
value: option.value, value: optionValue,
// Also add in the display options so we can hide/show the option if necessary // Also add in the display options so we can hide/show the option if necessary
displayOptions: field.displayOptions, displayOptions: field.displayOptions,
}; };

View File

@@ -1,16 +1,16 @@
import { isNumber } from '@/utils/typeGuards'; import { isNumber, isValidNodeConnectionType } from '@/utils/typeGuards';
import { NODE_OUTPUT_DEFAULT_KEY, STICKY_NODE_TYPE } from '@/constants'; import { NODE_OUTPUT_DEFAULT_KEY, STICKY_NODE_TYPE } from '@/constants';
import type { EndpointStyle, IBounds, INodeUi, XYPosition } from '@/Interface'; import type { EndpointMeta, EndpointStyle, IBounds, INodeUi, XYPosition } from '@/Interface';
import type { ArrayAnchorSpec, ConnectorSpec, OverlaySpec, PaintStyle } from '@jsplumb/common'; import type { ArrayAnchorSpec, ConnectorSpec, OverlaySpec, PaintStyle } from '@jsplumb/common';
import type { Endpoint, Connection } from '@jsplumb/core'; import type { Connection, Endpoint, SelectOptions } from '@jsplumb/core';
import { N8nConnector } from '@/plugins/connectors/N8nCustomConnector'; import { N8nConnector } from '@/plugins/connectors/N8nCustomConnector';
import type { import type {
ConnectionTypes, ConnectionTypes,
IConnection, IConnection,
ITaskData,
INodeExecutionData, INodeExecutionData,
NodeInputConnections,
INodeTypeDescription, INodeTypeDescription,
ITaskData,
NodeInputConnections,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow'; import { NodeConnectionType } from 'n8n-workflow';
import type { BrowserJsPlumbInstance } from '@jsplumb/browser-ui'; import type { BrowserJsPlumbInstance } from '@jsplumb/browser-ui';
@@ -72,13 +72,15 @@ export const CONNECTOR_FLOWCHART_TYPE: ConnectorSpec = {
alwaysRespectStubs: false, alwaysRespectStubs: false,
loopbackVerticalLength: NODE_SIZE + GRID_SIZE, // height of vertical segment when looping loopbackVerticalLength: NODE_SIZE + GRID_SIZE, // height of vertical segment when looping
loopbackMinimum: LOOPBACK_MINIMUM, // minimum length before flowchart loops around loopbackMinimum: LOOPBACK_MINIMUM, // minimum length before flowchart loops around
getEndpointOffset(endpoint: Endpoint) { getEndpointOffset(endpoint: Endpoint & EndpointMeta) {
const indexOffset = 10; // stub offset between different endpoints of same node const indexOffset = 10; // stub offset between different endpoints of same node
const index = endpoint?.__meta ? endpoint.__meta.index : 0; const index = endpoint?.__meta ? endpoint.__meta.index : 0;
const totalEndpoints = endpoint?.__meta ? endpoint.__meta.totalEndpoints : 0; const totalEndpoints = endpoint?.__meta ? endpoint.__meta.totalEndpoints : 0;
const outputOverlay = getOverlay(endpoint, OVERLAY_OUTPUT_NAME_LABEL); const outputOverlay = getOverlay(endpoint, OVERLAY_OUTPUT_NAME_LABEL);
const labelOffset = outputOverlay?.label && outputOverlay.label.length > 1 ? 10 : 0; const outputOverlayLabel =
outputOverlay && 'label' in outputOverlay ? `${outputOverlay?.label}` : '';
const labelOffset = outputOverlayLabel.length > 1 ? 10 : 0;
const outputsOffset = totalEndpoints > 3 ? 24 : 0; // avoid intersecting plus const outputsOffset = totalEndpoints > 3 ? 24 : 0; // avoid intersecting plus
return index * indexOffset + labelOffset + outputsOffset; return index * indexOffset + labelOffset + outputsOffset;
@@ -111,6 +113,10 @@ export const CONNECTOR_PAINT_STYLE_DATA: PaintStyle = {
stroke: 'var(--color-foreground-dark)', stroke: 'var(--color-foreground-dark)',
}; };
export function isCanvasAugmentedType<T>(overlay: T): overlay is T & { canvas: HTMLElement } {
return typeof overlay === 'object' && overlay !== null && 'canvas' in overlay && !!overlay.canvas;
}
export const getConnectorColor = (type: ConnectionTypes, category?: string): string => { export const getConnectorColor = (type: ConnectionTypes, category?: string): string => {
if (category === 'error') { if (category === 'error') {
return '--color-node-error-output-text-color'; return '--color-node-error-output-text-color';
@@ -228,15 +234,18 @@ export const getAnchorPosition = (
return returnPositions; return returnPositions;
}; };
export const getScope = (type?: string) => { export const getScope = (type?: NodeConnectionType): NodeConnectionType | undefined => {
if (!type || type === NodeConnectionType.Main) { if (!type || type === NodeConnectionType.Main) {
return undefined; return undefined;
} }
return type; return type;
}; };
export const getEndpointScope = (endpointType: ConnectionTypes): string | undefined => { export const getEndpointScope = (
if (Object.values(NodeConnectionType).includes(endpointType)) { endpointType: NodeConnectionType | string,
): NodeConnectionType | undefined => {
if (isValidNodeConnectionType(endpointType)) {
return getScope(endpointType); return getScope(endpointType);
} }
@@ -276,7 +285,7 @@ export const getInputNameOverlay = (
id: OVERLAY_INPUT_NAME_LABEL, id: OVERLAY_INPUT_NAME_LABEL,
visible: true, visible: true,
location: [-1, -1], location: [-1, -1],
create: (component: Endpoint) => { create: (_: Endpoint) => {
const label = document.createElement('div'); const label = document.createElement('div');
label.innerHTML = labelText; label.innerHTML = labelText;
if (required) { if (required) {
@@ -303,20 +312,20 @@ export const getOutputEndpointStyle = (
export const getOutputNameOverlay = ( export const getOutputNameOverlay = (
labelText: string, labelText: string,
outputName: ConnectionTypes, outputName: NodeConnectionType,
category?: string, category?: string,
): OverlaySpec => ({ ): OverlaySpec => ({
type: 'Custom', type: 'Custom',
options: { options: {
id: OVERLAY_OUTPUT_NAME_LABEL, id: OVERLAY_OUTPUT_NAME_LABEL,
visible: true, visible: true,
create: (ep: Endpoint) => { create: (ep: Endpoint & EndpointMeta) => {
const label = document.createElement('div'); const label = document.createElement('div');
label.innerHTML = labelText; label.innerHTML = labelText;
label.classList.add('node-output-endpoint-label'); label.classList.add('node-output-endpoint-label');
if (ep?.__meta?.endpointLabelLength) { if (ep?.__meta?.endpointLabelLength) {
label.setAttribute('data-endpoint-label-length', ep?.__meta?.endpointLabelLength); label.setAttribute('data-endpoint-label-length', `${ep?.__meta?.endpointLabelLength}`);
} }
label.classList.add(`node-connection-type-${getScope(outputName) ?? 'main'}`); label.classList.add(`node-connection-type-${getScope(outputName) ?? 'main'}`);
if (outputName !== NodeConnectionType.Main) { if (outputName !== NodeConnectionType.Main) {
@@ -438,7 +447,10 @@ export const showOrHideMidpointArrow = (connection: Connection) => {
if (arrow) { if (arrow) {
arrow.setVisible(isArrowVisible); arrow.setVisible(isArrowVisible);
arrow.setLocation(hasItemsLabel ? 0.6 : 0.5); arrow.setLocation(hasItemsLabel ? 0.6 : 0.5);
connection.instance.repaint(arrow.canvas);
if (isCanvasAugmentedType(arrow)) {
connection.instance.repaint(arrow.canvas);
}
} }
}; };
@@ -481,7 +493,9 @@ export const showOrHideItemsLabel = (connection: Connection) => {
const isHidden = diffX < MIN_X_TO_SHOW_OUTPUT_LABEL && diffY < MIN_Y_TO_SHOW_OUTPUT_LABEL; const isHidden = diffX < MIN_X_TO_SHOW_OUTPUT_LABEL && diffY < MIN_Y_TO_SHOW_OUTPUT_LABEL;
overlay.setVisible(!isHidden); overlay.setVisible(!isHidden);
const innerElement = overlay.canvas?.querySelector('span'); const innerElement = isCanvasAugmentedType(overlay)
? overlay.canvas.querySelector('span')
: undefined;
if (innerElement) { if (innerElement) {
if (diffY === 0 || isLoopingBackwards(connection)) { if (diffY === 0 || isLoopingBackwards(connection)) {
innerElement.classList.add('floating'); innerElement.classList.add('floating');
@@ -812,8 +826,11 @@ export const addClassesToOverlays = ({
overlayIds.forEach((overlayId) => { overlayIds.forEach((overlayId) => {
const overlay = getOverlay(connection, overlayId); const overlay = getOverlay(connection, overlayId);
overlay?.canvas?.classList.add(...classNames); if (overlay && isCanvasAugmentedType(overlay)) {
if (includeConnector) { overlay.canvas?.classList.add(...classNames);
}
if (includeConnector && isCanvasAugmentedType(connection.connector)) {
connection.connector.canvas?.classList.add(...classNames); connection.connector.canvas?.classList.add(...classNames);
} }
}); });
@@ -995,18 +1012,18 @@ export const addConnectionActionsOverlay = (
export const getOutputEndpointUUID = ( export const getOutputEndpointUUID = (
nodeId: string, nodeId: string,
connectionType: ConnectionTypes, connectionType: NodeConnectionType,
outputIndex: number, outputIndex: number,
) => { ) => {
return `${nodeId}${OUTPUT_UUID_KEY}${getScope(connectionType) || ''}${outputIndex}`; return `${nodeId}${OUTPUT_UUID_KEY}${getScope(connectionType) ?? ''}${outputIndex}`;
}; };
export const getInputEndpointUUID = ( export const getInputEndpointUUID = (
nodeId: string, nodeId: string,
connectionType: ConnectionTypes, connectionType: NodeConnectionType,
inputIndex: number, inputIndex: number,
) => { ) => {
return `${nodeId}${INPUT_UUID_KEY}${getScope(connectionType) || ''}${inputIndex}`; return `${nodeId}${INPUT_UUID_KEY}${getScope(connectionType) ?? ''}${inputIndex}`;
}; };
export const getFixedNodesList = <T extends { position: XYPosition }>(workflowNodes: T[]): T[] => { export const getFixedNodesList = <T extends { position: XYPosition }>(workflowNodes: T[]): T[] => {
@@ -1089,10 +1106,10 @@ export const getJSPlumbEndpoints = (
node: INodeUi | null, node: INodeUi | null,
instance: BrowserJsPlumbInstance, instance: BrowserJsPlumbInstance,
): Endpoint[] => { ): Endpoint[] => {
const nodeEl = instance.getManagedElement(node?.id); if (!node) return [];
const endpoints = instance?.getEndpoints(nodeEl); const nodeEl = instance.getManagedElement(node?.id);
return endpoints; return instance?.getEndpoints(nodeEl);
}; };
export const getPlusEndpoint = ( export const getPlusEndpoint = (
@@ -1102,8 +1119,7 @@ export const getPlusEndpoint = (
): Endpoint | undefined => { ): Endpoint | undefined => {
const endpoints = getJSPlumbEndpoints(node, instance); const endpoints = getJSPlumbEndpoints(node, instance);
return endpoints.find( return endpoints.find(
(endpoint: Endpoint) => (endpoint: Endpoint & EndpointMeta) =>
// @ts-ignore
endpoint.endpoint.type === 'N8nPlus' && endpoint?.__meta?.index === outputIndex, endpoint.endpoint.type === 'N8nPlus' && endpoint?.__meta?.index === outputIndex,
); );
}; };
@@ -1113,7 +1129,7 @@ export const getJSPlumbConnection = (
sourceOutputIndex: number, sourceOutputIndex: number,
targetNode: INodeUi | null, targetNode: INodeUi | null,
targetInputIndex: number, targetInputIndex: number,
connectionType: ConnectionTypes, connectionType: NodeConnectionType,
sourceNodeType: INodeTypeDescription | null, sourceNodeType: INodeTypeDescription | null,
instance: BrowserJsPlumbInstance, instance: BrowserJsPlumbInstance,
): Connection | undefined => { ): Connection | undefined => {
@@ -1127,17 +1143,24 @@ export const getJSPlumbConnection = (
const sourceEndpoint = getOutputEndpointUUID(sourceId, connectionType, sourceOutputIndex); const sourceEndpoint = getOutputEndpointUUID(sourceId, connectionType, sourceOutputIndex);
const targetEndpoint = getInputEndpointUUID(targetId, connectionType, targetInputIndex); const targetEndpoint = getInputEndpointUUID(targetId, connectionType, targetInputIndex);
const sourceNodeOutput = sourceNodeType?.outputs?.[sourceOutputIndex] || NodeConnectionType.Main; const sourceNodeOutput = sourceNodeType?.outputs?.[sourceOutputIndex] ?? NodeConnectionType.Main;
const sourceNodeOutputName = const sourceNodeOutputName =
typeof sourceNodeOutput === 'string' ? sourceNodeOutput : sourceNodeOutput.name; typeof sourceNodeOutput === 'string'
? sourceNodeOutput
: 'name' in sourceNodeOutput
? `${sourceNodeOutput.name}`
: '';
const scope = getEndpointScope(sourceNodeOutputName); const scope = getEndpointScope(sourceNodeOutputName);
// @ts-ignore
const connections = instance?.getConnections({ const connections = instance?.getConnections({
scope, scope,
source: sourceId, source: sourceId,
target: targetId, target: targetId,
}) as Connection[]; } as SelectOptions<Element>);
if (!Array.isArray(connections)) {
return;
}
return connections.find((connection: Connection) => { return connections.find((connection: Connection) => {
const uuids = connection.getUuids(); const uuids = connection.getUuids();

View File

@@ -11,20 +11,24 @@ export function inferProjectIdFromRoute(to: RouteLocationNormalized): string {
export function inferResourceTypeFromRoute(to: RouteLocationNormalized): Resource | undefined { export function inferResourceTypeFromRoute(to: RouteLocationNormalized): Resource | undefined {
const routeParts = to.path.split('/'); const routeParts = to.path.split('/');
const routeMap = { const routeMap: Record<string, string> = {
workflow: 'workflows', workflow: 'workflows',
credential: 'credentials', credential: 'credentials',
user: 'users', user: 'users',
variable: 'variables', variable: 'variables',
sourceControl: 'source-control', sourceControl: 'source-control',
externalSecretsStore: 'external-secrets', externalSecret: 'external-secrets',
}; };
for (const resource of Object.keys(routeMap) as Array<keyof typeof routeMap>) { const isResource = (key: string): key is Resource => routeParts.includes(routeMap[key]);
if (routeParts.includes(routeMap[resource])) {
for (const resource of Object.keys(routeMap)) {
if (isResource(resource)) {
return resource; return resource;
} }
} }
return undefined;
} }
export function inferResourceIdFromRoute(to: RouteLocationNormalized): string | undefined { export function inferResourceIdFromRoute(to: RouteLocationNormalized): string | undefined {

View File

@@ -1,27 +0,0 @@
import type { SourceControlAggregatedFile, SourceControlStatus } from '@/Interface';
export function aggregateSourceControlFiles(sourceControlStatus: SourceControlStatus) {
return sourceControlStatus.files.reduce<SourceControlAggregatedFile[]>((acc, file) => {
const staged = sourceControlStatus.staged.includes(file.path);
let status = '';
(
['conflicted', 'created', 'deleted', 'modified', 'renamed'] as Array<
keyof SourceControlStatus
>
).forEach((key) => {
const filesForStatus = sourceControlStatus[key] as string[];
if (filesForStatus.includes(file.path)) {
status = key;
}
});
acc.push({
path: file.path,
status,
staged,
});
return acc;
}, []);
}

View File

@@ -26,12 +26,14 @@ export function createExpressionTelemetryPayload(
handlebar_error_count: erroringResolvables.length, handlebar_error_count: erroringResolvables.length,
short_errors: erroringResolvables.map((r) => r.resolved ?? null), short_errors: erroringResolvables.map((r) => r.resolved ?? null),
full_errors: erroringResolvables.map((erroringResolvable) => { full_errors: erroringResolvables.map((erroringResolvable) => {
if (!erroringResolvable.fullError) return null; if (erroringResolvable.fullError) {
return {
...exposeErrorProperties(erroringResolvable.fullError),
stack: erroringResolvable.fullError.stack,
};
}
return { return null;
...exposeErrorProperties(erroringResolvable.fullError),
stack: erroringResolvable.fullError.stack,
};
}), }),
}; };
} }

View File

@@ -295,17 +295,14 @@ export const fullSaveEmailAttachmentsToNextCloudTemplate = {
export const fullCreateApiEndpointTemplate = { export const fullCreateApiEndpointTemplate = {
id: 1750, id: 1750,
name: 'Creating an API endpoint', name: 'Creating an API endpoint',
views: 13265,
recentViews: 9899, recentViews: 9899,
totalViews: 13265, totalViews: 13265,
createdAt: '2022-07-06T14:45:19.659Z', createdAt: '2022-07-06T14:45:19.659Z',
description: description:
'**Task:**\nCreate a simple API endpoint using the Webhook and Respond to Webhook nodes\n\n**Why:**\nYou can prototype or replace a backend process with a single workflow\n\n**Main use cases:**\nReplace backend logic with a workflow', '**Task:**\nCreate a simple API endpoint using the Webhook and Respond to Webhook nodes\n\n**Why:**\nYou can prototype or replace a backend process with a single workflow\n\n**Main use cases:**\nReplace backend logic with a workflow',
workflow: { workflow: {
meta: { instanceId: '8c8c5237b8e37b006a7adce87f4369350c58e41f3ca9de16196d3197f69eabcd' },
nodes: [ nodes: [
{ {
id: 'f80aceed-b676-42aa-bf25-f7a44408b1bc',
name: 'Webhook', name: 'Webhook',
type: 'n8n-nodes-base.webhook', type: 'n8n-nodes-base.webhook',
position: [375, 115], position: [375, 115],
@@ -318,7 +315,6 @@ export const fullCreateApiEndpointTemplate = {
typeVersion: 1, typeVersion: 1,
}, },
{ {
id: '3b9ec913-0bbe-4906-bf8e-da352b556655',
name: 'Note1', name: 'Note1',
type: 'n8n-nodes-base.stickyNote', type: 'n8n-nodes-base.stickyNote',
position: [355, -25], position: [355, -25],
@@ -331,7 +327,6 @@ export const fullCreateApiEndpointTemplate = {
typeVersion: 1, typeVersion: 1,
}, },
{ {
id: '9c36dae5-0700-450c-9739-e9f3eff31bfe',
name: 'Respond to Webhook', name: 'Respond to Webhook',
type: 'n8n-nodes-base.respondToWebhook', type: 'n8n-nodes-base.respondToWebhook',
position: [815, 115], position: [815, 115],
@@ -344,7 +339,6 @@ export const fullCreateApiEndpointTemplate = {
typeVersion: 1, typeVersion: 1,
}, },
{ {
id: '5a228fcb-78b9-4a28-95d2-d7c9fdf1d4ea',
name: 'Create URL string', name: 'Create URL string',
type: 'n8n-nodes-base.set', type: 'n8n-nodes-base.set',
position: [595, 115], position: [595, 115],
@@ -364,7 +358,6 @@ export const fullCreateApiEndpointTemplate = {
typeVersion: 1, typeVersion: 1,
}, },
{ {
id: 'e7971820-45a8-4dc8-ba4c-b3220d65307a',
name: 'Note3', name: 'Note3',
type: 'n8n-nodes-base.stickyNote', type: 'n8n-nodes-base.stickyNote',
position: [355, 275], position: [355, 275],
@@ -383,7 +376,10 @@ export const fullCreateApiEndpointTemplate = {
}, },
}, },
lastUpdatedBy: 1, lastUpdatedBy: 1,
workflowInfo: null, workflowInfo: {
nodeCount: 2,
nodeTypes: {},
},
user: { username: 'jon-n8n' }, user: { username: 'jon-n8n' },
nodes: [ nodes: [
{ {

View File

@@ -1,4 +1,5 @@
import type { INodeParameterResourceLocator } from 'n8n-workflow'; import type { INodeParameterResourceLocator, NodeConnectionType } from 'n8n-workflow';
import { nodeConnectionTypes } from 'n8n-workflow';
import type { ICredentialsResponse, NewCredentialsModal } from '@/Interface'; import type { ICredentialsResponse, NewCredentialsModal } from '@/Interface';
/* /*
@@ -49,3 +50,9 @@ export function isDateObject(date: unknown): date is Date {
!!date && Object.prototype.toString.call(date) === '[object Date]' && !isNaN(date as number) !!date && Object.prototype.toString.call(date) === '[object Date]' && !isNaN(date as number)
); );
} }
export function isValidNodeConnectionType(
connectionType: string,
): connectionType is NodeConnectionType {
return nodeConnectionTypes.includes(connectionType as NodeConnectionType);
}

View File

@@ -77,20 +77,17 @@ export function shorten(s: string, limit: number, keep: number) {
export const convertPath = (path: string): string => { export const convertPath = (path: string): string => {
// TODO: That can for sure be done fancier but for now it works // TODO: That can for sure be done fancier but for now it works
const placeholder = '*___~#^#~___*'; const placeholder = '*___~#^#~___*';
let inBrackets = path.match(/\[(.*?)]/g); let inBrackets: string[] = path.match(/\[(.*?)]/g) ?? [];
inBrackets = inBrackets
.map((item) => item.slice(1, -1))
.map((item) => {
if (item.startsWith('"') && item.endsWith('"')) {
return item.slice(1, -1);
}
return item;
});
if (inBrackets === null) {
inBrackets = [];
} else {
inBrackets = inBrackets
.map((item) => item.slice(1, -1))
.map((item) => {
if (item.startsWith('"') && item.endsWith('"')) {
return item.slice(1, -1);
}
return item;
});
}
const withoutBrackets = path.replace(/\[(.*?)]/g, placeholder); const withoutBrackets = path.replace(/\[(.*?)]/g, placeholder);
const pathParts = withoutBrackets.split('.'); const pathParts = withoutBrackets.split('.');
const allParts = [] as string[]; const allParts = [] as string[];
@@ -98,7 +95,7 @@ export const convertPath = (path: string): string => {
let index = part.indexOf(placeholder); let index = part.indexOf(placeholder);
while (index !== -1) { while (index !== -1) {
if (index === 0) { if (index === 0) {
allParts.push(inBrackets!.shift() as string); allParts.push(inBrackets.shift() ?? '');
part = part.substr(placeholder.length); part = part.substr(placeholder.length);
} else { } else {
allParts.push(part.substr(0, index)); allParts.push(part.substr(0, index));

View File

@@ -135,7 +135,7 @@ function getPersonalizationSurveyV2OrLater(
const companySize = answers[COMPANY_SIZE_KEY]; const companySize = answers[COMPANY_SIZE_KEY];
const companyType = answers[COMPANY_TYPE_KEY]; const companyType = answers[COMPANY_TYPE_KEY];
const automationGoal = answers[AUTOMATION_GOAL_KEY]; const automationGoal = AUTOMATION_GOAL_KEY in answers ? answers[AUTOMATION_GOAL_KEY] : undefined;
let codingSkill = null; let codingSkill = null;
if (CODING_SKILL_KEY in answers && answers[CODING_SKILL_KEY]) { if (CODING_SKILL_KEY in answers && answers[CODING_SKILL_KEY]) {

View File

@@ -1704,6 +1704,21 @@ export const enum NodeConnectionType {
Main = 'main', Main = 'main',
} }
export const nodeConnectionTypes: NodeConnectionType[] = [
NodeConnectionType.AiAgent,
NodeConnectionType.AiChain,
NodeConnectionType.AiDocument,
NodeConnectionType.AiEmbedding,
NodeConnectionType.AiLanguageModel,
NodeConnectionType.AiMemory,
NodeConnectionType.AiOutputParser,
NodeConnectionType.AiRetriever,
NodeConnectionType.AiTextSplitter,
NodeConnectionType.AiTool,
NodeConnectionType.AiVectorStore,
NodeConnectionType.Main,
];
export interface INodeInputFilter { export interface INodeInputFilter {
// TODO: Later add more filter options like categories, subcatogries, // TODO: Later add more filter options like categories, subcatogries,
// regex, allow to exclude certain nodes, ... ? // regex, allow to exclude certain nodes, ... ?