n8n/packages/editor-ui/src/components/CodeEdit.vue
Iván Ovejero d9b98fc8be
refactor: Lint for no unneeded backticks (#5057) (no-changelog)
*  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`
2022-12-29 12:20:43 +01:00

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>