mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
feat(sqllab): Improved query status indicator bar (#36936)
This commit is contained in:
@@ -35,7 +35,9 @@ export function Timer({
|
|||||||
status = 'success',
|
status = 'success',
|
||||||
}: TimerProps) {
|
}: TimerProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [clockStr, setClockStr] = useState('00:00:00.00');
|
const [clockStr, setClockStr] = useState(
|
||||||
|
startTime && endTime ? fDuration(startTime, endTime) : '00:00:00.00',
|
||||||
|
);
|
||||||
const timer = useRef<ReturnType<typeof setInterval>>();
|
const timer = useRef<ReturnType<typeof setInterval>>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -316,6 +316,7 @@ export type Query = {
|
|||||||
errorMessage: string | null;
|
errorMessage: string | null;
|
||||||
extra: {
|
extra: {
|
||||||
progress: string | null;
|
progress: string | null;
|
||||||
|
progress_text?: string;
|
||||||
errors?: SupersetError[];
|
errors?: SupersetError[];
|
||||||
};
|
};
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ export function startQuery(query: Query, runPreviewOnly?: boolean) {
|
|||||||
id: query.id ? query.id : nanoid(11),
|
id: query.id ? query.id : nanoid(11),
|
||||||
progress: 0,
|
progress: 0,
|
||||||
startDttm: now(),
|
startDttm: now(),
|
||||||
state: query.runAsync ? 'pending' : 'running',
|
state: 'pending',
|
||||||
cached: false,
|
cached: false,
|
||||||
});
|
});
|
||||||
return { type: START_QUERY, query, runPreviewOnly } as const;
|
return { type: START_QUERY, query, runPreviewOnly } as const;
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
/**
|
||||||
|
* 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 { isValidElement } from 'react';
|
||||||
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
|
import { QueryState, type QueryResponse } from '@superset-ui/core';
|
||||||
|
import QueryStatusBar from '.';
|
||||||
|
|
||||||
|
jest.mock('../QueryStateLabel', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: ({ query }: { query: { state: QueryState } }) => (
|
||||||
|
<div data-test="query-state-label">{query.state}</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const createMockQuery = (
|
||||||
|
overrides: Partial<QueryResponse> = {},
|
||||||
|
): QueryResponse =>
|
||||||
|
({
|
||||||
|
id: 'test-query-id',
|
||||||
|
dbId: 1,
|
||||||
|
sql: 'SELECT * FROM test',
|
||||||
|
sqlEditorId: 'test-editor',
|
||||||
|
tab: 'Test Tab',
|
||||||
|
ctas: false,
|
||||||
|
cached: false,
|
||||||
|
progress: 0,
|
||||||
|
startDttm: Date.now() - 1000,
|
||||||
|
endDttm: undefined,
|
||||||
|
state: QueryState.Running,
|
||||||
|
tempSchema: null,
|
||||||
|
tempTable: null,
|
||||||
|
userId: 1,
|
||||||
|
executedSql: null,
|
||||||
|
rows: 0,
|
||||||
|
queryLimit: 100,
|
||||||
|
catalog: null,
|
||||||
|
schema: 'test_schema',
|
||||||
|
errorMessage: null,
|
||||||
|
extra: {},
|
||||||
|
results: undefined,
|
||||||
|
...overrides,
|
||||||
|
}) as QueryResponse;
|
||||||
|
|
||||||
|
test('is valid element', () => {
|
||||||
|
const query = createMockQuery();
|
||||||
|
expect(isValidElement(<QueryStatusBar query={query} />)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders query state label', () => {
|
||||||
|
const query = createMockQuery({ state: QueryState.Running });
|
||||||
|
render(<QueryStatusBar query={query} />);
|
||||||
|
expect(screen.getByTestId('query-state-label')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Query State:')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders elapsed time section', () => {
|
||||||
|
const query = createMockQuery({ state: QueryState.Running });
|
||||||
|
render(<QueryStatusBar query={query} />);
|
||||||
|
expect(screen.getByText('Elapsed:')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders steps for running query', () => {
|
||||||
|
const query = createMockQuery({ state: QueryState.Running, progress: 50 });
|
||||||
|
render(<QueryStatusBar query={query} />);
|
||||||
|
expect(screen.getByText('Validate query')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Connect to engine')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Running')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Download to client')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Finish')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders steps for pending query', () => {
|
||||||
|
const query = createMockQuery({ state: QueryState.Pending });
|
||||||
|
render(<QueryStatusBar query={query} />);
|
||||||
|
expect(screen.getByText('Validate query')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null when query is successful with results', () => {
|
||||||
|
const query = createMockQuery({
|
||||||
|
state: QueryState.Success,
|
||||||
|
results: {
|
||||||
|
displayLimitReached: false,
|
||||||
|
columns: [],
|
||||||
|
selected_columns: [],
|
||||||
|
expanded_columns: [],
|
||||||
|
data: [],
|
||||||
|
query: { limit: 100 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { container } = render(<QueryStatusBar query={query} />);
|
||||||
|
expect(container).toBeEmptyDOMElement();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('displays progress percentage when available', () => {
|
||||||
|
const query = createMockQuery({
|
||||||
|
state: QueryState.Running,
|
||||||
|
progress: 75,
|
||||||
|
});
|
||||||
|
render(<QueryStatusBar query={query} />);
|
||||||
|
expect(screen.getByText('(75%)')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('displays progress text when available', () => {
|
||||||
|
const query = createMockQuery({
|
||||||
|
state: QueryState.Running,
|
||||||
|
progress: 50,
|
||||||
|
extra: { progress: null, progress_text: 'Processing rows' },
|
||||||
|
});
|
||||||
|
render(<QueryStatusBar query={query} />);
|
||||||
|
expect(screen.getByText('(50%, Processing rows)')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('displays only progress text when no percentage', () => {
|
||||||
|
const query = createMockQuery({
|
||||||
|
state: QueryState.Running,
|
||||||
|
progress: 0,
|
||||||
|
extra: { progress: null, progress_text: 'Initializing' },
|
||||||
|
});
|
||||||
|
render(<QueryStatusBar query={query} />);
|
||||||
|
expect(screen.getByText('(Initializing)')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders for failed query state', () => {
|
||||||
|
const query = createMockQuery({
|
||||||
|
state: QueryState.Failed,
|
||||||
|
errorMessage: 'Query failed',
|
||||||
|
});
|
||||||
|
render(<QueryStatusBar query={query} />);
|
||||||
|
expect(screen.getByTestId('query-state-label')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders for stopped query state', () => {
|
||||||
|
const query = createMockQuery({ state: QueryState.Stopped });
|
||||||
|
render(<QueryStatusBar query={query} />);
|
||||||
|
expect(screen.getByTestId('query-state-label')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders for fetching state', () => {
|
||||||
|
const query = createMockQuery({
|
||||||
|
state: QueryState.Fetching,
|
||||||
|
progress: 100,
|
||||||
|
});
|
||||||
|
render(<QueryStatusBar query={query} />);
|
||||||
|
expect(screen.getByText('Download to client')).toBeInTheDocument();
|
||||||
|
});
|
||||||
214
superset-frontend/src/SqlLab/components/QueryStatusBar/index.tsx
Normal file
214
superset-frontend/src/SqlLab/components/QueryStatusBar/index.tsx
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
/**
|
||||||
|
* 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 { FC, useMemo, createContext, useContext, useRef } from 'react';
|
||||||
|
import { t, styled } from '@apache-superset/core';
|
||||||
|
import {
|
||||||
|
Flex,
|
||||||
|
Steps,
|
||||||
|
type StepsProps,
|
||||||
|
StyledSpin,
|
||||||
|
Timer,
|
||||||
|
} from '@superset-ui/core/components';
|
||||||
|
import { QueryResponse, QueryState, usePrevious } from '@superset-ui/core';
|
||||||
|
import QueryStateLabel from '../QueryStateLabel';
|
||||||
|
|
||||||
|
type QueryStatusBarProps = {
|
||||||
|
query: QueryResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
const STATE_TO_STEP: Record<string, number> = {
|
||||||
|
offline: 4,
|
||||||
|
failed: 4,
|
||||||
|
pending: 0,
|
||||||
|
fetching: 3,
|
||||||
|
running: 2,
|
||||||
|
stopped: 4,
|
||||||
|
success: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ERROR_STATE = [QueryState.Failed, QueryState.Stopped];
|
||||||
|
|
||||||
|
const StyledSteps = styled.div`
|
||||||
|
& .ant-steps {
|
||||||
|
margin: ${({ theme }) => theme.sizeUnit * 2}px 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ActiveDot = styled.div`
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: ${({ theme }) => theme.colorPrimary};
|
||||||
|
top: -1px;
|
||||||
|
opacity: 0;
|
||||||
|
animation: pulse 2s ease-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
animation-delay: 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(0.5);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(3);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const progressContext = createContext<[number, string]>([0, '']);
|
||||||
|
|
||||||
|
const ProgressStatus = () => {
|
||||||
|
const [percent, progressText] = useContext(progressContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{percent > 0 ? (
|
||||||
|
<span>
|
||||||
|
({percent}%{progressText && `, ${progressText}`})
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<>{progressText && <span>({progressText})</span>}</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProgressSpin = () => {
|
||||||
|
const [percent] = useContext(progressContext);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{typeof percent === 'number' && percent > 0 && (
|
||||||
|
<StyledSpin size="small" percent={percent} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const customDot: StepsProps['progressDot'] = (dot, { status }) =>
|
||||||
|
status === 'process' ? (
|
||||||
|
<ActiveDot>
|
||||||
|
<ProgressSpin />
|
||||||
|
</ActiveDot>
|
||||||
|
) : (
|
||||||
|
<>{dot}</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const QueryStatusBar: FC<QueryStatusBarProps> = ({ query }) => {
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: t('Validate query'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Connect to engine'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<Flex align="center" gap="small">
|
||||||
|
{t('Running')}
|
||||||
|
<ProgressStatus />
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Download to client'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Finish'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const hasError = useMemo(
|
||||||
|
() => ERROR_STATE.includes(query.state),
|
||||||
|
[query.state],
|
||||||
|
);
|
||||||
|
const prevStepRef = useRef<number>(0);
|
||||||
|
const progress = query.progress > 0 ? query.progress : undefined;
|
||||||
|
const { progress_text: progressText } = query.extra ?? {};
|
||||||
|
const state =
|
||||||
|
query.state === QueryState.Success &&
|
||||||
|
prevStepRef.current === STATE_TO_STEP[QueryState.Running] &&
|
||||||
|
!query.results
|
||||||
|
? QueryState.Fetching
|
||||||
|
: query.state;
|
||||||
|
|
||||||
|
const currentIndex = STATE_TO_STEP[state] || 0;
|
||||||
|
const prevStep = usePrevious(currentIndex);
|
||||||
|
prevStepRef.current = prevStep ?? prevStepRef.current;
|
||||||
|
|
||||||
|
if (query.state === QueryState.Success && query.results) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
query.state === QueryState.Failed &&
|
||||||
|
prevStep === STATE_TO_STEP[QueryState.Failed]
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledSteps>
|
||||||
|
<Flex justify="space-between">
|
||||||
|
<Flex gap="small" align="center">
|
||||||
|
<span>{t('Query State')}:</span>
|
||||||
|
<QueryStateLabel query={query} />
|
||||||
|
</Flex>
|
||||||
|
<Flex gap="small" align="center">
|
||||||
|
<span>{t('Elapsed')}:</span>
|
||||||
|
<Timer
|
||||||
|
startTime={query.startDttm}
|
||||||
|
endTime={query.endDttm}
|
||||||
|
status="default"
|
||||||
|
isRunning={currentIndex < steps.length - 2}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<progressContext.Provider value={[progress ?? 0, progressText ?? '']}>
|
||||||
|
<Steps
|
||||||
|
size="small"
|
||||||
|
current={hasError ? prevStep : currentIndex}
|
||||||
|
items={steps}
|
||||||
|
status={
|
||||||
|
hasError
|
||||||
|
? 'error'
|
||||||
|
: currentIndex < steps.length - 1
|
||||||
|
? 'process'
|
||||||
|
: 'finish'
|
||||||
|
}
|
||||||
|
{...(!hasError && { progressDot: customDot })}
|
||||||
|
/>
|
||||||
|
</progressContext.Provider>
|
||||||
|
</StyledSteps>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default QueryStatusBar;
|
||||||
@@ -35,7 +35,6 @@ import {
|
|||||||
cachedQuery,
|
cachedQuery,
|
||||||
failedQueryWithErrors,
|
failedQueryWithErrors,
|
||||||
queries,
|
queries,
|
||||||
runningQuery,
|
|
||||||
stoppedQuery,
|
stoppedQuery,
|
||||||
initialState,
|
initialState,
|
||||||
user,
|
user,
|
||||||
@@ -85,32 +84,6 @@ const stoppedQueryState = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const runningQueryState = {
|
|
||||||
...initialState,
|
|
||||||
sqlLab: {
|
|
||||||
...initialState.sqlLab,
|
|
||||||
queries: {
|
|
||||||
[runningQuery.id]: runningQuery,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const fetchingQueryState = {
|
|
||||||
...initialState,
|
|
||||||
sqlLab: {
|
|
||||||
...initialState.sqlLab,
|
|
||||||
queries: {
|
|
||||||
[mockedProps.queryId]: {
|
|
||||||
dbId: 1,
|
|
||||||
cached: false,
|
|
||||||
ctas: false,
|
|
||||||
id: 'ryhHUZCGb',
|
|
||||||
progress: 100,
|
|
||||||
state: 'fetching',
|
|
||||||
startDttm: Date.now() - 500,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const cachedQueryState = {
|
const cachedQueryState = {
|
||||||
...initialState,
|
...initialState,
|
||||||
sqlLab: {
|
sqlLab: {
|
||||||
@@ -332,25 +305,6 @@ describe('ResultSet', () => {
|
|||||||
expect(alert).toBeInTheDocument();
|
expect(alert).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should render running/pending/fetching query', async () => {
|
|
||||||
const { getByTestId } = setup(
|
|
||||||
{ ...mockedProps, queryId: runningQuery.id },
|
|
||||||
mockStore(runningQueryState),
|
|
||||||
);
|
|
||||||
const progressBar = getByTestId('progress-bar');
|
|
||||||
expect(progressBar).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should render fetching w/ 100 progress query', async () => {
|
|
||||||
const { getByRole, getByText } = setup(
|
|
||||||
mockedProps,
|
|
||||||
mockStore(fetchingQueryState),
|
|
||||||
);
|
|
||||||
const loading = getByRole('status');
|
|
||||||
expect(loading).toBeInTheDocument();
|
|
||||||
expect(getByText('fetching')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should render a failed query with an errors object', async () => {
|
test('should render a failed query with an errors object', async () => {
|
||||||
const { errors } = failedQueryWithErrors;
|
const { errors } = failedQueryWithErrors;
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Input,
|
Input,
|
||||||
Label,
|
Label,
|
||||||
Loading,
|
|
||||||
} from '@superset-ui/core/components';
|
} from '@superset-ui/core/components';
|
||||||
import {
|
import {
|
||||||
CopyToClipboard,
|
CopyToClipboard,
|
||||||
@@ -62,7 +61,6 @@ import {
|
|||||||
import { EXPLORE_CHART_DEFAULT, SqlLabRootState } from 'src/SqlLab/types';
|
import { EXPLORE_CHART_DEFAULT, SqlLabRootState } from 'src/SqlLab/types';
|
||||||
import { mountExploreUrl } from 'src/explore/exploreUtils';
|
import { mountExploreUrl } from 'src/explore/exploreUtils';
|
||||||
import { postFormData } from 'src/explore/exploreUtils/formData';
|
import { postFormData } from 'src/explore/exploreUtils/formData';
|
||||||
import ProgressBar from '@superset-ui/core/components/ProgressBar';
|
|
||||||
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||||
import { prepareCopyToClipboardTabularData } from 'src/utils/common';
|
import { prepareCopyToClipboardTabularData } from 'src/utils/common';
|
||||||
import { getItem, LocalStorageKeys } from 'src/utils/localStorageHelpers';
|
import { getItem, LocalStorageKeys } from 'src/utils/localStorageHelpers';
|
||||||
@@ -90,7 +88,6 @@ import { makeUrl } from 'src/utils/pathUtils';
|
|||||||
import ExploreCtasResultsButton from '../ExploreCtasResultsButton';
|
import ExploreCtasResultsButton from '../ExploreCtasResultsButton';
|
||||||
import ExploreResultsButton from '../ExploreResultsButton';
|
import ExploreResultsButton from '../ExploreResultsButton';
|
||||||
import HighlightedSql from '../HighlightedSql';
|
import HighlightedSql from '../HighlightedSql';
|
||||||
import QueryStateLabel from '../QueryStateLabel';
|
|
||||||
import PanelToolbar from 'src/components/PanelToolbar';
|
import PanelToolbar from 'src/components/PanelToolbar';
|
||||||
import { ViewContribution } from 'src/SqlLab/contributions';
|
import { ViewContribution } from 'src/SqlLab/contributions';
|
||||||
|
|
||||||
@@ -823,34 +820,20 @@ const ResultSet = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let progressBar;
|
|
||||||
if (query.progress > 0) {
|
|
||||||
progressBar = (
|
|
||||||
<ProgressBar percent={parseInt(query.progress.toFixed(0), 10)} striped />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const progressMsg = query?.extra?.progress ?? null;
|
const progressMsg = query?.extra?.progress ?? null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ResultlessStyles>
|
||||||
<ResultlessStyles>
|
{progressMsg && (
|
||||||
<div>{!progressBar && <Loading position="normal" />}</div>
|
<Alert type="success" message={progressMsg} closable={false} />
|
||||||
{/* show loading bar whenever progress bar is completed but needs time to render */}
|
)}
|
||||||
<div>{query.progress === 100 && <Loading position="normal" />}</div>
|
{trackingUrl && <div>{trackingUrl}</div>}
|
||||||
<QueryStateLabel query={query} />
|
|
||||||
<div>
|
|
||||||
{progressMsg && <Alert type="success" message={progressMsg} />}
|
|
||||||
</div>
|
|
||||||
<div>{query.progress !== 100 && progressBar}</div>
|
|
||||||
{trackingUrl && <div>{trackingUrl}</div>}
|
|
||||||
</ResultlessStyles>
|
|
||||||
<StreamingExportModal
|
<StreamingExportModal
|
||||||
visible={showStreamingModal}
|
visible={showStreamingModal}
|
||||||
onCancel={handleCloseStreamingModal}
|
onCancel={handleCloseStreamingModal}
|
||||||
progress={progress}
|
progress={progress}
|
||||||
/>
|
/>
|
||||||
</>
|
</ResultlessStyles>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { FC } from 'react';
|
import { FC, useMemo } from 'react';
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
import { EmptyState } from '@superset-ui/core/components';
|
import { EmptyState } from '@superset-ui/core/components';
|
||||||
import { t } from '@apache-superset/core';
|
import { t } from '@apache-superset/core';
|
||||||
@@ -26,6 +26,7 @@ import { styled, Alert } from '@apache-superset/core/ui';
|
|||||||
import { SqlLabRootState } from 'src/SqlLab/types';
|
import { SqlLabRootState } from 'src/SqlLab/types';
|
||||||
import ResultSet from '../ResultSet';
|
import ResultSet from '../ResultSet';
|
||||||
import { LOCALSTORAGE_MAX_QUERY_AGE_MS } from '../../constants';
|
import { LOCALSTORAGE_MAX_QUERY_AGE_MS } from '../../constants';
|
||||||
|
import QueryStatusBar from '../QueryStatusBar';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
latestQueryId?: string;
|
latestQueryId?: string;
|
||||||
@@ -53,10 +54,14 @@ const Results: FC<Props> = ({
|
|||||||
({ sqlLab: { databases } }: SqlLabRootState) => databases,
|
({ sqlLab: { databases } }: SqlLabRootState) => databases,
|
||||||
shallowEqual,
|
shallowEqual,
|
||||||
);
|
);
|
||||||
const latestQuery = useSelector(
|
const queries = useSelector(
|
||||||
({ sqlLab: { queries } }: SqlLabRootState) => queries[latestQueryId || ''],
|
({ sqlLab: { queries } }: SqlLabRootState) => queries,
|
||||||
shallowEqual,
|
shallowEqual,
|
||||||
);
|
);
|
||||||
|
const latestQuery = useMemo(
|
||||||
|
() => queries[latestQueryId ?? ''],
|
||||||
|
[queries, latestQueryId],
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!latestQuery ||
|
!latestQuery ||
|
||||||
@@ -72,30 +77,32 @@ const Results: FC<Props> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
const hasNoStoredResults =
|
||||||
isFeatureEnabled(FeatureFlag.SqllabBackendPersistence) &&
|
isFeatureEnabled(FeatureFlag.SqllabBackendPersistence) &&
|
||||||
latestQuery.state === 'success' &&
|
latestQuery.state === 'success' &&
|
||||||
!latestQuery.resultsKey &&
|
!latestQuery.resultsKey &&
|
||||||
!latestQuery.results
|
!latestQuery.results;
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<Alert
|
|
||||||
type="info"
|
|
||||||
message={t('No stored results found, you need to re-run your query')}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResultSet
|
<>
|
||||||
search
|
<QueryStatusBar key={latestQueryId} query={latestQuery} />
|
||||||
queryId={latestQuery.id}
|
{hasNoStoredResults ? (
|
||||||
database={databases[latestQuery.dbId]}
|
<Alert
|
||||||
displayLimit={displayLimit}
|
type="info"
|
||||||
defaultQueryLimit={defaultQueryLimit}
|
message={t('No stored results found, you need to re-run your query')}
|
||||||
showSql
|
/>
|
||||||
showSqlInline
|
) : (
|
||||||
/>
|
<ResultSet
|
||||||
|
search
|
||||||
|
queryId={latestQuery.id}
|
||||||
|
database={databases[latestQuery.dbId]}
|
||||||
|
displayLimit={displayLimit}
|
||||||
|
defaultQueryLimit={defaultQueryLimit}
|
||||||
|
showSql
|
||||||
|
showSqlInline
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user