mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-15 17:14:05 -08:00
d9b98fc8be
* ✨ Create rule `no-unneeded-backticks` * 👕 Enable rule * ⚡ Run rule on `cli` * ⚡ Run rule on `core` * ⚡ Run rule on `workflow` * ⚡ Rule rule on `design-system` * ⚡ Run rule on `node-dev` * ⚡ Run rule on `editor-ui` * ⚡ Run rule on `nodes-base`
281 lines
7.6 KiB
Vue
281 lines
7.6 KiB
Vue
<template>
|
|
<el-dialog
|
|
visible
|
|
append-to-body
|
|
:close-on-click-modal="false"
|
|
width="80%"
|
|
:title="`${$locale.baseText('codeEdit.edit')} ${$locale
|
|
.nodeText()
|
|
.inputLabelDisplayName(parameter, path)}`"
|
|
:before-close="closeDialog"
|
|
>
|
|
<div class="text-editor-wrapper ignore-key-press">
|
|
<code-editor
|
|
:value="value"
|
|
:autocomplete="loadAutocompleteData"
|
|
:readonly="readonly"
|
|
@input="$emit('valueChanged', $event)"
|
|
/>
|
|
</div>
|
|
</el-dialog>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { genericHelpers } from '@/mixins/genericHelpers';
|
|
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
|
|
|
import mixins from 'vue-typed-mixins';
|
|
import { IExecutionResponse, INodeUi } from '@/Interface';
|
|
import {
|
|
IBinaryKeyData,
|
|
IDataObject,
|
|
INodeExecutionData,
|
|
IRunExecutionData,
|
|
IWorkflowDataProxyAdditionalKeys,
|
|
WorkflowDataProxy,
|
|
} from 'n8n-workflow';
|
|
|
|
import { PLACEHOLDER_FILLED_AT_EXECUTION_TIME } from '@/constants';
|
|
import { CodeEditor } from './forms';
|
|
import { mapStores } from 'pinia';
|
|
import { useWorkflowsStore } from '@/stores/workflows';
|
|
import { useRootStore } from '@/stores/n8nRootStore';
|
|
import { useNDVStore } from '@/stores/ndv';
|
|
|
|
export default mixins(genericHelpers, workflowHelpers).extend({
|
|
name: 'CodeEdit',
|
|
components: {
|
|
CodeEditor,
|
|
},
|
|
props: ['codeAutocomplete', 'parameter', 'path', 'type', 'value', 'readonly'],
|
|
computed: {
|
|
...mapStores(useNDVStore, useRootStore, useWorkflowsStore),
|
|
},
|
|
methods: {
|
|
loadAutocompleteData(): string[] {
|
|
if (['function', 'functionItem'].includes(this.codeAutocomplete)) {
|
|
const itemIndex = 0;
|
|
const inputName = 'main';
|
|
const mode = 'manual';
|
|
let runIndex = 0;
|
|
|
|
const executedWorkflow = this.workflowsStore.getWorkflowExecution;
|
|
const workflow = this.getCurrentWorkflow();
|
|
const activeNode: INodeUi | null = this.ndvStore.activeNode;
|
|
const parentNode = workflow.getParentNodes(activeNode!.name, inputName, 1);
|
|
const nodeConnection = workflow.getNodeConnectionIndexes(
|
|
activeNode!.name,
|
|
parentNode[0],
|
|
) || {
|
|
sourceIndex: 0,
|
|
destinationIndex: 0,
|
|
};
|
|
|
|
const executionData = this.workflowsStore.getWorkflowExecution;
|
|
|
|
let runExecutionData: IRunExecutionData;
|
|
if (!executionData || !executionData.data) {
|
|
runExecutionData = {
|
|
resultData: {
|
|
runData: {},
|
|
},
|
|
};
|
|
} else {
|
|
runExecutionData = executionData.data;
|
|
if (runExecutionData.resultData.runData[activeNode!.name]) {
|
|
runIndex = runExecutionData.resultData.runData[activeNode!.name].length - 1;
|
|
}
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
const dataProxy = new WorkflowDataProxy(
|
|
workflow,
|
|
runExecutionData,
|
|
runIndex,
|
|
itemIndex,
|
|
activeNode!.name,
|
|
connectionInputData || [],
|
|
{},
|
|
mode,
|
|
this.rootStore.timezone,
|
|
additionalProxyKeys,
|
|
);
|
|
const proxy = dataProxy.getDataProxy();
|
|
|
|
const autoCompleteItems = [
|
|
'function $evaluateExpression(expression: string, itemIndex?: number): any {};',
|
|
'function getNodeParameter(parameterName: string, itemIndex: number, fallbackValue?: any): any {};',
|
|
'function getWorkflowStaticData(type: string): {};',
|
|
'function $item(itemIndex: number, runIndex?: number): {};',
|
|
'function $items(nodeName?: string, outputIndex?: number, runIndex?: number): {};',
|
|
];
|
|
|
|
const baseKeys = [
|
|
'$env',
|
|
'$executionId',
|
|
'$mode',
|
|
'$parameter',
|
|
'$resumeWebhookUrl',
|
|
'$workflow',
|
|
'$now',
|
|
'$today',
|
|
'$thisRunIndex',
|
|
'DateTime',
|
|
'Duration',
|
|
'Interval',
|
|
];
|
|
|
|
const functionItemKeys = ['$json', '$binary', '$position', '$thisItem', '$thisItemIndex'];
|
|
|
|
const additionalKeys: string[] = [];
|
|
if (this.codeAutocomplete === 'functionItem') {
|
|
additionalKeys.push(...functionItemKeys);
|
|
}
|
|
|
|
if (executedWorkflow && connectionInputData && connectionInputData.length) {
|
|
baseKeys.push(...additionalKeys);
|
|
} else {
|
|
additionalKeys.forEach((key) => {
|
|
autoCompleteItems.push(`const ${key} = {}`);
|
|
});
|
|
}
|
|
|
|
for (const key of baseKeys) {
|
|
autoCompleteItems.push(
|
|
`const ${key} = ${JSON.stringify(this.createSimpleRepresentation(proxy[key]))}`,
|
|
);
|
|
}
|
|
|
|
// Add the nodes and their simplified data
|
|
const nodes: {
|
|
[key: string]: INodeExecutionData;
|
|
} = {};
|
|
for (const [nodeName, node] of Object.entries(workflow.nodes)) {
|
|
// To not load to much data create a simple representation.
|
|
nodes[nodeName] = {
|
|
json: {} as IDataObject,
|
|
parameter: this.createSimpleRepresentation(
|
|
proxy.$node[nodeName].parameter,
|
|
) as IDataObject,
|
|
};
|
|
|
|
try {
|
|
nodes[nodeName]!.json = this.createSimpleRepresentation(
|
|
proxy.$node[nodeName].json,
|
|
) as IDataObject;
|
|
nodes[nodeName]!.context = this.createSimpleRepresentation(
|
|
proxy.$node[nodeName].context,
|
|
) as IDataObject;
|
|
nodes[nodeName]!.runIndex = proxy.$node[nodeName].runIndex;
|
|
if (Object.keys(proxy.$node[nodeName].binary).length) {
|
|
nodes[nodeName]!.binary = this.createSimpleRepresentation(
|
|
proxy.$node[nodeName].binary,
|
|
) as IBinaryKeyData;
|
|
}
|
|
} catch (error) {}
|
|
}
|
|
autoCompleteItems.push(`const $node = ${JSON.stringify(nodes)}`);
|
|
autoCompleteItems.push('function $jmespath(jsonDoc: object, query: string): {};');
|
|
|
|
if (this.codeAutocomplete === 'function') {
|
|
if (connectionInputData) {
|
|
autoCompleteItems.push(
|
|
`const items = ${JSON.stringify(
|
|
this.createSimpleRepresentation(connectionInputData),
|
|
)}`,
|
|
);
|
|
} else {
|
|
autoCompleteItems.push('const items: {json: {[key: string]: any}}[] = []');
|
|
}
|
|
} else if (this.codeAutocomplete === 'functionItem') {
|
|
if (connectionInputData) {
|
|
autoCompleteItems.push('const item = $json');
|
|
} else {
|
|
autoCompleteItems.push('const item: {[key: string]: any} = {}');
|
|
}
|
|
}
|
|
|
|
return autoCompleteItems;
|
|
}
|
|
|
|
return [];
|
|
},
|
|
closeDialog() {
|
|
// Handle the close externally as the visible parameter is an external prop
|
|
// and is so not allowed to be changed here.
|
|
this.$emit('closeDialog');
|
|
return false;
|
|
},
|
|
|
|
createSimpleRepresentation(
|
|
inputData:
|
|
| object
|
|
| null
|
|
| undefined
|
|
| boolean
|
|
| string
|
|
| number
|
|
| boolean[]
|
|
| string[]
|
|
| number[]
|
|
| object[],
|
|
):
|
|
| object
|
|
| null
|
|
| undefined
|
|
| boolean
|
|
| string
|
|
| number
|
|
| boolean[]
|
|
| string[]
|
|
| number[]
|
|
| object[] {
|
|
if (inputData === null || inputData === undefined) {
|
|
return inputData;
|
|
} else if (typeof inputData === 'string') {
|
|
return '';
|
|
} else if (typeof inputData === 'boolean') {
|
|
return true;
|
|
} else if (typeof inputData === 'number') {
|
|
return 1;
|
|
} else if (Array.isArray(inputData)) {
|
|
return inputData.map((value) => this.createSimpleRepresentation(value));
|
|
} else if (typeof inputData === 'object') {
|
|
const returnData: { [key: string]: object } = {};
|
|
Object.keys(inputData).forEach((key) => {
|
|
// @ts-ignore
|
|
returnData[key] = this.createSimpleRepresentation(inputData[key]);
|
|
});
|
|
return returnData;
|
|
}
|
|
return inputData;
|
|
},
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.text-editor {
|
|
min-height: 30rem;
|
|
}
|
|
</style>
|