mirror of
https://github.com/apache/superset.git
synced 2026-05-06 16:34:32 +00:00
feat(trino): support early cancellation of queries (#22498)
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import shortid from 'shortid';
|
||||
import { t, SupersetClient } from '@superset-ui/core';
|
||||
import { SupersetClient, t } from '@superset-ui/core';
|
||||
import invert from 'lodash/invert';
|
||||
import mapKeys from 'lodash/mapKeys';
|
||||
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
|
||||
@@ -229,11 +229,13 @@ export function startQuery(query) {
|
||||
|
||||
export function querySuccess(query, results) {
|
||||
return function (dispatch) {
|
||||
const sqlEditorId = results?.query?.sqlEditorId;
|
||||
const sync =
|
||||
sqlEditorId &&
|
||||
!query.isDataPreview &&
|
||||
isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE)
|
||||
? SupersetClient.put({
|
||||
endpoint: encodeURI(`/tabstateview/${results.query.sqlEditorId}`),
|
||||
endpoint: encodeURI(`/tabstateview/${sqlEditorId}`),
|
||||
postPayload: { latest_query_id: query.id },
|
||||
})
|
||||
: Promise.resolve();
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
initialState,
|
||||
queryId,
|
||||
} from 'src/SqlLab/fixtures';
|
||||
import { QueryState } from '@superset-ui/core';
|
||||
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureMockStore(middlewares);
|
||||
@@ -502,6 +503,7 @@ describe('async actions', () => {
|
||||
const results = {
|
||||
data: mockBigNumber,
|
||||
query: { sqlEditorId: 'abcd' },
|
||||
status: QueryState.SUCCESS,
|
||||
query_id: 'efgh',
|
||||
};
|
||||
fetchMock.get(fetchQueryEndpoint, JSON.stringify(results), {
|
||||
@@ -525,6 +527,35 @@ describe('async actions', () => {
|
||||
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't update the tab state in the backend on stoppped query", () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const results = {
|
||||
status: QueryState.STOPPED,
|
||||
query_id: 'efgh',
|
||||
};
|
||||
fetchMock.get(fetchQueryEndpoint, JSON.stringify(results), {
|
||||
overwriteRoutes: true,
|
||||
});
|
||||
const store = mockStore({});
|
||||
const expectedActions = [
|
||||
{
|
||||
type: actions.REQUEST_QUERY_RESULTS,
|
||||
query,
|
||||
},
|
||||
// missing below
|
||||
{
|
||||
type: actions.QUERY_SUCCESS,
|
||||
query,
|
||||
results,
|
||||
},
|
||||
];
|
||||
return store.dispatch(actions.fetchQueryResults(query)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addQueryEditor', () => {
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import ButtonGroup from 'src/components/ButtonGroup';
|
||||
import Alert from 'src/components/Alert';
|
||||
import Button from 'src/components/Button';
|
||||
import shortid from 'shortid';
|
||||
import { styled, t, QueryResponse } from '@superset-ui/core';
|
||||
import { QueryResponse, QueryState, styled, t } from '@superset-ui/core';
|
||||
import { usePrevious } from 'src/hooks/usePrevious';
|
||||
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
||||
import {
|
||||
@@ -43,9 +43,9 @@ import CopyToClipboard from 'src/components/CopyToClipboard';
|
||||
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||
import { prepareCopyToClipboardTabularData } from 'src/utils/common';
|
||||
import {
|
||||
CtasEnum,
|
||||
clearQueryResults,
|
||||
addQueryEditor,
|
||||
clearQueryResults,
|
||||
CtasEnum,
|
||||
fetchQueryResults,
|
||||
reFetchQueryResults,
|
||||
reRunQuery,
|
||||
@@ -387,8 +387,8 @@ const ResultSet = ({
|
||||
let trackingUrl;
|
||||
if (
|
||||
query.trackingUrl &&
|
||||
query.state !== 'success' &&
|
||||
query.state !== 'fetching'
|
||||
query.state !== QueryState.SUCCESS &&
|
||||
query.state !== QueryState.FETCHING
|
||||
) {
|
||||
trackingUrl = (
|
||||
<Button
|
||||
@@ -397,7 +397,9 @@ const ResultSet = ({
|
||||
href={query.trackingUrl}
|
||||
target="_blank"
|
||||
>
|
||||
{query.state === 'running' ? t('Track job') : t('See query details')}
|
||||
{query.state === QueryState.RUNNING
|
||||
? t('Track job')
|
||||
: t('See query details')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -406,11 +408,11 @@ const ResultSet = ({
|
||||
sql = <HighlightedSql sql={query.sql} />;
|
||||
}
|
||||
|
||||
if (query.state === 'stopped') {
|
||||
if (query.state === QueryState.STOPPED) {
|
||||
return <Alert type="warning" message={t('Query was stopped')} />;
|
||||
}
|
||||
|
||||
if (query.state === 'failed') {
|
||||
if (query.state === QueryState.FAILED) {
|
||||
return (
|
||||
<ResultlessStyles>
|
||||
<ErrorMessageWithStackTrace
|
||||
@@ -426,7 +428,7 @@ const ResultSet = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (query.state === 'success' && query.ctas) {
|
||||
if (query.state === QueryState.SUCCESS && query.ctas) {
|
||||
const { tempSchema, tempTable } = query;
|
||||
let object = 'Table';
|
||||
if (query.ctas_method === CtasEnum.VIEW) {
|
||||
@@ -465,7 +467,7 @@ const ResultSet = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (query.state === 'success' && query.results) {
|
||||
if (query.state === QueryState.SUCCESS && query.results) {
|
||||
const { results } = query;
|
||||
// Accounts for offset needed for height of ResultSetRowsReturned component if !limitReached
|
||||
const rowMessageHeight = !limitReached ? 32 : 0;
|
||||
@@ -508,7 +510,7 @@ const ResultSet = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (query.cached || (query.state === 'success' && !query.results)) {
|
||||
if (query.cached || (query.state === QueryState.SUCCESS && !query.results)) {
|
||||
if (query.isDataPreview) {
|
||||
return (
|
||||
<Button
|
||||
|
||||
@@ -53,7 +53,7 @@ const SqlEditorTabHeader: React.FC<Props> = ({ queryEditor }) => {
|
||||
}),
|
||||
shallowEqual,
|
||||
);
|
||||
const queryStatus = useSelector<SqlLabRootState, QueryState>(
|
||||
const queryState = useSelector<SqlLabRootState, QueryState>(
|
||||
({ sqlLab }) => sqlLab.queries[qe.latestQueryId || '']?.state || '',
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
@@ -139,7 +139,7 @@ const SqlEditorTabHeader: React.FC<Props> = ({ queryEditor }) => {
|
||||
</Menu>
|
||||
}
|
||||
/>
|
||||
<TabTitle>{qe.name}</TabTitle> <TabStatusIcon tabState={queryStatus} />{' '}
|
||||
<TabTitle>{qe.name}</TabTitle> <TabStatusIcon tabState={queryState} />{' '}
|
||||
</TabTitleWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { t } from '@superset-ui/core';
|
||||
|
||||
import { QueryState, t } from '@superset-ui/core';
|
||||
import getInitialState from './getInitialState';
|
||||
import * as actions from '../actions/sqlLab';
|
||||
import { now } from '../../utils/dates';
|
||||
@@ -391,7 +390,7 @@ export default function sqlLabReducer(state = {}, action) {
|
||||
},
|
||||
[actions.STOP_QUERY]() {
|
||||
return alterInObject(state, 'queries', action.query, {
|
||||
state: 'stopped',
|
||||
state: QueryState.STOPPED,
|
||||
results: [],
|
||||
});
|
||||
},
|
||||
@@ -405,12 +404,16 @@ export default function sqlLabReducer(state = {}, action) {
|
||||
},
|
||||
[actions.REQUEST_QUERY_RESULTS]() {
|
||||
return alterInObject(state, 'queries', action.query, {
|
||||
state: 'fetching',
|
||||
state: QueryState.FETCHING,
|
||||
});
|
||||
},
|
||||
[actions.QUERY_SUCCESS]() {
|
||||
// prevent race condition were query succeeds shortly after being canceled
|
||||
if (action.query.state === 'stopped') {
|
||||
// prevent race condition where query succeeds shortly after being canceled
|
||||
// or the final result was unsuccessful
|
||||
if (
|
||||
action.query.state === QueryState.STOPPED ||
|
||||
action.results.status !== QueryState.SUCCESS
|
||||
) {
|
||||
return state;
|
||||
}
|
||||
const alts = {
|
||||
@@ -418,7 +421,7 @@ export default function sqlLabReducer(state = {}, action) {
|
||||
progress: 100,
|
||||
results: action.results,
|
||||
rows: action?.results?.query?.rows || 0,
|
||||
state: 'success',
|
||||
state: QueryState.SUCCESS,
|
||||
limitingFactor: action?.results?.query?.limitingFactor,
|
||||
tempSchema: action?.results?.query?.tempSchema,
|
||||
tempTable: action?.results?.query?.tempTable,
|
||||
@@ -434,11 +437,11 @@ export default function sqlLabReducer(state = {}, action) {
|
||||
return alterInObject(state, 'queries', action.query, alts);
|
||||
},
|
||||
[actions.QUERY_FAILED]() {
|
||||
if (action.query.state === 'stopped') {
|
||||
if (action.query.state === QueryState.STOPPED) {
|
||||
return state;
|
||||
}
|
||||
const alts = {
|
||||
state: 'failed',
|
||||
state: QueryState.FAILED,
|
||||
errors: action.errors,
|
||||
errorMessage: action.msg,
|
||||
endDttm: now(),
|
||||
@@ -723,8 +726,8 @@ export default function sqlLabReducer(state = {}, action) {
|
||||
Object.entries(action.alteredQueries).forEach(([id, changedQuery]) => {
|
||||
if (
|
||||
!state.queries.hasOwnProperty(id) ||
|
||||
(state.queries[id].state !== 'stopped' &&
|
||||
state.queries[id].state !== 'failed')
|
||||
(state.queries[id].state !== QueryState.STOPPED &&
|
||||
state.queries[id].state !== QueryState.FAILED)
|
||||
) {
|
||||
if (changedQuery.changedOn > queriesLastUpdate) {
|
||||
queriesLastUpdate = changedQuery.changedOn;
|
||||
@@ -738,8 +741,8 @@ export default function sqlLabReducer(state = {}, action) {
|
||||
// because of async behavior, sql lab may still poll a couple of seconds
|
||||
// when it started fetching or finished rendering results
|
||||
state:
|
||||
currentState === 'success' &&
|
||||
['fetching', 'success'].includes(prevState)
|
||||
currentState === QueryState.SUCCESS &&
|
||||
[QueryState.FETCHING, QueryState.SUCCESS].includes(prevState)
|
||||
? prevState
|
||||
: currentState,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user