Skip to content
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
26 changes: 12 additions & 14 deletions superset-frontend/src/SqlLab/actions/sqlLab.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ export const QUERY_EDITOR_SET_FUNCTION_NAMES =
export const QUERY_EDITOR_PERSIST_HEIGHT = 'QUERY_EDITOR_PERSIST_HEIGHT';
export const QUERY_EDITOR_TOGGLE_LEFT_BAR = 'QUERY_EDITOR_TOGGLE_LEFT_BAR';
export const MIGRATE_QUERY_EDITOR = 'MIGRATE_QUERY_EDITOR';
export const MIGRATE_TAB_HISTORY = 'MIGRATE_TAB_HISTORY';
export const MIGRATE_TABLE = 'MIGRATE_TABLE';
export const MIGRATE_QUERY = 'MIGRATE_QUERY';

Expand Down Expand Up @@ -391,7 +390,7 @@ export function runQueryFromSqlEditor(
const query = {
dbId: qe.dbId,
sql: qe.selectedText || qe.sql,
sqlEditorId: qe.id,
sqlEditorId: qe.tabViewId ?? qe.id,
tab: qe.name,
catalog: qe.catalog,
schema: qe.schema,
Expand Down Expand Up @@ -499,26 +498,21 @@ export function syncQueryEditor(queryEditor) {
.then(({ json }) => {
const newQueryEditor = {
...queryEditor,
id: json.id.toString(),
inLocalStorage: false,
loaded: true,
tabViewId: json.id.toString(),
};
dispatch({
type: MIGRATE_QUERY_EDITOR,
oldQueryEditor: queryEditor,
newQueryEditor,
});
dispatch({
type: MIGRATE_TAB_HISTORY,
oldId: queryEditor.id,
newId: newQueryEditor.id,
});
return Promise.all([
...localStorageTables.map(table =>
migrateTable(table, newQueryEditor.id, dispatch),
migrateTable(table, newQueryEditor.tabViewId, dispatch),
),
...localStorageQueries.map(query =>
migrateQuery(query.id, newQueryEditor.id, dispatch),
migrateQuery(query.id, newQueryEditor.tabViewId, dispatch),
),
]);
})
Expand Down Expand Up @@ -685,8 +679,9 @@ export function setTables(tableSchemas) {

export function fetchQueryEditor(queryEditor, displayLimit) {
return function (dispatch) {
const queryEditorId = queryEditor.tabViewId ?? queryEditor.id;
SupersetClient.get({
endpoint: encodeURI(`/tabstateview/${queryEditor.id}`),
endpoint: encodeURI(`/tabstateview/${queryEditorId}`),
})
.then(({ json }) => {
const loadedQueryEditor = {
Expand Down Expand Up @@ -756,10 +751,11 @@ export function removeAllOtherQueryEditors(queryEditor) {

export function removeQuery(query) {
return function (dispatch) {
const queryEditorId = query.sqlEditorId ?? query.id;
const sync = isFeatureEnabled(FeatureFlag.SqllabBackendPersistence)
? SupersetClient.delete({
endpoint: encodeURI(
`/tabstateview/${query.sqlEditorId}/query/${query.id}`,
`/tabstateview/${queryEditorId}/query/${query.id}`,
),
})
: Promise.resolve();
Expand Down Expand Up @@ -839,9 +835,10 @@ export function saveQuery(query, clientId) {

export const addSavedQueryToTabState =
(queryEditor, savedQuery) => dispatch => {
const queryEditorId = queryEditor.tabViewId ?? queryEditor.id;
const sync = isFeatureEnabled(FeatureFlag.SqllabBackendPersistence)
? SupersetClient.put({
endpoint: `/tabstateview/${queryEditor.id}`,
endpoint: `/tabstateview/${queryEditorId}`,
postPayload: { saved_query_id: savedQuery.remoteId },
})
: Promise.resolve();
Expand Down Expand Up @@ -889,9 +886,10 @@ export function queryEditorSetAndSaveSql(targetQueryEditor, sql, queryId) {
const queryEditor = getUpToDateQuery(getState(), targetQueryEditor);
// saved query and set tab state use this action
dispatch(queryEditorSetSql(queryEditor, sql, queryId));
const queryEditorId = queryEditor.tabViewId ?? queryEditor.id;
if (isFeatureEnabled(FeatureFlag.SqllabBackendPersistence)) {
return SupersetClient.put({
endpoint: encodeURI(`/tabstateview/${queryEditor.id}`),
endpoint: encodeURI(`/tabstateview/${queryEditorId}`),
postPayload: { sql, latest_query_id: queryId },
}).catch(() =>
dispatch(
Expand Down
7 changes: 1 addition & 6 deletions superset-frontend/src/SqlLab/actions/sqlLab.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1252,16 +1252,11 @@ describe('async actions', () => {
// new qe has a different id
newQueryEditor: {
...oldQueryEditor,
id: '1',
tabViewId: '1',
inLocalStorage: false,
loaded: true,
},
},
{
type: actions.MIGRATE_TAB_HISTORY,
newId: '1',
oldId: 'abcd',
},
{
type: actions.MIGRATE_TABLE,
oldTable: tables[0],
Expand Down
21 changes: 18 additions & 3 deletions superset-frontend/src/SqlLab/components/EditorAutoSync/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { useRef, useEffect, FC } from 'react';
import { useRef, useEffect, FC, useMemo } from 'react';

import { useDispatch, useSelector } from 'react-redux';
import { logging } from '@superset-ui/core';
Expand Down Expand Up @@ -69,6 +69,17 @@ const EditorAutoSync: FC = () => {
const queryEditors = useSelector<SqlLabRootState, QueryEditor[]>(
state => state.sqlLab.queryEditors,
);
const queryEditorsById = useMemo(
() =>
queryEditors.reduce(
(acc, queryEditor) => {
acc[queryEditor.id] = queryEditor;
return acc;
},
{} as Record<string, QueryEditor>,
),
[queryEditors],
);
const unsavedQueryEditor = useSelector<SqlLabRootState, UnsavedQueryEditor>(
state => state.sqlLab.unsavedQueryEditor,
);
Expand Down Expand Up @@ -120,7 +131,10 @@ const EditorAutoSync: FC = () => {
!queryEditors.find(({ id }) => id === currentQueryEditorId)
?.inLocalStorage
) {
updateCurrentSqlEditor(currentQueryEditorId).then(() => {
const queryEditorId =
queryEditorsById[currentQueryEditorId]?.tabViewId ??
currentQueryEditorId;
updateCurrentSqlEditor(queryEditorId).then(() => {
dispatch(setLastUpdatedActiveTab(currentQueryEditorId));
});
}
Expand All @@ -129,7 +143,8 @@ const EditorAutoSync: FC = () => {
const syncDeletedQueryEditor = useEffectEvent(() => {
if (Object.keys(destroyedQueryEditors).length > 0) {
Object.keys(destroyedQueryEditors).forEach(id => {
deleteSqlEditor(id)
const queryEditorId = queryEditorsById[id]?.tabViewId ?? id;
deleteSqlEditor(queryEditorId)
.then(() => {
dispatch(clearDestoryedQueryEditor(id));
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@ import fetchMock from 'fetch-mock';
import { FeatureFlag, isFeatureEnabled, QueryState } from '@superset-ui/core';
import { render, screen, waitFor } from 'spec/helpers/testing-library';
import QueryHistory from 'src/SqlLab/components/QueryHistory';
import { initialState } from 'src/SqlLab/fixtures';
import {
initialState,
defaultQueryEditor,
extraQueryEditor3,
} from 'src/SqlLab/fixtures';

const mockedProps = {
queryEditorId: 123,
queryEditorId: defaultQueryEditor.id,
displayLimit: 1000,
latestQueryId: 'yhMUZCGb',
};
Expand Down Expand Up @@ -77,6 +81,8 @@ const setup = (overrides = {}) => (
<QueryHistory {...mockedProps} {...overrides} />
);

afterEach(() => fetchMock.reset());

test('Renders an empty state for query history', () => {
render(setup(), { useRedux: true, initialState });

Expand All @@ -102,3 +108,28 @@ test('fetches the query history when the persistence mode is enabled', async ()
expect(queryResultText).toBeInTheDocument();
isFeatureEnabledMock.mockClear();
});

test('fetches the query history by the tabViewId', async () => {
const isFeatureEnabledMock = mockedIsFeatureEnabled.mockImplementation(
featureFlag => featureFlag === FeatureFlag.SqllabBackendPersistence,
);

const editorQueryApiRoute = `glob:*/api/v1/query/?q=*sql_editor_id*${extraQueryEditor3.tabViewId}*`;
fetchMock.get(editorQueryApiRoute, fakeApiResult);
render(setup({ queryEditorId: extraQueryEditor3.id }), {
useRedux: true,
initialState: {
...initialState,
sqlLab: {
...initialState.sqlLab,
queryEditors: [...initialState.sqlLab.queryEditors, extraQueryEditor3],
},
},
});
await waitFor(() =>
expect(fetchMock.calls(editorQueryApiRoute).length).toBe(1),
);
const queryResultText = screen.getByText(fakeApiResult.result[0].rows);
expect(queryResultText).toBeInTheDocument();
isFeatureEnabledMock.mockClear();
});
13 changes: 9 additions & 4 deletions superset-frontend/src/SqlLab/components/QueryHistory/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import QueryTable from 'src/SqlLab/components/QueryTable';
import { SqlLabRootState } from 'src/SqlLab/types';
import { useEditorQueriesQuery } from 'src/hooks/apiResources/queries';
import useEffectEvent from 'src/hooks/useEffectEvent';
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';

interface QueryHistoryProps {
queryEditorId: string | number;
Expand Down Expand Up @@ -63,6 +64,10 @@ const QueryHistory = ({
displayLimit,
latestQueryId,
}: QueryHistoryProps) => {
const { id, tabViewId } = useQueryEditor(String(queryEditorId), [
'tabViewId',
]);
const editorId = tabViewId ?? id;
const [ref, hasReachedBottom] = useInView({ threshold: 0 });
const [pageIndex, setPageIndex] = useState(0);
const queries = useSelector(
Expand All @@ -74,7 +79,7 @@ const QueryHistory = ({
isLoading,
isFetching,
} = useEditorQueriesQuery(
{ editorId: `${queryEditorId}`, pageIndex },
{ editorId, pageIndex },
{
skip: !isFeatureEnabled(FeatureFlag.SqllabBackendPersistence),
},
Expand All @@ -87,12 +92,12 @@ const QueryHistory = ({
queries,
data.result.map(({ id }) => id),
),
queryEditorId,
editorId,
)
.concat(data.result)
.reverse()
: getEditorQueries(queries, queryEditorId),
[queries, data, queryEditorId],
: getEditorQueries(queries, editorId),
[queries, data, editorId],
);

const loadNext = useEffectEvent(() => {
Expand Down
10 changes: 5 additions & 5 deletions superset-frontend/src/SqlLab/components/SouthPane/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { removeTables, setActiveSouthPaneTab } from 'src/SqlLab/actions/sqlLab';
import { Label } from '@superset-ui/core/components';
import { Icons } from '@superset-ui/core/components/Icons';
import { SqlLabRootState } from 'src/SqlLab/types';
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
import QueryHistory from '../QueryHistory';
import {
STATUS_OPTIONS,
Expand Down Expand Up @@ -96,6 +97,8 @@ const SouthPane = ({
displayLimit,
defaultQueryLimit,
}: SouthPaneProps) => {
const { id, tabViewId } = useQueryEditor(queryEditorId, ['tabViewId']);
const editorId = tabViewId ?? id;
const theme = useTheme();
const dispatch = useDispatch();
const { offline, tables } = useSelector(
Expand All @@ -111,11 +114,8 @@ const SouthPane = ({
) ?? 'Results';

const pinnedTables = useMemo(
() =>
tables.filter(
({ queryEditorId: qeId }) => String(queryEditorId) === qeId,
),
[queryEditorId, tables],
() => tables.filter(({ queryEditorId: qeId }) => String(editorId) === qeId),
[editorId, tables],
);
const pinnedTableKeys = useMemo(
() =>
Expand Down
8 changes: 8 additions & 0 deletions superset-frontend/src/SqlLab/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,14 @@ export const extraQueryEditor2 = {
name: 'Untitled Query 3',
};

export const extraQueryEditor3 = {
...defaultQueryEditor,
id: 'kvk23',
sql: '',
name: 'Untitled Query 4',
tabViewId: 37,
};

export const queries = [
{
dbId: 1,
Expand Down
14 changes: 12 additions & 2 deletions superset-frontend/src/SqlLab/hooks/useQueryEditor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,28 @@
* under the License.
*/
import { pick } from 'lodash';
import { useMemo } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { SqlLabRootState, QueryEditor } from 'src/SqlLab/types';

export default function useQueryEditor<T extends keyof QueryEditor>(
sqlEditorId: string,
attributes: ReadonlyArray<T>,
) {
const queryEditors = useSelector<SqlLabRootState, QueryEditor[]>(
({ sqlLab: { queryEditors } }) => queryEditors,
shallowEqual,
);
const queryEditorsById = useMemo(
() => Object.fromEntries(queryEditors.map(editor => [editor.id, editor])),
[queryEditors.map(({ id }) => id).join(',')],
);

return useSelector<SqlLabRootState, Pick<QueryEditor, T | 'id'>>(
({ sqlLab: { unsavedQueryEditor, queryEditors } }) =>
({ sqlLab: { unsavedQueryEditor } }) =>
pick(
{
...queryEditors.find(({ id }) => id === sqlEditorId),
...queryEditorsById[sqlEditorId],
...(sqlEditorId === unsavedQueryEditor?.id && unsavedQueryEditor),
},
['id'].concat(attributes),
Expand Down
11 changes: 6 additions & 5 deletions superset-frontend/src/SqlLab/reducers/getInitialState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,22 +171,23 @@ export default function getInitialState({
// add query editors and tables to state with a special flag so they can
// be migrated if the `SQLLAB_BACKEND_PERSISTENCE` feature flag is on
sqlLab.queryEditors.forEach(qe => {
const hasConflictFromBackend = Boolean(queryEditors[qe.id]);
const unsavedUpdatedAt = queryEditors[qe.id]?.updatedAt;
const sqlEditorId = qe.tabViewId ?? qe.id;
const hasConflictFromBackend = Boolean(queryEditors[sqlEditorId]);
const unsavedUpdatedAt = queryEditors[sqlEditorId]?.updatedAt;
const hasUnsavedUpdateSinceLastSave =
qe.updatedAt &&
(!unsavedUpdatedAt || qe.updatedAt > unsavedUpdatedAt);
const cachedQueryEditor: UnsavedQueryEditor =
!hasConflictFromBackend || hasUnsavedUpdateSinceLastSave ? qe : {};
queryEditors = {
...queryEditors,
[qe.id]: {
...queryEditors[qe.id],
[sqlEditorId]: {
...queryEditors[sqlEditorId],
...cachedQueryEditor,
name:
cachedQueryEditor.title ||
cachedQueryEditor.name ||
queryEditors[qe.id]?.name,
queryEditors[sqlEditorId]?.name,
...(cachedQueryEditor.id &&
unsavedQueryEditor.id === qe.id &&
unsavedQueryEditor),
Expand Down
Loading
Loading