chore(sqllab): Remove functionNames from sqlLab state (#24026)

This commit is contained in:
JUST.in DO IT 2023-05-23 10:42:00 -07:00 committed by GitHub
parent 8e45af43e1
commit 779b372d89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 161 additions and 63 deletions

View File

@ -73,7 +73,7 @@ export function createWrapper(options?: Options) {
result = <DndProvider backend={HTML5Backend}>{result}</DndProvider>; result = <DndProvider backend={HTML5Backend}>{result}</DndProvider>;
} }
if (useRedux) { if (useRedux || store) {
const mockStore = const mockStore =
store ?? createStore(initialState, reducers || reducerIndex); store ?? createStore(initialState, reducers || reducerIndex);
result = <Provider store={mockStore}>{result}</Provider>; result = <Provider store={mockStore}>{result}</Provider>;

View File

@ -1555,34 +1555,3 @@ export function createCtasDatasource(vizOptions) {
}); });
}; };
} }
export function queryEditorSetFunctionNames(queryEditor, dbId) {
return function (dispatch) {
return SupersetClient.get({
endpoint: encodeURI(`/api/v1/database/${dbId}/function_names/`),
})
.then(({ json }) =>
dispatch({
type: QUERY_EDITOR_SET_FUNCTION_NAMES,
queryEditor,
functionNames: json.function_names,
}),
)
.catch(err => {
if (err.status === 404) {
// for databases that have been deleted, just reset the function names
dispatch({
type: QUERY_EDITOR_SET_FUNCTION_NAMES,
queryEditor,
functionNames: [],
});
} else {
dispatch(
addDangerToast(
t('An error occurred while fetching function names.'),
),
);
}
});
};
}

View File

