mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
refactor(editor): Update Code node editor for native Python runner (#18538)
This commit is contained in:
@@ -21,12 +21,14 @@ import { useSettingsStore } from '@/stores/settings.store';
|
|||||||
import { dropInCodeEditor } from '@/plugins/codemirror/dragAndDrop';
|
import { dropInCodeEditor } from '@/plugins/codemirror/dragAndDrop';
|
||||||
import type { TargetNodeParameterContext } from '@/Interface';
|
import type { TargetNodeParameterContext } from '@/Interface';
|
||||||
|
|
||||||
|
export type CodeNodeLanguageOption = CodeNodeEditorLanguage | 'pythonNative';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
mode: CodeExecutionMode;
|
mode: CodeExecutionMode;
|
||||||
modelValue: string;
|
modelValue: string;
|
||||||
aiButtonEnabled?: boolean;
|
aiButtonEnabled?: boolean;
|
||||||
fillParent?: boolean;
|
fillParent?: boolean;
|
||||||
language?: CodeNodeEditorLanguage;
|
language?: CodeNodeLanguageOption;
|
||||||
isReadOnly?: boolean;
|
isReadOnly?: boolean;
|
||||||
rows?: number;
|
rows?: number;
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -63,7 +65,7 @@ const settingsStore = useSettingsStore();
|
|||||||
|
|
||||||
const linter = useLinter(
|
const linter = useLinter(
|
||||||
() => props.mode,
|
() => props.mode,
|
||||||
() => props.language,
|
() => (props.language === 'pythonNative' ? 'python' : props.language),
|
||||||
);
|
);
|
||||||
const extensions = computed(() => [linter.value]);
|
const extensions = computed(() => [linter.value]);
|
||||||
const placeholder = computed(() => CODE_PLACEHOLDERS[props.language]?.[props.mode] ?? '');
|
const placeholder = computed(() => CODE_PLACEHOLDERS[props.language]?.[props.mode] ?? '');
|
||||||
|
|||||||
@@ -24,7 +24,20 @@ export const useCompleter = (
|
|||||||
mode: MaybeRefOrGetter<CodeExecutionMode>,
|
mode: MaybeRefOrGetter<CodeExecutionMode>,
|
||||||
editor: MaybeRefOrGetter<EditorView | null>,
|
editor: MaybeRefOrGetter<EditorView | null>,
|
||||||
) => {
|
) => {
|
||||||
function autocompletionExtension(language: 'javaScript' | 'python'): Extension {
|
function autocompletionExtension(language: 'javaScript' | 'python' | 'pythonNative'): Extension {
|
||||||
|
if (language === 'pythonNative') {
|
||||||
|
const completions = (context: CompletionContext): CompletionResult | null => {
|
||||||
|
const word = context.matchBefore(/\w*/);
|
||||||
|
if (!word) return null;
|
||||||
|
|
||||||
|
const label = toValue(mode) === 'runOnceForEachItem' ? '_item' : '_items';
|
||||||
|
|
||||||
|
return { from: word.from, options: [{ label, type: 'variable' }] };
|
||||||
|
};
|
||||||
|
|
||||||
|
return autocompletion({ icons: false, override: [completions] });
|
||||||
|
}
|
||||||
|
|
||||||
// Base completions
|
// Base completions
|
||||||
const { baseCompletions, itemCompletions, nodeSelectorCompletions } = useBaseCompletions(
|
const { baseCompletions, itemCompletions, nodeSelectorCompletions } = useBaseCompletions(
|
||||||
toValue(mode),
|
toValue(mode),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { STICKY_NODE_TYPE } from '@/constants';
|
import { STICKY_NODE_TYPE } from '@/constants';
|
||||||
import type { Diagnostic } from '@codemirror/lint';
|
import type { Diagnostic } from '@codemirror/lint';
|
||||||
import type { CodeExecutionMode, CodeNodeEditorLanguage } from 'n8n-workflow';
|
import type { CodeExecutionMode } from 'n8n-workflow';
|
||||||
|
import type { CodeNodeLanguageOption } from './CodeNodeEditor.vue';
|
||||||
|
|
||||||
export const NODE_TYPES_EXCLUDED_FROM_AUTOCOMPLETION = [STICKY_NODE_TYPE];
|
export const NODE_TYPES_EXCLUDED_FROM_AUTOCOMPLETION = [STICKY_NODE_TYPE];
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ export const DEFAULT_LINTER_DELAY_IN_MS = 500;
|
|||||||
export const OFFSET_FOR_SCRIPT_WRAPPER = 'module.exports = async function() {'.length;
|
export const OFFSET_FOR_SCRIPT_WRAPPER = 'module.exports = async function() {'.length;
|
||||||
|
|
||||||
export const CODE_PLACEHOLDERS: Partial<
|
export const CODE_PLACEHOLDERS: Partial<
|
||||||
Record<CodeNodeEditorLanguage, Record<CodeExecutionMode, string>>
|
Record<CodeNodeLanguageOption, Record<CodeExecutionMode, string>>
|
||||||
> = {
|
> = {
|
||||||
javaScript: {
|
javaScript: {
|
||||||
runOnceForAllItems: `
|
runOnceForAllItems: `
|
||||||
@@ -63,4 +64,15 @@ return _input.all()`.trim(),
|
|||||||
_input.item.json.myNewField = 1
|
_input.item.json.myNewField = 1
|
||||||
return _input.item`.trim(),
|
return _input.item`.trim(),
|
||||||
},
|
},
|
||||||
|
pythonNative: {
|
||||||
|
runOnceForAllItems: `
|
||||||
|
# Loop over input items and add a new field called 'my_new_field' to the JSON of each one
|
||||||
|
for item in _items:
|
||||||
|
item["json"]["my_new_field"] = 1
|
||||||
|
return _items`.trim(),
|
||||||
|
runOnceForEachItem: `
|
||||||
|
# Add a new field called 'my_new_field' to the JSON of the item
|
||||||
|
_item["json"]["my_new_field"] = 1
|
||||||
|
return _item`.trim(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import type {
|
|||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
import type {
|
import type {
|
||||||
CodeExecutionMode,
|
CodeExecutionMode,
|
||||||
CodeNodeEditorLanguage,
|
|
||||||
EditorType,
|
EditorType,
|
||||||
IDataObject,
|
IDataObject,
|
||||||
ILoadOptions,
|
ILoadOptions,
|
||||||
@@ -24,6 +23,7 @@ import type {
|
|||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { CREDENTIAL_EMPTY_VALUE, isResourceLocatorValue, NodeHelpers } from 'n8n-workflow';
|
import { CREDENTIAL_EMPTY_VALUE, isResourceLocatorValue, NodeHelpers } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import type { CodeNodeLanguageOption } from '@/components/CodeNodeEditor/CodeNodeEditor.vue';
|
||||||
import CodeNodeEditor from '@/components/CodeNodeEditor/CodeNodeEditor.vue';
|
import CodeNodeEditor from '@/components/CodeNodeEditor/CodeNodeEditor.vue';
|
||||||
import CredentialsSelect from '@/components/CredentialsSelect.vue';
|
import CredentialsSelect from '@/components/CredentialsSelect.vue';
|
||||||
import ExpressionEditModal from '@/components/ExpressionEditModal.vue';
|
import ExpressionEditModal from '@/components/ExpressionEditModal.vue';
|
||||||
@@ -259,10 +259,12 @@ const editorIsReadOnly = computed<boolean>(() => {
|
|||||||
return getTypeOption<boolean>('editorIsReadOnly') ?? false;
|
return getTypeOption<boolean>('editorIsReadOnly') ?? false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const editorLanguage = computed<CodeNodeEditorLanguage>(() => {
|
const editorLanguage = computed<CodeNodeLanguageOption>(() => {
|
||||||
if (editorType.value === 'json' || props.parameter.type === 'json')
|
if (editorType.value === 'json' || props.parameter.type === 'json') return 'json';
|
||||||
return 'json' as CodeNodeEditorLanguage;
|
|
||||||
return getTypeOption<CodeNodeEditorLanguage>('editorLanguage') ?? 'javaScript';
|
if (node.value?.parameters?.language === 'pythonNative') return 'pythonNative';
|
||||||
|
|
||||||
|
return getTypeOption<CodeNodeLanguageOption>('editorLanguage') ?? 'javaScript';
|
||||||
});
|
});
|
||||||
|
|
||||||
const codeEditorMode = computed<CodeExecutionMode>(() => {
|
const codeEditorMode = computed<CodeExecutionMode>(() => {
|
||||||
|
|||||||
@@ -50,19 +50,21 @@ import {
|
|||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { useCompleter } from '../components/CodeNodeEditor/completer';
|
import { useCompleter } from '../components/CodeNodeEditor/completer';
|
||||||
import { mappingDropCursor } from '../plugins/codemirror/dragAndDrop';
|
import { mappingDropCursor } from '../plugins/codemirror/dragAndDrop';
|
||||||
import { languageFacet, type CodeEditorLanguage } from '../plugins/codemirror/format';
|
import { languageFacet } from '../plugins/codemirror/format';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { ignoreUpdateAnnotation } from '../utils/forceParse';
|
import { ignoreUpdateAnnotation } from '../utils/forceParse';
|
||||||
import type { TargetNodeParameterContext } from '@/Interface';
|
import type { TargetNodeParameterContext } from '@/Interface';
|
||||||
|
import type { CodeNodeLanguageOption } from '@/components/CodeNodeEditor/CodeNodeEditor.vue';
|
||||||
|
|
||||||
export type CodeEditorLanguageParamsMap = {
|
export type CodeEditorLanguageParamsMap = {
|
||||||
json: {};
|
json: {};
|
||||||
html: {};
|
html: {};
|
||||||
javaScript: { mode: CodeExecutionMode };
|
javaScript: { mode: CodeExecutionMode };
|
||||||
python: { mode: CodeExecutionMode };
|
python: { mode: CodeExecutionMode };
|
||||||
|
pythonNative: { mode: CodeExecutionMode };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCodeEditor = <L extends CodeEditorLanguage>({
|
export const useCodeEditor = <L extends CodeNodeLanguageOption>({
|
||||||
editorRef,
|
editorRef,
|
||||||
editorValue,
|
editorValue,
|
||||||
language,
|
language,
|
||||||
@@ -116,7 +118,7 @@ export const useCodeEditor = <L extends CodeEditorLanguage>({
|
|||||||
targetNodeParameterContext,
|
targetNodeParameterContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
function getInitialLanguageExtensions(lang: CodeEditorLanguage): Extension[] {
|
function getInitialLanguageExtensions(lang: CodeNodeLanguageOption): Extension[] {
|
||||||
switch (lang) {
|
switch (lang) {
|
||||||
case 'javaScript':
|
case 'javaScript':
|
||||||
return [javascript()];
|
return [javascript()];
|
||||||
@@ -128,7 +130,9 @@ export const useCodeEditor = <L extends CodeEditorLanguage>({
|
|||||||
async function getFullLanguageExtensions(): Promise<Extension[]> {
|
async function getFullLanguageExtensions(): Promise<Extension[]> {
|
||||||
if (!editor.value) return [];
|
if (!editor.value) return [];
|
||||||
const lang = toValue(language);
|
const lang = toValue(language);
|
||||||
const langExtensions: Extension[] = [languageFacet.of(lang)];
|
const langExtensions: Extension[] = [
|
||||||
|
languageFacet.of(lang === 'pythonNative' ? 'python' : lang),
|
||||||
|
];
|
||||||
|
|
||||||
switch (lang) {
|
switch (lang) {
|
||||||
case 'javaScript': {
|
case 'javaScript': {
|
||||||
@@ -136,9 +140,10 @@ export const useCodeEditor = <L extends CodeEditorLanguage>({
|
|||||||
langExtensions.push(tsExtension);
|
langExtensions.push(tsExtension);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'python': {
|
case 'python':
|
||||||
|
case 'pythonNative': {
|
||||||
const pythonAutocomplete = useCompleter(mode, editor.value ?? null).autocompletionExtension(
|
const pythonAutocomplete = useCompleter(mode, editor.value ?? null).autocompletionExtension(
|
||||||
'python',
|
lang,
|
||||||
);
|
);
|
||||||
langExtensions.push([python(), pythonAutocomplete]);
|
langExtensions.push([python(), pythonAutocomplete]);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const pythonCodeDescription: INodeProperties[] = [
|
|||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: `${PRINT_INSTRUCTION}<br><br>The native Python option does not support <code>_</code> syntax and helpers, except for <code>_items</code> and <code>_item</code>.`,
|
displayName: `${PRINT_INSTRUCTION}<br><br>The native Python option does not support <code>_</code> syntax and helpers, except for <code>_items</code> in all-items mode and <code>_item</code> in per-item mode.`,
|
||||||
name: 'notice',
|
name: 'notice',
|
||||||
type: 'notice',
|
type: 'notice',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const WAIT_INDEFINITELY = new Date('3000-01-01T00:00:00.000Z');
|
|||||||
|
|
||||||
export const LOG_LEVELS = ['silent', 'error', 'warn', 'info', 'debug'] as const;
|
export const LOG_LEVELS = ['silent', 'error', 'warn', 'info', 'debug'] as const;
|
||||||
|
|
||||||
export const CODE_LANGUAGES = ['javaScript', 'python'] as const;
|
export const CODE_LANGUAGES = ['javaScript', 'python', 'json', 'html'] as const;
|
||||||
export const CODE_EXECUTION_MODES = ['runOnceForAllItems', 'runOnceForEachItem'] as const;
|
export const CODE_EXECUTION_MODES = ['runOnceForAllItems', 'runOnceForEachItem'] as const;
|
||||||
|
|
||||||
// Arbitrary value to represent an empty credential value
|
// Arbitrary value to represent an empty credential value
|
||||||
|
|||||||
Reference in New Issue
Block a user