mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 12:57:29 -08:00
feat(editor): Add SQL editor support (#5517)
This commit is contained in:
parent
f9b11c73b9
commit
70aaf24784
|
@ -30,6 +30,7 @@
|
|||
"@codemirror/commands": "^6.1.0",
|
||||
"@codemirror/lang-javascript": "^6.1.2",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-sql": "^6.4.1",
|
||||
"@codemirror/language": "^6.2.1",
|
||||
"@codemirror/lint": "^6.0.0",
|
||||
"@codemirror/state": "^6.1.4",
|
||||
|
|
|
@ -103,6 +103,14 @@
|
|||
@valueChanged="valueChangedDebounced"
|
||||
/>
|
||||
|
||||
<sql-editor
|
||||
v-else-if="editorType === 'sqlEditor'"
|
||||
:query="node.parameters.query"
|
||||
:dialect="getArgument('sqlDialect')"
|
||||
:isReadOnly="isReadOnly"
|
||||
@valueChanged="valueChangedDebounced"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-else-if="editorType"
|
||||
class="readonly-code clickable ph-no-capture"
|
||||
|
@ -367,6 +375,7 @@ import ExpressionParameterInput from '@/components/ExpressionParameterInput.vue'
|
|||
import TextEdit from '@/components/TextEdit.vue';
|
||||
import CodeNodeEditor from '@/components/CodeNodeEditor/CodeNodeEditor.vue';
|
||||
import HtmlEditor from '@/components/HtmlEditor/HtmlEditor.vue';
|
||||
import SqlEditor from '@/components/SqlEditor/SqlEditor.vue';
|
||||
import { externalHooks } from '@/mixins/externalHooks';
|
||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
||||
import { showMessage } from '@/mixins/showMessage';
|
||||
|
@ -374,8 +383,7 @@ import { workflowHelpers } from '@/mixins/workflowHelpers';
|
|||
import { hasExpressionMapping, isValueExpression, isResourceLocatorValue } from '@/utils';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { CUSTOM_API_CALL_KEY, HTML_NODE_TYPE } from '@/constants';
|
||||
import { CODE_NODE_TYPE } from '@/constants';
|
||||
import { CODE_NODE_TYPE, CUSTOM_API_CALL_KEY, HTML_NODE_TYPE } from '@/constants';
|
||||
import type { PropType } from 'vue';
|
||||
import { debounceHelper } from '@/mixins/debounce';
|
||||
import { mapStores } from 'pinia';
|
||||
|
@ -398,6 +406,7 @@ export default mixins(
|
|||
components: {
|
||||
CodeNodeEditor,
|
||||
HtmlEditor,
|
||||
SqlEditor,
|
||||
ExpressionEdit,
|
||||
ExpressionParameterInput,
|
||||
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',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
editor: 'sqlEditor',
|
||||
sqlDialect: 'postgres',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['executeQuery'],
|
||||
|
|
|
@ -14,6 +14,9 @@ const properties: INodeProperties[] = [
|
|||
displayName: 'SQL Query',
|
||||
name: 'sqlQuery',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
editor: 'sqlEditor',
|
||||
},
|
||||
displayOptions: {
|
||||
hide: {
|
||||
'/options.useLegacySql': [true],
|
||||
|
@ -28,6 +31,9 @@ const properties: INodeProperties[] = [
|
|||
displayName: 'SQL Query',
|
||||
name: 'sqlQuery',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
editor: 'sqlEditor',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/options.useLegacySql': [true],
|
||||
|
|
|
@ -90,6 +90,10 @@ export class MicrosoftSql implements INodeType {
|
|||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
editor: 'sqlEditor',
|
||||
sqlDialect: 'mssql',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['executeQuery'],
|
||||
|
|
|
@ -72,6 +72,10 @@ const versionDescription: INodeTypeDescription = {
|
|||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
editor: 'sqlEditor',
|
||||
sqlDialect: 'mysql',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['executeQuery'],
|
||||
|
|
|
@ -21,7 +21,8 @@ const properties: INodeProperties[] = [
|
|||
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.",
|
||||
typeOptions: {
|
||||
rows: 3,
|
||||
editor: 'sqlEditor',
|
||||
sqlDialect: 'mysql',
|
||||
},
|
||||
hint: 'Prefer using query parameters over n8n expressions to avoid SQL injection attacks',
|
||||
},
|
||||
|
|
|
@ -71,6 +71,10 @@ const versionDescription: INodeTypeDescription = {
|
|||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
editor: 'sqlEditor',
|
||||
sqlDialect: 'postgres',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['executeQuery'],
|
||||
|
|
|
@ -21,7 +21,8 @@ const properties: INodeProperties[] = [
|
|||
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.",
|
||||
typeOptions: {
|
||||
rows: 3,
|
||||
editor: 'sqlEditor',
|
||||
sqlDialect: 'postgres',
|
||||
},
|
||||
hint: 'Prefer using query parameters over n8n expressions to avoid SQL injection attacks',
|
||||
},
|
||||
|
|
|
@ -60,6 +60,10 @@ export class QuestDb implements INodeType {
|
|||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
editor: 'sqlEditor',
|
||||
sqlDialect: 'postgres',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['executeQuery'],
|
||||
|
|
|
@ -65,6 +65,9 @@ export class Snowflake implements INodeType {
|
|||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
editor: 'sqlEditor',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['executeQuery'],
|
||||
|
|
|
@ -65,6 +65,10 @@ export class TimescaleDb implements INodeType {
|
|||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
editor: 'sqlEditor',
|
||||
sqlDialect: 'postgres',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['executeQuery'],
|
||||
|
|
|
@ -1019,8 +1019,9 @@ export type NodePropertyTypes =
|
|||
|
||||
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 SQLDialect = 'mssql' | 'mysql' | 'postgres';
|
||||
|
||||
export interface ILoadOptions {
|
||||
routing?: {
|
||||
|
@ -1035,6 +1036,7 @@ export interface INodePropertyTypeOptions {
|
|||
codeAutocomplete?: CodeAutocompleteTypes; // Supported by: string
|
||||
editor?: EditorType; // Supported by: string
|
||||
editorLanguage?: CodeNodeEditorLanguage; // Supported by: string in combination with editor: codeNodeEditor
|
||||
sqlDialect?: SQLDialect; // Supported by: sqlEditor
|
||||
loadOptionsDependsOn?: string[]; // Supported by: options
|
||||
loadOptionsMethod?: string; // Supported by: options
|
||||
loadOptions?: ILoadOptions; // Supported by: options
|
||||
|
|
|
@ -844,6 +844,9 @@ importers:
|
|||
'@codemirror/lang-json':
|
||||
specifier: ^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':
|
||||
specifier: ^6.2.1
|
||||
version: 6.2.1
|
||||
|
@ -3367,6 +3370,19 @@ packages:
|
|||
'@lezer/json': 1.0.0
|
||||
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:
|
||||
resolution: {integrity: sha512-MC3svxuvIj0MRpFlGHxLS6vPyIdbTr2KKPEW46kCoCXw2ktb4NTkpkPBI/lSP/FoNXLCBJ0mrnUi1OoZxtpW1Q==}
|
||||
dependencies:
|
||||
|
|
Loading…
Reference in a new issue