@ -18,14 +18,14 @@
*/ */
import React, { useState, useEffect, useRef, useMemo } from 'react'; import React, { useState, useEffect, useRef, useMemo } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { css, styled, usePrevious } from '@superset-ui/core'; import { css, styled, usePrevious, t } from '@superset-ui/core';
import { areArraysShallowEqual } from 'src/reduxUtils'; import { areArraysShallowEqual } from 'src/reduxUtils';
import sqlKeywords from 'src/SqlLab/utils/sqlKeywords'; import sqlKeywords from 'src/SqlLab/utils/sqlKeywords';
import { import {
queryEditorSetSelectedText, queryEditorSetSelectedText,
queryEditorSetFunctionNames,
addTable, addTable,
addDangerToast,
} from 'src/SqlLab/actions/sqlLab'; } from 'src/SqlLab/actions/sqlLab';
import { import {
SCHEMA_AUTOCOMPLETE_SCORE, SCHEMA_AUTOCOMPLETE_SCORE,
@ -40,6 +40,7 @@ import {
} from 'src/components/AsyncAceEditor'; } from 'src/components/AsyncAceEditor';
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor'; import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
import { useSchemas, useTables } from 'src/hooks/apiResources'; import { useSchemas, useTables } from 'src/hooks/apiResources';
import { useDatabaseFunctionsQuery } from 'src/hooks/apiResources/databaseFunctions';
type HotKey = { type HotKey = {
key: string; key: string;
@ -95,7 +96,6 @@ const AceEditorWrapper = ({
'id', 'id',
'dbId', 'dbId',
'sql', 'sql',
'functionNames',
'validationResult', 'validationResult',
'schema', 'schema',
]); ]);
@ -109,8 +109,20 @@ const AceEditorWrapper = ({
}), }),
}); });
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 ?? ''; const currentSql = queryEditor.sql ?? '';
const functionNames = queryEditor.functionNames ?? [];
// Loading schema, table and column names as auto-completable words // Loading schema, table and column names as auto-completable words
const { schemas, schemaWords } = useMemo( const { schemas, schemaWords } = useMemo(
@ -139,9 +151,6 @@ const AceEditorWrapper = ({
useEffect(() => { useEffect(() => {
// Making sure no text is selected from previous mount // Making sure no text is selected from previous mount
dispatch(queryEditorSetSelectedText(queryEditor, null)); dispatch(queryEditorSetSelectedText(queryEditor, null));
if (queryEditor.dbId) {
dispatch(queryEditorSetFunctionNames(queryEditor, queryEditor.dbId));
}
setAutoCompleter(); setAutoCompleter();
}, []); }, []);
@ -238,7 +247,7 @@ const AceEditorWrapper = ({
meta: 'column', meta: 'column',
})); }));
const functionWords = functionNames.map(func => ({ const functionWords = (functionNames ?? []).map(func => ({
name: func, name: func,
value: func, value: func,
score: SQL_FUNCTIONS_AUTOCOMPLETE_SCORE, score: SQL_FUNCTIONS_AUTOCOMPLETE_SCORE,

View File

@ -29,7 +29,6 @@ import querystring from 'query-string';
import { import {
queryEditorSetDb, queryEditorSetDb,
queryEditorSetFunctionNames,
addTable, addTable,
removeTables, removeTables,
collapseTable, collapseTable,
@ -142,7 +141,6 @@ const SqlEditorLeftBar = ({
const onDbChange = ({ id: dbId }: { id: number }) => { const onDbChange = ({ id: dbId }: { id: number }) => {
setEmptyState(false); setEmptyState(false);
dispatch(queryEditorSetDb(queryEditor, dbId)); dispatch(queryEditorSetDb(queryEditor, dbId));
dispatch(queryEditorSetFunctionNames(queryEditor, dbId));
}; };
const selectedTableNames = useMemo( const selectedTableNames = useMemo(

View File

@ -185,7 +185,6 @@ export const defaultQueryEditor = {
name: 'Untitled Query 1', name: 'Untitled Query 1',
schema: 'main', schema: 'main',
remoteId: null, remoteId: null,
functionNames: [],
hideLeftBar: false, hideLeftBar: false,
templateParams: '{}', templateParams: '{}',
}; };

View File

@ -56,7 +56,6 @@ export default function getInitialState({
autorun: false, autorun: false,
templateParams: null, templateParams: null,
dbId: defaultDbId, dbId: defaultDbId,
functionNames: [],
queryLimit: common.conf.DEFAULT_SQLLAB_LIMIT, queryLimit: common.conf.DEFAULT_SQLLAB_LIMIT,
validationResult: { validationResult: {
id: null, id: null,
@ -87,7 +86,6 @@ export default function getInitialState({
autorun: activeTab.autorun, autorun: activeTab.autorun,
templateParams: activeTab.template_params || undefined, templateParams: activeTab.template_params || undefined,
dbId: activeTab.database_id, dbId: activeTab.database_id,
functionNames: [],
schema: activeTab.schema, schema: activeTab.schema,
queryLimit: activeTab.query_limit, queryLimit: activeTab.query_limit,
validationResult: { validationResult: {

View File

@ -563,18 +563,6 @@ export default function sqlLabReducer(state = {}, action) {
), ),
}; };
}, },
[actions.QUERY_EDITOR_SET_FUNCTION_NAMES]() {
return {
...state,
...alterUnsavedQueryEditorState(
state,
{
functionNames: action.functionNames,
},
action.queryEditor.id,
),
};
},
[actions.QUERY_EDITOR_SET_SCHEMA]() { [actions.QUERY_EDITOR_SET_SCHEMA]() {
return { return {
...state, ...state,

View File

@ -209,14 +209,15 @@ describe('sqlLabReducer', () => {
newState = sqlLabReducer(newState, action); newState = sqlLabReducer(newState, action);
expect(newState.unsavedQueryEditor.sql).toBe(expectedSql); expect(newState.unsavedQueryEditor.sql).toBe(expectedSql);
const interceptedAction = { const interceptedAction = {
type: actions.QUERY_EDITOR_SET_FUNCTION_NAMES, type: actions.QUERY_EDITOR_PERSIST_HEIGHT,
queryEditor: newState.queryEditors[0], queryEditor: newState.queryEditors[0],
functionNames: ['func1', 'func2'], northPercent: 46,
southPercent: 54,
}; };
newState = sqlLabReducer(newState, interceptedAction); newState = sqlLabReducer(newState, interceptedAction);
expect(newState.unsavedQueryEditor.sql).toBe(expectedSql); expect(newState.unsavedQueryEditor.sql).toBe(expectedSql);
expect(newState.queryEditors[0].functionNames).toBe( expect(newState.queryEditors[0].northPercent).toBe(
interceptedAction.functionNames, interceptedAction.northPercent,
); );
}); });
}); });

View File

@ -39,7 +39,6 @@ export interface QueryEditor {
autorun: boolean; autorun: boolean;
sql: string; sql: string;
remoteId: number | null; remoteId: number | null;
functionNames: string[];
validationResult?: { validationResult?: {
completed: boolean; completed: boolean;
errors: SupersetError[]; errors: SupersetError[];

View File

@ -0,0 +1,92 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import fetchMock from 'fetch-mock';
import { act, renderHook } from '@testing-library/react-hooks';
import {
createWrapper,
defaultStore as store,
} from 'spec/helpers/testing-library';
import { api } from 'src/hooks/apiResources/queryApi';
import { useDatabaseFunctionsQuery } from './databaseFunctions';
const fakeApiResult = {
function_names: ['abs', 'avg', 'sum'],
};
const expectedResult = fakeApiResult.function_names;
const expectDbId = 'db1';
const dbFunctionNamesApiRoute = `glob:*/api/v1/database/${expectDbId}/function_names/`;
afterEach(() => {
fetchMock.reset();
act(() => {
store.dispatch(api.util.resetApiState());
});
});
beforeEach(() => {
fetchMock.get(dbFunctionNamesApiRoute, fakeApiResult);
});
test('returns api response mapping json result', async () => {
const { result, waitFor } = renderHook(
() =>
useDatabaseFunctionsQuery({
dbId: expectDbId,
}),
{
wrapper: createWrapper({
useRedux: true,
store,
}),
},
);
await waitFor(() =>
expect(fetchMock.calls(dbFunctionNamesApiRoute).length).toBe(1),
);
expect(result.current.data).toEqual(expectedResult);
expect(fetchMock.calls(dbFunctionNamesApiRoute).length).toBe(1);
act(() => {
result.current.refetch();
});
await waitFor(() =>
expect(fetchMock.calls(dbFunctionNamesApiRoute).length).toBe(2),
);
expect(result.current.data).toEqual(expectedResult);
});
test('returns cached data without api request', async () => {
const { result, waitFor, rerender } = renderHook(
() =>
useDatabaseFunctionsQuery({
dbId: expectDbId,
}),
{
wrapper: createWrapper({
store,
useRedux: true,
}),
},
);
await waitFor(() => expect(result.current.data).toEqual(expectedResult));
expect(fetchMock.calls(dbFunctionNamesApiRoute).length).toBe(1);
rerender();
await waitFor(() => expect(result.current.data).toEqual(expectedResult));
expect(fetchMock.calls(dbFunctionNamesApiRoute).length).toBe(1);
});

View File

@ -0,0 +1,45 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { api } from './queryApi';
export type FetchDataFunctionsQueryParams = {
dbId?: string | number;
};
type FunctionNamesResponse = {
json: {
function_names: string[];
};
response: Response;
};
const databaseFunctionApi = api.injectEndpoints({
endpoints: builder => ({
databaseFunctions: builder.query<string[], FetchDataFunctionsQueryParams>({
providesTags: ['DatabaseFunctions'],
query: ({ dbId }) => ({
endpoint: `/api/v1/database/${dbId}/function_names/`,
transformResponse: ({ json }: FunctionNamesResponse) =>
json.function_names,
}),
}),
}),
});
export const { useDatabaseFunctionsQuery } = databaseFunctionApi;

View File

@ -65,7 +65,7 @@ export const supersetClientQuery: BaseQueryFn<
export const api = createApi({ export const api = createApi({
reducerPath: 'queryApi', reducerPath: 'queryApi',
tagTypes: ['Schemas', 'Tables'], tagTypes: ['Schemas', 'Tables', 'DatabaseFunctions'],
endpoints: () => ({}), endpoints: () => ({}),
baseQuery: supersetClientQuery, baseQuery: supersetClientQuery,
}); });