fix(editor): Fix SQL editors not always re-rendering when query changes (#8621)

This commit is contained in:
Elias Meire 2024-02-16 11:26:43 +01:00 committed by GitHub
parent 8c665e4d20
commit 8e9d3106a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 56 additions and 28 deletions

View file

@ -27,6 +27,26 @@ describe('SQL editors', () => {
ndv.getters.sqlEditorContainer().should('contain', 'SELECT * FROM `testTable` LIMIT 10'); ndv.getters.sqlEditorContainer().should('contain', 'SELECT * FROM `testTable` LIMIT 10');
}); });
it('should update expression output dropdown as the query is edited', () => {
workflowPage.actions.addInitialNodeToCanvas('MySQL', {
action: 'Execute a SQL query',
});
ndv.actions.close();
workflowPage.actions.openNode('When clicking "Test workflow"');
ndv.actions.setPinnedData([{ table: 'test_table' }]);
ndv.actions.close();
workflowPage.actions.openNode('MySQL');
ndv.getters
.sqlEditorContainer()
.find('.cm-content')
.type('SELECT * FROM {{ $json.table }}', { parseSpecialCharSequences: false });
workflowPage.getters
.inlineExpressionEditorOutput()
.should('have.text', 'SELECT * FROM test_table');
});
it('should not push NDV header out with a lot of code in Postgres editor', () => { it('should not push NDV header out with a lot of code in Postgres editor', () => {
workflowPage.actions.addInitialNodeToCanvas('Postgres', { workflowPage.actions.addInitialNodeToCanvas('Postgres', {
action: 'Execute a SQL query', action: 'Execute a SQL query',

View file

@ -43,6 +43,7 @@ import { enterKeyMap, tabKeyMap } from '../CodeNodeEditor/baseExtensions';
import { codeNodeEditorTheme } from '../CodeNodeEditor/theme'; import { codeNodeEditorTheme } from '../CodeNodeEditor/theme';
import type { Range, Section } from './types'; import type { Range, Section } from './types';
import { nonTakenRanges } from './utils'; import { nonTakenRanges } from './utils';
import { isEqual } from 'lodash-es';
export default defineComponent({ export default defineComponent({
name: 'HtmlEditor', name: 'HtmlEditor',
@ -79,6 +80,16 @@ export default defineComponent({
editorState: null as EditorState | null, editorState: null as EditorState | null,
}; };
}, },
watch: {
displayableSegments(segments, newSegments) {
if (isEqual(segments, newSegments)) return;
highlighter.removeColor(this.editor, this.plaintextSegments);
highlighter.addColor(this.editor, this.resolvableSegments);
this.$emit('update:modelValue', this.editor?.state.doc.toString());
},
},
computed: { computed: {
doc(): string { doc(): string {
return this.editor.state.doc.toString(); return this.editor.state.doc.toString();
@ -124,13 +135,10 @@ export default defineComponent({
EditorView.editable.of(!this.isReadOnly), EditorView.editable.of(!this.isReadOnly),
EditorState.readOnly.of(this.isReadOnly), EditorState.readOnly.of(this.isReadOnly),
EditorView.updateListener.of((viewUpdate: ViewUpdate) => { EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
if (!viewUpdate.docChanged) return; if (!this.editor || !viewUpdate.docChanged) return;
this.getHighlighter()?.removeColor(this.editor, this.htmlSegments); // Force segments value update by keeping track of editor state
this.getHighlighter()?.addColor(this.editor, this.resolvableSegments); this.editorState = this.editor.state;
// eslint-disable-next-line @typescript-eslint/no-base-to-string
this.$emit('update:modelValue', this.editor?.state.doc.toString());
}), }),
]; ];
}, },

View file

@ -1,6 +1,6 @@
<template> <template>
<div v-on-click-outside="onBlur" :class="$style.sqlEditor"> <div v-on-click-outside="onBlur" :class="$style.sqlEditor">
<div ref="sqlEditor" data-test-id="sql-editor-container"></div> <div :class="$style.codemirror" ref="sqlEditor" data-test-id="sql-editor-container"></div>
<slot name="suffix" /> <slot name="suffix" />
<InlineExpressionEditorOutput <InlineExpressionEditorOutput
v-if="!fillParent" v-if="!fillParent"
@ -49,6 +49,7 @@ import {
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { enterKeyMap, tabKeyMap } from '../CodeNodeEditor/baseExtensions'; import { enterKeyMap, tabKeyMap } from '../CodeNodeEditor/baseExtensions';
import { codeNodeEditorTheme } from '../CodeNodeEditor/theme'; import { codeNodeEditorTheme } from '../CodeNodeEditor/theme';
import { isEqual } from 'lodash-es';
const SQL_DIALECTS = { const SQL_DIALECTS = {
StandardSQL, StandardSQL,
@ -171,12 +172,10 @@ export default defineComponent({
dropCursor(), dropCursor(),
bracketMatching(), bracketMatching(),
EditorView.updateListener.of((viewUpdate: ViewUpdate) => { EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
if (!viewUpdate.docChanged || !this.editor) return; if (!this.editor || !viewUpdate.docChanged) return;
highlighter.removeColor(this.editor as EditorView, this.plaintextSegments); // Force segments value update by keeping track of editor state
highlighter.addColor(this.editor as EditorView, this.resolvableSegments); this.editorState = this.editor.state;
this.$emit('update:modelValue', this.editor?.state.doc.toString());
}), }),
); );
} }
@ -184,18 +183,13 @@ export default defineComponent({
}, },
}, },
watch: { watch: {
'ndvStore.ndvInputData'() { displayableSegments(segments, newSegments) {
this.editor?.dispatch({ if (isEqual(segments, newSegments)) return;
changes: {
from: 0,
to: this.editor.state.doc.length,
insert: this.modelValue,
},
});
setTimeout(() => { highlighter.removeColor(this.editor, this.plaintextSegments);
this.editor?.contentDOM.blur(); highlighter.addColor(this.editor, this.resolvableSegments);
});
this.$emit('update:modelValue', this.editor?.state.doc.toString());
}, },
}, },
mounted() { mounted() {
@ -244,9 +238,9 @@ export default defineComponent({
.sqlEditor { .sqlEditor {
position: relative; position: relative;
height: 100%; height: 100%;
}
& > div { .codemirror {
height: 100%; height: 100%;
}
} }
</style> </style>

View file

@ -13,6 +13,7 @@ import type { EditorView } from '@codemirror/view';
import type { TargetItem } from '@/Interface'; import type { TargetItem } from '@/Interface';
import type { Html, Plaintext, RawSegment, Resolvable, Segment } from '@/types/expressions'; import type { Html, Plaintext, RawSegment, Resolvable, Segment } from '@/types/expressions';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers'; import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
import { isEqual } from 'lodash-es';
export const expressionManager = defineComponent({ export const expressionManager = defineComponent({
props: { props: {
@ -98,12 +99,17 @@ export const expressionManager = defineComponent({
if (skipSegments.includes(node.type.name)) return; if (skipSegments.includes(node.type.name)) return;
rawSegments.push({ const newSegment: RawSegment = {
from: node.from, from: node.from,
to: node.to, to: node.to,
text, text,
token: node.type.name === 'Resolvable' ? 'Resolvable' : 'Plaintext', token: node.type.name === 'Resolvable' ? 'Resolvable' : 'Plaintext',
}); };
// Avoid duplicates
if (isEqual(newSegment, rawSegments.at(-1))) return;
rawSegments.push(newSegment);
}); });
return rawSegments.reduce<Segment[]>((acc, segment) => { return rawSegments.reduce<Segment[]>((acc, segment) => {