Skip to content

Commit

Permalink
fix(sqllab): Replace autocomplete logic by a hook (#24677)
Browse files Browse the repository at this point in the history
(cherry picked from commit 7750517)
  • Loading branch information
justinpark authored and michael-s-molina committed Jul 26, 2023
1 parent d86ae30 commit 84488b7
Show file tree
Hide file tree
Showing 9 changed files with 541 additions and 186 deletions.
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

0 comments on commit 84488b7

Please sign in to comment.