Skip to content

Commit

Permalink
feat(core): Improve paired item and add additional variables (#3765)
Browse files Browse the repository at this point in the history
* ⚡ Remove duplicate and old string

* ⚡ Add telemetry

* ⚡ Futher improvements

* ⚡ Change error message and display only name of last parameter

* 👕 Fix lint issue

* ⚡ Remove not needed comments

* ⚡ Rename properties, add new ones and improve error messages

* ⚡ Add support for $execution, $prevNode and make it possible to use proxies as object

* ⚡ Some small improvements

* 🐛 Fix error message

* ⚡ Improve some error messages

* ⚡ Change resumeUrl variable and display in editor

* ⚡ Fix and extend tests

* ⚡ Multiple pairedItem improvements

* ⚡ Display "More Info" link with error messages if user can fix issue

* ⚡ Display different errors in Function Nodes
  • Loading branch information
janober authored Sep 29, 2022
1 parent 737cbf9 commit 5526057
Show file tree
Hide file tree
Showing 15 changed files with 681 additions and 298 deletions.
10 changes: 8 additions & 2 deletions packages/cli/src/InternalHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,14 @@ export class InternalHooksClass implements IInternalHooksClass {

if (!properties.success && runData?.data.resultData.error) {
properties.error_message = runData?.data.resultData.error.message;
let errorNodeName = runData?.data.resultData.error.node?.name;
properties.error_node_type = runData?.data.resultData.error.node?.type;
let errorNodeName =
'node' in runData?.data.resultData.error
? runData?.data.resultData.error.node?.name
: undefined;
properties.error_node_type =
'node' in runData?.data.resultData.error
? runData?.data.resultData.error.node?.type
: undefined;

if (runData.data.resultData.lastNodeExecuted) {
const lastNode = TelemetryHelpers.getNodeTypeForName(
Expand Down
71 changes: 40 additions & 31 deletions packages/core/src/NodeExecuteFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1487,11 +1487,20 @@ export async function requestWithAuthentication(
*/
export function getAdditionalKeys(
additionalData: IWorkflowExecuteAdditionalData,
mode: WorkflowExecuteMode,
): IWorkflowDataProxyAdditionalKeys {
const executionId = additionalData.executionId || PLACEHOLDER_EMPTY_EXECUTION_ID;
const resumeUrl = `${additionalData.webhookWaitingBaseUrl}/${executionId}`;
return {
$execution: {
id: executionId,
mode: mode === 'manual' ? 'test' : 'production',
resumeUrl,
},

// deprecated
$executionId: executionId,
$resumeWebhookUrl: `${additionalData.webhookWaitingBaseUrl}/${executionId}`,
$resumeWebhookUrl: resumeUrl,
};
}

Expand Down Expand Up @@ -1601,7 +1610,7 @@ export async function getCredentials(
// TODO: solve using credentials via expression
// if (name.charAt(0) === '=') {
// // If the credential name is an expression resolve it
// const additionalKeys = getAdditionalKeys(additionalData);
// const additionalKeys = getAdditionalKeys(additionalData, mode);
// name = workflow.expression.getParameterValue(
// name,
// runExecutionData || null,
Expand Down Expand Up @@ -1638,30 +1647,29 @@ export function getNode(node: INode): INode {
* Clean up parameter data to make sure that only valid data gets returned
* INFO: Currently only converts Luxon Dates as we know for sure it will not be breaking
*/
function cleanupParameterData(inputData: NodeParameterValueType): NodeParameterValueType {
if (inputData === null || inputData === undefined) {
return inputData;
function cleanupParameterData(inputData: NodeParameterValueType): void {
if (typeof inputData !== 'object' || inputData === null) {
return;
}

if (Array.isArray(inputData)) {
inputData.forEach((value) => cleanupParameterData(value));
return inputData;
}

if (inputData.constructor.name === 'DateTime') {
// Is a special luxon date so convert to string
return inputData.toString();
return;
}

if (typeof inputData === 'object') {
Object.keys(inputData).forEach((key) => {
inputData[key as keyof typeof inputData] = cleanupParameterData(
inputData[key as keyof typeof inputData],
);
if (typeof inputData[key as keyof typeof inputData] === 'object') {
if (inputData[key as keyof typeof inputData]?.constructor.name === 'DateTime') {
// Is a special luxon date so convert to string
inputData[key as keyof typeof inputData] =
inputData[key as keyof typeof inputData]?.toString();
} else {
cleanupParameterData(inputData[key as keyof typeof inputData]);
}
}
});
}

return inputData;
}

/**
Expand Down Expand Up @@ -1710,7 +1718,7 @@ export function getNodeParameter(
executeData,
);

returnData = cleanupParameterData(returnData);
cleanupParameterData(returnData);
} catch (e) {
if (e.context) e.context.parameter = parameterName;
e.cause = value;
Expand Down Expand Up @@ -1883,7 +1891,7 @@ export function getExecutePollFunctions(
itemIndex,
mode,
additionalData.timezone,
getAdditionalKeys(additionalData),
getAdditionalKeys(additionalData, mode),
undefined,
fallbackValue,
options,
Expand Down Expand Up @@ -2032,7 +2040,7 @@ export function getExecuteTriggerFunctions(
itemIndex,
mode,
additionalData.timezone,
getAdditionalKeys(additionalData),
getAdditionalKeys(additionalData, mode),
undefined,
fallbackValue,
options,
Expand Down Expand Up @@ -2160,7 +2168,7 @@ export function getExecuteFunctions(
connectionInputData,
mode,
additionalData.timezone,
getAdditionalKeys(additionalData),
getAdditionalKeys(additionalData, mode),
executeData,
);
},
Expand Down Expand Up @@ -2237,7 +2245,7 @@ export function getExecuteFunctions(
itemIndex,
mode,
additionalData.timezone,
getAdditionalKeys(additionalData),
getAdditionalKeys(additionalData, mode),
executeData,
fallbackValue,
options,
Expand Down Expand Up @@ -2272,7 +2280,7 @@ export function getExecuteFunctions(
{},
mode,
additionalData.timezone,
getAdditionalKeys(additionalData),
getAdditionalKeys(additionalData, mode),
executeData,
);
return dataProxy.getDataProxy();
Expand Down Expand Up @@ -2421,7 +2429,7 @@ export function getExecuteSingleFunctions(
connectionInputData,
mode,
additionalData.timezone,
getAdditionalKeys(additionalData),
getAdditionalKeys(additionalData, mode),
executeData,
);
},
Expand Down Expand Up @@ -2501,7 +2509,7 @@ export function getExecuteSingleFunctions(
itemIndex,
mode,
additionalData.timezone,
getAdditionalKeys(additionalData),
getAdditionalKeys(additionalData, mode),
executeData,
fallbackValue,
options,
Expand All @@ -2521,7 +2529,7 @@ export function getExecuteSingleFunctions(
{},
mode,
additionalData.timezone,
getAdditionalKeys(additionalData),
getAdditionalKeys(additionalData, mode),
executeData,
);
return dataProxy.getDataProxy();
Expand Down Expand Up @@ -2658,6 +2666,7 @@ export function getLoadOptionsFunctions(
const runExecutionData: IRunExecutionData | null = null;
const itemIndex = 0;
const runIndex = 0;
const mode = 'internal' as WorkflowExecuteMode;
const connectionInputData: INodeExecutionData[] = [];

return getNodeParameter(
Expand All @@ -2668,9 +2677,9 @@ export function getLoadOptionsFunctions(
node,
parameterName,
itemIndex,
'internal' as WorkflowExecuteMode,
mode,
additionalData.timezone,
getAdditionalKeys(additionalData),
getAdditionalKeys(additionalData, mode),
undefined,
fallbackValue,
options,
Expand Down Expand Up @@ -2792,7 +2801,7 @@ export function getExecuteHookFunctions(
itemIndex,
mode,
additionalData.timezone,
getAdditionalKeys(additionalData),
getAdditionalKeys(additionalData, mode),
undefined,
fallbackValue,
options,
Expand All @@ -2806,7 +2815,7 @@ export function getExecuteHookFunctions(
additionalData,
mode,
additionalData.timezone,
getAdditionalKeys(additionalData),
getAdditionalKeys(additionalData, mode),
isTest,
);
},
Expand Down Expand Up @@ -2945,7 +2954,7 @@ export function getExecuteWebhookFunctions(
itemIndex,
mode,
additionalData.timezone,
getAdditionalKeys(additionalData),
getAdditionalKeys(additionalData, mode),
undefined,
fallbackValue,
options,
Expand Down Expand Up @@ -2983,7 +2992,7 @@ export function getExecuteWebhookFunctions(
additionalData,
mode,
additionalData.timezone,
getAdditionalKeys(additionalData),
getAdditionalKeys(additionalData, mode),
);
},
getTimezone: (): string => {
Expand Down
7 changes: 7 additions & 0 deletions packages/editor-ui/src/components/CodeEdit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ export default mixins(
const connectionInputData = this.connectionInputData(parentNode, activeNode!.name, inputName, runIndex, nodeConnection);
const additionalProxyKeys: IWorkflowDataProxyAdditionalKeys = {
$execution: {
id: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
mode: 'test',
resumeUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
},
// deprecated
$executionId: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
$resumeWebhookUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
};
Expand Down
20 changes: 13 additions & 7 deletions packages/editor-ui/src/components/Error/NodeErrorView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div>
<div class="error-header">
<div class="error-message">{{ $locale.baseText('nodeErrorView.error') + ': ' + getErrorMessage() }}</div>
<div class="error-description" v-if="error.description">{{getErrorDescription()}}</div>
<div class="error-description" v-if="error.description" v-html="getErrorDescription()"></div>
</div>
<details>
<summary class="error-details__summary">
Expand Down Expand Up @@ -139,28 +139,34 @@ export default mixins(
},
},
methods: {
replacePlaceholders (parameter: string, message: string): string {
const parameterName = this.parameterDisplayName(parameter, false);
const parameterFullName = this.parameterDisplayName(parameter, true);
return message.replace(/%%PARAMETER%%/g, parameterName).replace(/%%PARAMETER_FULL%%/g, parameterFullName);
},
getErrorDescription (): string {
if (!this.error.context || !this.error.context.descriptionTemplate) {
return this.error.description;
}
const parameterName = this.parameterDisplayName(this.error.context.parameter);
return this.error.context.descriptionTemplate.replace(/%%PARAMETER%%/g, parameterName);
return this.replacePlaceholders(this.error.context.parameter, this.error.context.descriptionTemplate);
},
getErrorMessage (): string {
if (!this.error.context || !this.error.context.messageTemplate) {
return this.error.message;
}
const parameterName = this.parameterDisplayName(this.error.context.parameter);
return this.error.context.messageTemplate.replace(/%%PARAMETER%%/g, parameterName);
return this.replacePlaceholders(this.error.context.parameter, this.error.context.messageTemplate);
},
parameterDisplayName(path: string) {
parameterDisplayName(path: string, fullPath = true) {
try {
const parameters = this.parameterName(this.parameters, path.split('.'));
if (!parameters.length) {
throw new Error();
}
if (fullPath === false) {
return parameters.pop()!.displayName;
}
return parameters.map(parameter => parameter.displayName).join(' > ');
} catch (error) {
return `Could not find parameter "${path}"`;
Expand Down
7 changes: 7 additions & 0 deletions packages/editor-ui/src/components/VariableSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,13 @@ export default mixins(
}
const additionalKeys: IWorkflowDataProxyAdditionalKeys = {
$execution: {
id: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
mode: 'test',
resumeUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
},
// deprecated
$executionId: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
$resumeWebhookUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
};
Expand Down
54 changes: 46 additions & 8 deletions packages/editor-ui/src/components/mixins/pushConnection.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import {
IExecutionsCurrentSummaryExtended,
IPushData,
IPushDataConsoleMessage,
IPushDataExecutionFinished,
IPushDataExecutionStarted,
IPushDataNodeExecuteAfter,
IPushDataNodeExecuteBefore,
IPushDataTestWebhook,
} from '../../Interface';

import { externalHooks } from '@/components/mixins/externalHooks';
Expand All @@ -16,7 +10,11 @@ import { titleChange } from '@/components/mixins/titleChange';
import { workflowHelpers } from '@/components/mixins/workflowHelpers';

import {
ExpressionError,
IDataObject,
INodeTypeNameVersion,
IWorkflowBase,
TelemetryHelpers,
} from 'n8n-workflow';

import mixins from 'vue-typed-mixins';
Expand Down Expand Up @@ -215,7 +213,7 @@ export const pushConnection = mixins(

const runDataExecuted = pushData.data;

const runDataExecutedErrorMessage = this.$getExecutionError(runDataExecuted.data.resultData.error);
const runDataExecutedErrorMessage = this.$getExecutionError(runDataExecuted.data);

const workflow = this.getCurrentWorkflow();
if (runDataExecuted.waitTill !== undefined) {
Expand Down Expand Up @@ -251,8 +249,48 @@ export const pushConnection = mixins(
} else if (runDataExecuted.finished !== true) {
this.$titleSet(workflow.name as string, 'ERROR');

if (
runDataExecuted.data.resultData.error!.name === 'ExpressionError' &&
(runDataExecuted.data.resultData.error as ExpressionError).context.functionality === 'pairedItem'
) {
const error = runDataExecuted.data.resultData.error as ExpressionError;

this.getWorkflowDataToSave().then((workflowData) => {
const eventData: IDataObject = {
caused_by_credential: false,
error_message: error.description,
error_title: error.message,
error_type: error.context.type,
node_graph_string: JSON.stringify(TelemetryHelpers.generateNodesGraph(workflowData as IWorkflowBase, this.getNodeTypes()).nodeGraph),
workflow_id: this.$store.getters.workflowId,
};

if (error.context.nodeCause && ['no pairing info', 'invalid pairing info'].includes(error.context.type as string)) {
const node = workflow.getNode(error.context.nodeCause as string);

if (node) {
eventData.is_pinned = !!workflow.getPinDataOfNode(node.name);
eventData.mode = node.parameters.mode;
eventData.node_type = node.type;
eventData.operation = node.parameters.operation;
eventData.resource = node.parameters.resource;
}
}

this.$telemetry.track('Instance FE emitted paired item error', eventData);
});

}

let title: string;
if (runDataExecuted.data.resultData.lastNodeExecuted) {
title = `Problem in node ‘${runDataExecuted.data.resultData.lastNodeExecuted}‘`;
} else {
title = 'Problem executing workflow';
}

this.$showMessage({
title: 'Problem executing workflow',
title,
message: runDataExecutedErrorMessage,
type: 'error',
duration: 0,
Expand Down
Loading

0 comments on commit 5526057

Please sign in to comment.