mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(editor): Add SQL editor support (#5517)
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
"@codemirror/commands": "^6.1.0",
|
"@codemirror/commands": "^6.1.0",
|
||||||
"@codemirror/lang-javascript": "^6.1.2",
|
"@codemirror/lang-javascript": "^6.1.2",
|
||||||
"@codemirror/lang-json": "^6.0.1",
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
|
"@codemirror/lang-sql": "^6.4.1",
|
||||||
"@codemirror/language": "^6.2.1",
|
"@codemirror/language": "^6.2.1",
|
||||||
"@codemirror/lint": "^6.0.0",
|
"@codemirror/lint": "^6.0.0",
|
||||||
"@codemirror/state": "^6.1.4",
|
"@codemirror/state": "^6.1.4",
|
||||||
|
|||||||
@@ -103,6 +103,14 @@
|
|||||||
@valueChanged="valueChangedDebounced"
|
@valueChanged="valueChangedDebounced"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<sql-editor
|
||||||
|
v-else-if="editorType === 'sqlEditor'"
|
||||||
|
:query="node.parameters.query"
|
||||||
|
:dialect="getArgument('sqlDialect')"
|
||||||
|
:isReadOnly="isReadOnly"
|
||||||
|
@valueChanged="valueChangedDebounced"
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-else-if="editorType"
|
v-else-if="editorType"
|
||||||
class="readonly-code clickable ph-no-capture"
|
class="readonly-code clickable ph-no-capture"
|
||||||
@@ -367,6 +375,7 @@ import ExpressionParameterInput from '@/components/ExpressionParameterInput.vue'
|
|||||||
import TextEdit from '@/components/TextEdit.vue';
|
import TextEdit from '@/components/TextEdit.vue';
|
||||||
import CodeNodeEditor from '@/components/CodeNodeEditor/CodeNodeEditor.vue';
|
import CodeNodeEditor from '@/components/CodeNodeEditor/CodeNodeEditor.vue';
|
||||||
import HtmlEditor from '@/components/HtmlEditor/HtmlEditor.vue';
|
import HtmlEditor from '@/components/HtmlEditor/HtmlEditor.vue';
|
||||||
|
import SqlEditor from '@/components/SqlEditor/SqlEditor.vue';
|
||||||
import { externalHooks } from '@/mixins/externalHooks';
|
import { externalHooks } from '@/mixins/externalHooks';
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
||||||
import { showMessage } from '@/mixins/showMessage';
|
import { showMessage } from '@/mixins/showMessage';
|
||||||
@@ -374,8 +383,7 @@ import { workflowHelpers } from '@/mixins/workflowHelpers';
|
|||||||
import { hasExpressionMapping, isValueExpression, isResourceLocatorValue } from '@/utils';
|
import { hasExpressionMapping, isValueExpression, isResourceLocatorValue } from '@/utils';
|
||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
import { CUSTOM_API_CALL_KEY, HTML_NODE_TYPE } from '@/constants';
|
import { CODE_NODE_TYPE, CUSTOM_API_CALL_KEY, HTML_NODE_TYPE } from '@/constants';
|
||||||
import { CODE_NODE_TYPE } from '@/constants';
|
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import { debounceHelper } from '@/mixins/debounce';
|
import { debounceHelper } from '@/mixins/debounce';
|
||||||
import { mapStores } from 'pinia';
|
import { mapStores } from 'pinia';
|
||||||
@@ -398,6 +406,7 @@ export default mixins(
|
|||||||
components: {
|
components: {
|
||||||
CodeNodeEditor,
|
CodeNodeEditor,
|
||||||
HtmlEditor,
|
HtmlEditor,
|
||||||
|
SqlEditor,
|
||||||
ExpressionEdit,
|
ExpressionEdit,
|
||||||
ExpressionParameterInput,
|
ExpressionParameterInput,
|
||||||
NodeCredentials,
|
NodeCredentials,
|
||||||
|
|||||||
95
packages/editor-ui/src/components/SqlEditor/SqlEditor.vue
Normal file
95
packages/editor-ui/src/components/SqlEditor/SqlEditor.vue
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="sqlEditor" class="ph-no-capture"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { autocompletion } from '@codemirror/autocomplete';
|
||||||
|
import { indentWithTab, history, redo } from '@codemirror/commands';
|
||||||
|
import { foldGutter, indentOnInput } from '@codemirror/language';
|
||||||
|
import { lintGutter } from '@codemirror/lint';
|
||||||
|
import type { Extension } from '@codemirror/state';
|
||||||
|
import { EditorState } from '@codemirror/state';
|
||||||
|
import type { ViewUpdate } from '@codemirror/view';
|
||||||
|
import {
|
||||||
|
dropCursor,
|
||||||
|
EditorView,
|
||||||
|
highlightActiveLine,
|
||||||
|
highlightActiveLineGutter,
|
||||||
|
keymap,
|
||||||
|
lineNumbers,
|
||||||
|
} from '@codemirror/view';
|
||||||
|
import { MSSQL, MySQL, PostgreSQL, sql, StandardSQL } from '@codemirror/lang-sql';
|
||||||
|
import type { SQLDialect } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { codeNodeEditorTheme } from '../CodeNodeEditor/theme';
|
||||||
|
|
||||||
|
const SQL_DIALECTS = {
|
||||||
|
standard: StandardSQL,
|
||||||
|
mssql: MSSQL,
|
||||||
|
mysql: MySQL,
|
||||||
|
postgres: PostgreSQL,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'sql-editor',
|
||||||
|
props: {
|
||||||
|
query: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
dialect: {
|
||||||
|
type: String as PropType<SQLDialect>,
|
||||||
|
default: 'standard',
|
||||||
|
},
|
||||||
|
isReadOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: {} as EditorView,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
doc(): string {
|
||||||
|
return this.editor.state.doc.toString();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
const dialect = SQL_DIALECTS[this.dialect as SQLDialect] ?? SQL_DIALECTS.standard;
|
||||||
|
const extensions: Extension[] = [
|
||||||
|
sql({ dialect, upperCaseKeywords: true }),
|
||||||
|
codeNodeEditorTheme({ maxHeight: false }),
|
||||||
|
lineNumbers(),
|
||||||
|
EditorView.lineWrapping,
|
||||||
|
lintGutter(),
|
||||||
|
EditorState.readOnly.of(this.isReadOnly),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (this.isReadOnly) {
|
||||||
|
extensions.push(EditorView.editable.of(this.isReadOnly));
|
||||||
|
} else {
|
||||||
|
extensions.push(
|
||||||
|
history(),
|
||||||
|
keymap.of([indentWithTab, { key: 'Mod-Shift-z', run: redo }]),
|
||||||
|
autocompletion(),
|
||||||
|
indentOnInput(),
|
||||||
|
highlightActiveLine(),
|
||||||
|
highlightActiveLineGutter(),
|
||||||
|
foldGutter(),
|
||||||
|
dropCursor(),
|
||||||
|
EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
|
||||||
|
if (!viewUpdate.docChanged) return;
|
||||||
|
this.$emit('valueChanged', this.doc);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const state = EditorState.create({ doc: this.query, extensions });
|
||||||
|
this.editor = new EditorView({ parent: this.$refs.sqlEditor as HTMLDivElement, state });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -73,6 +73,10 @@ export class CrateDb implements INodeType {
|
|||||||
displayName: 'Query',
|
displayName: 'Query',
|
||||||
name: 'query',
|
name: 'query',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
editor: 'sqlEditor',
|
||||||
|
sqlDialect: 'postgres',
|
||||||
|
},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: ['executeQuery'],
|
operation: ['executeQuery'],
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ const properties: INodeProperties[] = [
|
|||||||
displayName: 'SQL Query',
|
displayName: 'SQL Query',
|
||||||
name: 'sqlQuery',
|
name: 'sqlQuery',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
editor: 'sqlEditor',
|
||||||
|
},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
hide: {
|
hide: {
|
||||||
'/options.useLegacySql': [true],
|
'/options.useLegacySql': [true],
|
||||||
@@ -28,6 +31,9 @@ const properties: INodeProperties[] = [
|
|||||||
displayName: 'SQL Query',
|
displayName: 'SQL Query',
|
||||||
name: 'sqlQuery',
|
name: 'sqlQuery',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
editor: 'sqlEditor',
|
||||||
|
},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
'/options.useLegacySql': [true],
|
'/options.useLegacySql': [true],
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ export class MicrosoftSql implements INodeType {
|
|||||||
displayName: 'Query',
|
displayName: 'Query',
|
||||||
name: 'query',
|
name: 'query',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
editor: 'sqlEditor',
|
||||||
|
sqlDialect: 'mssql',
|
||||||
|
},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: ['executeQuery'],
|
operation: ['executeQuery'],
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ const versionDescription: INodeTypeDescription = {
|
|||||||
displayName: 'Query',
|
displayName: 'Query',
|
||||||
name: 'query',
|
name: 'query',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
editor: 'sqlEditor',
|
||||||
|
sqlDialect: 'mysql',
|
||||||
|
},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: ['executeQuery'],
|
operation: ['executeQuery'],
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ const properties: INodeProperties[] = [
|
|||||||
description:
|
description:
|
||||||
"The SQL query to execute. You can use n8n expressions and $1, $2, $3, etc to refer to the 'Query Parameters' set in options below.",
|
"The SQL query to execute. You can use n8n expressions and $1, $2, $3, etc to refer to the 'Query Parameters' set in options below.",
|
||||||
typeOptions: {
|
typeOptions: {
|
||||||
rows: 3,
|
editor: 'sqlEditor',
|
||||||
|
sqlDialect: 'mysql',
|
||||||
},
|
},
|
||||||
hint: 'Prefer using query parameters over n8n expressions to avoid SQL injection attacks',
|
hint: 'Prefer using query parameters over n8n expressions to avoid SQL injection attacks',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ const versionDescription: INodeTypeDescription = {
|
|||||||
displayName: 'Query',
|
displayName: 'Query',
|
||||||
name: 'query',
|
name: 'query',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
editor: 'sqlEditor',
|
||||||
|
sqlDialect: 'postgres',
|
||||||
|
},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: ['executeQuery'],
|
operation: ['executeQuery'],
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ const properties: INodeProperties[] = [
|
|||||||
description:
|
description:
|
||||||
"The SQL query to execute. You can use n8n expressions and $1, $2, $3, etc to refer to the 'Query Parameters' set in options below.",
|
"The SQL query to execute. You can use n8n expressions and $1, $2, $3, etc to refer to the 'Query Parameters' set in options below.",
|
||||||
typeOptions: {
|
typeOptions: {
|
||||||
rows: 3,
|
editor: 'sqlEditor',
|
||||||
|
sqlDialect: 'postgres',
|
||||||
},
|
},
|
||||||
hint: 'Prefer using query parameters over n8n expressions to avoid SQL injection attacks',
|
hint: 'Prefer using query parameters over n8n expressions to avoid SQL injection attacks',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ export class QuestDb implements INodeType {
|
|||||||
displayName: 'Query',
|
displayName: 'Query',
|
||||||
name: 'query',
|
name: 'query',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
editor: 'sqlEditor',
|
||||||
|
sqlDialect: 'postgres',
|
||||||
|
},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: ['executeQuery'],
|
operation: ['executeQuery'],
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ export class Snowflake implements INodeType {
|
|||||||
displayName: 'Query',
|
displayName: 'Query',
|
||||||
name: 'query',
|
name: 'query',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
editor: 'sqlEditor',
|
||||||
|
},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: ['executeQuery'],
|
operation: ['executeQuery'],
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ export class TimescaleDb implements INodeType {
|
|||||||
displayName: 'Query',
|
displayName: 'Query',
|
||||||
name: 'query',
|
name: 'query',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
editor: 'sqlEditor',
|
||||||
|
sqlDialect: 'postgres',
|
||||||
|
},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: ['executeQuery'],
|
operation: ['executeQuery'],
|
||||||
|
|||||||
@@ -1019,8 +1019,9 @@ export type NodePropertyTypes =
|
|||||||
|
|
||||||
export type CodeAutocompleteTypes = 'function' | 'functionItem';
|
export type CodeAutocompleteTypes = 'function' | 'functionItem';
|
||||||
|
|
||||||
export type EditorType = 'code' | 'codeNodeEditor' | 'htmlEditor' | 'json';
|
export type EditorType = 'code' | 'codeNodeEditor' | 'htmlEditor' | 'sqlEditor' | 'json';
|
||||||
export type CodeNodeEditorLanguage = 'javaScript' | 'json'; //| 'python' | 'sql';
|
export type CodeNodeEditorLanguage = 'javaScript' | 'json'; //| 'python' | 'sql';
|
||||||
|
export type SQLDialect = 'mssql' | 'mysql' | 'postgres';
|
||||||
|
|
||||||
export interface ILoadOptions {
|
export interface ILoadOptions {
|
||||||
routing?: {
|
routing?: {
|
||||||
@@ -1035,6 +1036,7 @@ export interface INodePropertyTypeOptions {
|
|||||||
codeAutocomplete?: CodeAutocompleteTypes; // Supported by: string
|
codeAutocomplete?: CodeAutocompleteTypes; // Supported by: string
|
||||||
editor?: EditorType; // Supported by: string
|
editor?: EditorType; // Supported by: string
|
||||||
editorLanguage?: CodeNodeEditorLanguage; // Supported by: string in combination with editor: codeNodeEditor
|
editorLanguage?: CodeNodeEditorLanguage; // Supported by: string in combination with editor: codeNodeEditor
|
||||||
|
sqlDialect?: SQLDialect; // Supported by: sqlEditor
|
||||||
loadOptionsDependsOn?: string[]; // Supported by: options
|
loadOptionsDependsOn?: string[]; // Supported by: options
|
||||||
loadOptionsMethod?: string; // Supported by: options
|
loadOptionsMethod?: string; // Supported by: options
|
||||||
loadOptions?: ILoadOptions; // Supported by: options
|
loadOptions?: ILoadOptions; // Supported by: options
|
||||||
|
|||||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@@ -844,6 +844,9 @@ importers:
|
|||||||
'@codemirror/lang-json':
|
'@codemirror/lang-json':
|
||||||
specifier: ^6.0.1
|
specifier: ^6.0.1
|
||||||
version: 6.0.1
|
version: 6.0.1
|
||||||
|
'@codemirror/lang-sql':
|
||||||
|
specifier: ^6.4.1
|
||||||
|
version: 6.4.1(@codemirror/view@6.5.1)(@lezer/common@1.0.1)
|
||||||
'@codemirror/language':
|
'@codemirror/language':
|
||||||
specifier: ^6.2.1
|
specifier: ^6.2.1
|
||||||
version: 6.2.1
|
version: 6.2.1
|
||||||
@@ -3367,6 +3370,19 @@ packages:
|
|||||||
'@lezer/json': 1.0.0
|
'@lezer/json': 1.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@codemirror/lang-sql@6.4.1(@codemirror/view@6.5.1)(@lezer/common@1.0.1):
|
||||||
|
resolution: {integrity: sha512-PFB56L+A0WGY35uRya+Trt5g19V9k2V9X3c55xoFW4RgiATr/yLqWsbbnEsdxuMn5tLpuikp7Kmj9smRsqBXAg==}
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/autocomplete': 6.4.0(@codemirror/language@6.2.1)(@codemirror/state@6.1.4)(@codemirror/view@6.5.1)(@lezer/common@1.0.1)
|
||||||
|
'@codemirror/language': 6.2.1
|
||||||
|
'@codemirror/state': 6.1.4
|
||||||
|
'@lezer/highlight': 1.1.1
|
||||||
|
'@lezer/lr': 1.2.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@codemirror/view'
|
||||||
|
- '@lezer/common'
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@codemirror/language@6.2.1:
|
/@codemirror/language@6.2.1:
|
||||||
resolution: {integrity: sha512-MC3svxuvIj0MRpFlGHxLS6vPyIdbTr2KKPEW46kCoCXw2ktb4NTkpkPBI/lSP/FoNXLCBJ0mrnUi1OoZxtpW1Q==}
|
resolution: {integrity: sha512-MC3svxuvIj0MRpFlGHxLS6vPyIdbTr2KKPEW46kCoCXw2ktb4NTkpkPBI/lSP/FoNXLCBJ0mrnUi1OoZxtpW1Q==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user