chore(sqllab): Remove functionNames from sqlLab state (#24026)
This commit is contained in:
parent
8e45af43e1
commit
779b372d89
|
|
@ -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>;
|
||||||
|
|
|
||||||
|
|
@ -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.'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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: '{}',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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[];
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
});
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue