Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(sqllab): Replace autocomplete logic by a hook #24677

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion superset-frontend/spec/helpers/testing-library.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type Options = Omit<RenderOptions, 'queries'> & {
store?: Store;
};

const createStore = (initialState: object = {}, reducers: object = {}) =>
export const createStore = (initialState: object = {}, reducers: object = {}) =>
configureStore({
preloadedState: initialState,
reducer: {
Expand Down
6 changes: 3 additions & 3 deletions superset-frontend/src/SqlLab/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { initFeatureFlags, isFeatureEnabled } from 'src/featureFlags';
import { setupStore } from 'src/views/store';
import setupExtensions from 'src/setup/setupExtensions';
import getBootstrapData from 'src/utils/getBootstrapData';
import { api } from 'src/hooks/apiResources/queryApi';
import { tableApiUtil } from 'src/hooks/apiResources/tables';
import getInitialState from './reducers/getInitialState';
import { reducers } from './reducers/index';
import App from './components/App';
Expand Down Expand Up @@ -127,14 +127,14 @@ initialState.sqlLab.tables.forEach(
({ name: table, schema, dbId, persistData }) => {
if (dbId && schema && table && persistData?.columns) {
store.dispatch(
api.util.upsertQueryData(
tableApiUtil.upsertQueryData(
'tableMetadata',
{ dbId, schema, table },
persistData,
),
);
store.dispatch(
api.util.upsertQueryData(
tableApiUtil.upsertQueryData(
'tableExtendedMetadata',
{ dbId, schema, table },
{},
Expand Down
174 changes: 16 additions & 158 deletions superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { useState, useEffect, useRef, useMemo } from 'react';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { css, styled, usePrevious, t } from '@superset-ui/core';
import React, { useState, useEffect, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { css, styled, usePrevious } from '@superset-ui/core';

import { areArraysShallowEqual } from 'src/reduxUtils';
import sqlKeywords from 'src/SqlLab/utils/sqlKeywords';
import {
queryEditorSetSelectedText,
addTable,
addDangerToast,
} from 'src/SqlLab/actions/sqlLab';
import {
SCHEMA_AUTOCOMPLETE_SCORE,
TABLE_AUTOCOMPLETE_SCORE,
COLUMN_AUTOCOMPLETE_SCORE,
SQL_FUNCTIONS_AUTOCOMPLETE_SCORE,
} from 'src/SqlLab/constants';
import {
Editor,
AceCompleterKeyword,
FullSQLEditor as AceEditor,
} from 'src/components/AsyncAceEditor';
import { queryEditorSetSelectedText } from 'src/SqlLab/actions/sqlLab';
import { FullSQLEditor as AceEditor } from 'src/components/AsyncAceEditor';
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
import {
useSchemas,
useTables,
tableEndpoints,
skipToken,
} from 'src/hooks/apiResources';
import { useDatabaseFunctionsQuery } from 'src/hooks/apiResources/databaseFunctions';
import { RootState } from 'src/views/store';
import { useAnnotations } from './useAnnotations';
import { useKeywords } from './useKeywords';

type HotKey = {
key: string;
Expand Down Expand Up @@ -101,68 +78,10 @@ const AceEditorWrapper = ({
'schema',
'templateParams',
]);
const { data: schemaOptions } = useSchemas({
...(autocomplete && { dbId: queryEditor.dbId }),
});
const { data: tableData } = useTables({
...(autocomplete && {
dbId: queryEditor.dbId,
schema: queryEditor.schema,
}),
});

const { data: functionNames, isError } = useDatabaseFunctionsQuery(
{ dbId: queryEditor.dbId },
{ skip: !autocomplete || !queryEditor.dbId },
);

useEffect(() => {
if (isError) {
dispatch(
addDangerToast(t('An error occurred while fetching function names.')),
);
}
}, [dispatch, isError]);

const currentSql = queryEditor.sql ?? '';

// Loading schema, table and column names as auto-completable words
const { schemas, schemaWords } = useMemo(
() => ({
schemas: schemaOptions ?? [],
schemaWords: (schemaOptions ?? []).map(s => ({
name: s.label,
value: s.value,
score: SCHEMA_AUTOCOMPLETE_SCORE,
meta: 'schema',
})),
}),
[schemaOptions],
);
const tables = tableData?.options ?? [];

const columns = useSelector<RootState, string[]>(state => {
const columns = new Set<string>();
tables.forEach(({ value }) => {
tableEndpoints.tableMetadata
.select(
queryEditor.dbId && queryEditor.schema
? {
dbId: queryEditor.dbId,
schema: queryEditor.schema,
table: value,
}
: skipToken,
)(state)
.data?.columns?.forEach(({ name }) => {
columns.add(name);
});
});
return [...columns];
}, shallowEqual);

const [sql, setSql] = useState(currentSql);
const [words, setWords] = useState<AceCompleterKeyword[]>([]);

// The editor changeSelection is called multiple times in a row,
// faster than React reconciliation process, so the selected text
Expand All @@ -173,24 +92,10 @@ const AceEditorWrapper = ({
useEffect(() => {
// Making sure no text is selected from previous mount
dispatch(queryEditorSetSelectedText(queryEditor, null));
setAutoCompleter();
}, []);

const prevTables = usePrevious(tables) ?? [];
const prevSchemas = usePrevious(schemas) ?? [];
const prevColumns = usePrevious(columns) ?? [];
const prevSql = usePrevious(currentSql);

useEffect(() => {
if (
!areArraysShallowEqual(tables, prevTables) ||
!areArraysShallowEqual(schemas, prevSchemas) ||
!areArraysShallowEqual(columns, prevColumns)
) {
setAutoCompleter();
}
}, [tables, schemas, columns]);

useEffect(() => {
if (currentSql !== prevSql) {
setSql(currentSql);
Expand Down Expand Up @@ -243,72 +148,25 @@ const AceEditorWrapper = ({
onChange(text);
};

function setAutoCompleter() {
const tableWords = tables.map(t => {
const tableName = t.value;

return {
name: t.label,
value: tableName,
score: TABLE_AUTOCOMPLETE_SCORE,
meta: 'table',
};
});

const columnWords = columns.map(col => ({
name: col,
value: col,
score: COLUMN_AUTOCOMPLETE_SCORE,
meta: 'column',
}));

const functionWords = (functionNames ?? []).map(func => ({
name: func,
value: func,
score: SQL_FUNCTIONS_AUTOCOMPLETE_SCORE,
meta: 'function',
}));

const completer = {
insertMatch: (editor: Editor, data: any) => {
if (data.meta === 'table') {
dispatch(addTable(queryEditor, data.value, queryEditor.schema));
}

let { caption } = data;
if (data.meta === 'table' && caption.includes(' ')) {
caption = `"${caption}"`;
}

// executing https://github.com/thlorenz/brace/blob/3a00c5d59777f9d826841178e1eb36694177f5e6/ext/language_tools.js#L1448
editor.completer.insertMatch(
`${caption}${['function', 'schema'].includes(data.meta) ? '' : ' '}`,
);
},
};

const words = schemaWords
.concat(tableWords)
.concat(columnWords)
.concat(functionWords)
.concat(sqlKeywords)
.map(word => ({
...word,
completer,
}));

setWords(words);
}
const { data: annotations } = useAnnotations({
dbId: queryEditor.dbId,
schema: queryEditor.schema,
sql: currentSql,
templateParams: queryEditor.templateParams,
});

const keywords = useKeywords(
{
queryEditorId,
dbId: queryEditor.dbId,
schema: queryEditor.schema,
},
!autocomplete,
);

return (
<StyledAceEditor
keywords={words}
keywords={keywords}
onLoad={onEditorLoad}
onBlur={onBlurSql}
height={height}
Expand Down
Loading
Loading