diff --git a/package-lock.json b/package-lock.json index 1911b045c..5d2ebe8b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,6 @@ "path-to-regexp": "^3.0.0", "qs": "^6.12.0", "react-error-boundary": "^4.0.13", - "react-freeze": "^1.0.4", "react-helmet-async": "^2.0.5", "react-hook-form": "^7.52.1", "react-json-inspector": "^7.1.1", @@ -20923,18 +20922,6 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" }, - "node_modules/react-freeze": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz", - "integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": ">=17.0.0" - } - }, "node_modules/react-helmet-async": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-2.0.5.tgz", diff --git a/package.json b/package.json index 68876631b..830c4599c 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "path-to-regexp": "^3.0.0", "qs": "^6.12.0", "react-error-boundary": "^4.0.13", - "react-freeze": "^1.0.4", "react-helmet-async": "^2.0.5", "react-hook-form": "^7.52.1", "react-json-inspector": "^7.1.1", diff --git a/src/components/NotRenderUntilFirstVisible/NotRenderUntilFirstVisible.scss b/src/components/NotRenderUntilFirstVisible/NotRenderUntilFirstVisible.scss deleted file mode 100644 index 29b1a843d..000000000 --- a/src/components/NotRenderUntilFirstVisible/NotRenderUntilFirstVisible.scss +++ /dev/null @@ -1,3 +0,0 @@ -.ydb-not-render-until-first-visible { - display: contents; -} diff --git a/src/components/NotRenderUntilFirstVisible/NotRenderUntilFirstVisible.tsx b/src/components/NotRenderUntilFirstVisible/NotRenderUntilFirstVisible.tsx deleted file mode 100644 index 5c6356739..000000000 --- a/src/components/NotRenderUntilFirstVisible/NotRenderUntilFirstVisible.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; - -import {Freeze} from 'react-freeze'; - -import {cn} from '../../utils/cn'; - -import './NotRenderUntilFirstVisible.scss'; - -const block = cn('ydb-not-render-until-first-visible'); - -interface Props { - show?: boolean; - className?: string; - children: React.ReactNode; -} - -export default function NotRenderUntilFirstVisible({show, className, children}: Props) { - return ( -
- {children} -
- ); -} diff --git a/src/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx b/src/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx index 4a347b440..ed8a6e15b 100644 --- a/src/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx +++ b/src/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx @@ -1,8 +1,5 @@ -import React from 'react'; - import {useThemeValue} from '@gravity-ui/uikit'; -import NotRenderUntilFirstVisible from '../../../components/NotRenderUntilFirstVisible/NotRenderUntilFirstVisible'; import {TENANT_PAGES_IDS} from '../../../store/reducers/tenant/constants'; import type {AdditionalNodesProps, AdditionalTenantsProps} from '../../../types/additionalProps'; import type {EPathType} from '../../../types/api/schema'; @@ -31,13 +28,12 @@ function ObjectGeneral(props: ObjectGeneralProps) { const renderPageContent = () => { const {type, additionalTenantProps, additionalNodesProps, tenantName, path} = props; - - return ( - - - - - + switch (tenantPage) { + case TENANT_PAGES_IDS.query: { + return ; + } + default: { + return ( - - - ); + ); + } + } }; return ( diff --git a/src/containers/Tenant/Query/CancelQueryButton/CancelQueryButton.scss b/src/containers/Tenant/Query/CancelQueryButton/CancelQueryButton.scss new file mode 100644 index 000000000..6c8350720 --- /dev/null +++ b/src/containers/Tenant/Query/CancelQueryButton/CancelQueryButton.scss @@ -0,0 +1,9 @@ +@import '../../../../styles/mixins.scss'; + +.cancel-query-button { + &__stop-button { + &_error { + @include query-buttons-animations(); + } + } +} diff --git a/src/containers/Tenant/Query/CancelQueryButton/CancelQueryButton.tsx b/src/containers/Tenant/Query/CancelQueryButton/CancelQueryButton.tsx new file mode 100644 index 000000000..937b292e7 --- /dev/null +++ b/src/containers/Tenant/Query/CancelQueryButton/CancelQueryButton.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +import {StopFill} from '@gravity-ui/icons'; +import {Button, Icon} from '@gravity-ui/uikit'; + +import {cancelQueryApi} from '../../../../store/reducers/cancelQuery'; +import {cn} from '../../../../utils/cn'; +import i18n from '../i18n'; + +import './CancelQueryButton.scss'; + +const b = cn('cancel-query-button'); + +interface CancelQueryButtonProps { + queryId: string; + tenantName: string; +} + +export function CancelQueryButton({queryId, tenantName}: CancelQueryButtonProps) { + const [sendCancelQuery, cancelQueryResponse] = cancelQueryApi.useCancelQueryMutation(); + + const onStopButtonClick = React.useCallback(() => { + sendCancelQuery({queryId, database: tenantName}); + }, [queryId, sendCancelQuery, tenantName]); + + return ( + + ); +} diff --git a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss b/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss index 52ec4bdb1..76eebd224 100644 --- a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss +++ b/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss @@ -78,10 +78,4 @@ &__elapsed-label { margin-left: var(--g-spacing-3); } - - &__stop-button { - &_error { - @include query-buttons-animations(); - } - } } diff --git a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx b/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx index e1f3e76b5..35208f56e 100644 --- a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx +++ b/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx @@ -1,8 +1,7 @@ import React from 'react'; -import {StopFill} from '@gravity-ui/icons'; import type {ControlGroupOption} from '@gravity-ui/uikit'; -import {Button, Icon, RadioButton, Tabs} from '@gravity-ui/uikit'; +import {RadioButton, Tabs} from '@gravity-ui/uikit'; import JSONTree from 'react-json-inspector'; import {ClipboardButton} from '../../../../components/ClipboardButton'; @@ -17,13 +16,14 @@ import {QueryResultTable} from '../../../../components/QueryResultTable/QueryRes import {disableFullscreen} from '../../../../store/reducers/fullscreen'; import type {ColumnType, KeyValueRow, TKqpStatsQuery} from '../../../../types/api/query'; import type {ValueOf} from '../../../../types/common'; -import type {IQueryResult} from '../../../../types/store/query'; +import type {ExecuteQueryResult} from '../../../../types/store/executeQuery'; import {getArray} from '../../../../utils'; import {cn} from '../../../../utils/cn'; import {getStringifiedData} from '../../../../utils/dataFormatters/dataFormatters'; import {useTypedDispatch} from '../../../../utils/hooks'; import {parseQueryError} from '../../../../utils/query'; import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers'; +import {CancelQueryButton} from '../CancelQueryButton/CancelQueryButton'; import {SimplifiedPlan} from '../ExplainResult/components/SimplifiedPlan/SimplifiedPlan'; import {ResultIssues} from '../Issues/Issues'; import {QueryDuration} from '../QueryDuration/QueryDuration'; @@ -49,34 +49,28 @@ const resultOptionsIds = { type SectionID = ValueOf; interface ExecuteResultProps { - data: IQueryResult | undefined; - error: unknown; - cancelError: unknown; + result: ExecuteQueryResult; isResultsCollapsed?: boolean; + theme?: string; + tenantName: string; onCollapseResults: VoidFunction; onExpandResults: VoidFunction; - onStopButtonClick: VoidFunction; - theme?: string; - loading?: boolean; - cancelQueryLoading?: boolean; } export function ExecuteResult({ - data, - error, - cancelError, + result, isResultsCollapsed, + theme, + tenantName, onCollapseResults, onExpandResults, - onStopButtonClick, - theme, - loading, - cancelQueryLoading, }: ExecuteResultProps) { const [selectedResultSet, setSelectedResultSet] = React.useState(0); const [activeSection, setActiveSection] = React.useState(resultOptionsIds.result); const dispatch = useTypedDispatch(); + const {error, isLoading, queryId, data} = result; + const stats: TKqpStatsQuery | undefined = data?.stats; const resultsSetsCount = data?.resultSets?.length; const isMulti = resultsSetsCount && resultsSetsCount > 0; @@ -111,10 +105,10 @@ export function ExecuteResult({ }; const renderResultTable = ( - result: KeyValueRow[] | undefined, + resultSet: KeyValueRow[] | undefined, columns: ColumnType[] | undefined, ) => { - return ; + return ; }; const renderResult = () => { @@ -243,8 +237,8 @@ export function ExecuteResult({
- - {!error && !loading && ( + + {!error && !isLoading && ( {stats?.DurationUs !== undefined && ( @@ -261,17 +255,10 @@ export function ExecuteResult({ )} )} - {loading ? ( + {isLoading ? ( - + ) : null} {data?.traceId ? : null} @@ -287,8 +274,8 @@ export function ExecuteResult({ />
- {loading || isQueryCancelledError(error) ? null : } - + {isLoading || isQueryCancelledError(error) ? null : } + {renderResultSection()}
diff --git a/src/containers/Tenant/Query/ExecuteResult/i18n/en.json b/src/containers/Tenant/Query/ExecuteResult/i18n/en.json index fcc63d028..b2294fa64 100644 --- a/src/containers/Tenant/Query/ExecuteResult/i18n/en.json +++ b/src/containers/Tenant/Query/ExecuteResult/i18n/en.json @@ -3,7 +3,6 @@ "action.result": "Result", "action.stats": "Stats", "action.schema": "Schema", - "action.stop": "Stop", "action.explain-plan": "Explain Plan", "action.copy": "Copy {{activeSection}}", "trace": "Trace" diff --git a/src/containers/Tenant/Query/ExplainResult/ExplainResult.scss b/src/containers/Tenant/Query/ExplainResult/ExplainResult.scss index 375ced444..5b247d9f4 100644 --- a/src/containers/Tenant/Query/ExplainResult/ExplainResult.scss +++ b/src/containers/Tenant/Query/ExplainResult/ExplainResult.scss @@ -43,10 +43,4 @@ &__elapsed-label { margin-left: var(--g-spacing-3); } - - &__stop-button { - &_error { - @include query-buttons-animations(); - } - } } diff --git a/src/containers/Tenant/Query/ExplainResult/ExplainResult.tsx b/src/containers/Tenant/Query/ExplainResult/ExplainResult.tsx index fd02e3a50..8d0cc756a 100644 --- a/src/containers/Tenant/Query/ExplainResult/ExplainResult.tsx +++ b/src/containers/Tenant/Query/ExplainResult/ExplainResult.tsx @@ -1,7 +1,6 @@ import React from 'react'; -import {StopFill} from '@gravity-ui/icons'; -import {Button, Icon, RadioButton} from '@gravity-ui/uikit'; +import {RadioButton} from '@gravity-ui/uikit'; import {ClipboardButton} from '../../../../components/ClipboardButton'; import Divider from '../../../../components/Divider/Divider'; @@ -10,14 +9,15 @@ import EnableFullscreenButton from '../../../../components/EnableFullscreenButto import Fullscreen from '../../../../components/Fullscreen/Fullscreen'; import {LoaderWrapper} from '../../../../components/LoaderWrapper/LoaderWrapper'; import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus'; -import type {PreparedExplainResponse} from '../../../../store/reducers/explainQuery/types'; import {disableFullscreen} from '../../../../store/reducers/fullscreen'; import type {ValueOf} from '../../../../types/common'; +import type {ExplainQueryResult} from '../../../../types/store/executeQuery'; import {cn} from '../../../../utils/cn'; import {getStringifiedData} from '../../../../utils/dataFormatters/dataFormatters'; import {useTypedDispatch} from '../../../../utils/hooks'; import {parseQueryErrorToString} from '../../../../utils/query'; import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers'; +import {CancelQueryButton} from '../CancelQueryButton/CancelQueryButton'; import {QueryDuration} from '../QueryDuration/QueryDuration'; import {QuerySettingsBanner} from '../QuerySettingsBanner/QuerySettingsBanner'; import {isQueryCancelledError} from '../utils/isQueryCancelledError'; @@ -60,38 +60,29 @@ const explainOptions = [ interface ExplainResultProps { theme: string; - explain?: PreparedExplainResponse['plan'] & {DurationUs?: number}; - simplifiedPlan?: PreparedExplainResponse['simplifiedPlan']; - ast?: string; - loading?: boolean; - cancelQueryLoading?: boolean; + result: ExplainQueryResult; + tenantName: string; isResultsCollapsed?: boolean; - error: unknown; - cancelError: unknown; onCollapseResults: VoidFunction; onExpandResults: VoidFunction; - onStopButtonClick: VoidFunction; } export function ExplainResult({ - explain, - ast, theme, - error, - cancelError, - loading, - cancelQueryLoading, + result, + tenantName, onCollapseResults, onExpandResults, - onStopButtonClick, isResultsCollapsed, - simplifiedPlan, }: ExplainResultProps) { const dispatch = useTypedDispatch(); const [activeOption, setActiveOption] = React.useState( EXPLAIN_OPTIONS_IDS.schema, ); const [isPending, startTransition] = React.useTransition(); + const {error, isLoading, queryId} = result; + + const {plan: explain, ast, simplifiedPlan} = result.data || {}; React.useEffect(() => { return () => { @@ -169,9 +160,9 @@ export function ExplainResult({
- + - {!error && !loading && ( + {!error && !isLoading && ( {explain?.DurationUs !== undefined && ( @@ -188,17 +179,10 @@ export function ExplainResult({ )} - {loading ? ( + {isLoading ? ( - + ) : null}
@@ -219,8 +203,8 @@ export function ExplainResult({ />
- {loading || isQueryCancelledError(error) ? null : } - + {isLoading || isQueryCancelledError(error) ? null : } + {renderContent()}
diff --git a/src/containers/Tenant/Query/ExplainResult/i18n/en.json b/src/containers/Tenant/Query/ExplainResult/i18n/en.json index 1befd1a5d..15bd21a2e 100644 --- a/src/containers/Tenant/Query/ExplainResult/i18n/en.json +++ b/src/containers/Tenant/Query/ExplainResult/i18n/en.json @@ -5,6 +5,5 @@ "action.explain-plan": "Explain Plan", "action.json": "JSON", "action.ast": "AST", - "action.copy": "Copy {{activeOption}}", - "action.stop": "Stop" + "action.copy": "Copy {{activeOption}}" } diff --git a/src/containers/Tenant/Query/Query.tsx b/src/containers/Tenant/Query/Query.tsx index 63f61fe83..1e78eae8e 100644 --- a/src/containers/Tenant/Query/Query.tsx +++ b/src/containers/Tenant/Query/Query.tsx @@ -2,7 +2,6 @@ import React from 'react'; import {Helmet} from 'react-helmet-async'; -import NotRenderUntilFirstVisible from '../../../components/NotRenderUntilFirstVisible/NotRenderUntilFirstVisible'; import {changeUserInput} from '../../../store/reducers/executeQuery'; import {TENANT_QUERY_TABS_ID} from '../../../store/reducers/tenant/constants'; import type {EPathType} from '../../../types/api/schema'; @@ -40,19 +39,20 @@ export const Query = (props: QueryProps) => { ); const renderContent = () => { - return ( - - - - - - - - - - - - ); + switch (queryTab) { + case TENANT_QUERY_TABS_ID.newQuery: { + return ; + } + case TENANT_QUERY_TABS_ID.history: { + return ; + } + case TENANT_QUERY_TABS_ID.saved: { + return ; + } + default: { + return null; + } + } }; return ( diff --git a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx index 576249e9c..383217728 100644 --- a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx @@ -9,23 +9,22 @@ import {v4 as uuidv4} from 'uuid'; import {MonacoEditor} from '../../../../components/MonacoEditor/MonacoEditor'; import SplitPane from '../../../../components/SplitPane'; import type {RootState} from '../../../../store'; -import {cancelQueryApi} from '../../../../store/reducers/cancelQuery'; import {useTracingLevelOptionAvailable} from '../../../../store/reducers/capabilities/hooks'; import { executeQueryApi, goToNextQuery, goToPreviousQuery, saveQueryToHistory, + setQueryResult, setTenantPath, } from '../../../../store/reducers/executeQuery'; import {explainQueryApi} from '../../../../store/reducers/explainQuery/explainQuery'; -import type {PreparedExplainResponse} from '../../../../store/reducers/explainQuery/types'; import {setQueryAction} from '../../../../store/reducers/queryActions/queryActions'; import {setShowPreview} from '../../../../store/reducers/schema/schema'; import type {EPathType} from '../../../../types/api/schema'; -import type {ValueOf} from '../../../../types/common'; -import type {ExecuteQueryState} from '../../../../types/store/executeQuery'; -import type {IQueryResult, QueryAction} from '../../../../types/store/query'; +import {ResultType} from '../../../../types/store/executeQuery'; +import type {ExecuteQueryState, QueryResult} from '../../../../types/store/executeQuery'; +import type {QueryAction} from '../../../../types/store/query'; import {cn} from '../../../../utils/cn'; import { DEFAULT_IS_QUERY_RESULT_COLLAPSED, @@ -57,10 +56,6 @@ import {getKeyBindings} from './keybindings'; import './QueryEditor.scss'; const CONTEXT_MENU_GROUP_ID = 'navigation'; -const RESULT_TYPES = { - EXECUTE: 'execute', - EXPLAIN: 'explain', -} as const; const b = cn('query-editor'); @@ -78,6 +73,7 @@ interface QueryEditorProps { goToPreviousQuery: (...args: Parameters) => void; setTenantPath: (...args: Parameters) => void; setQueryAction: (...args: Parameters) => void; + setQueryResult: (...args: Parameters) => void; executeQuery: ExecuteQueryState; theme: string; type?: EPathType; @@ -96,12 +92,13 @@ function QueryEditor(props: QueryEditorProps) { type, theme, changeUserInput, + setQueryResult, showPreview, } = props; const {tenantPath: savedPath} = executeQuery; - const [resultType, setResultType] = React.useState>(); - const [isResultLoaded, setIsResultLoaded] = React.useState(false); + const isResultLoaded = Boolean(executeQuery.result); + const [querySettings] = useQueryExecutionSettings(); const enableTracingLevel = useTracingLevelOptionAvailable(); const [lastQueryExecutionSettings, setLastQueryExecutionSettings] = @@ -113,18 +110,18 @@ function QueryEditor(props: QueryEditorProps) { LAST_USED_QUERY_ACTION_KEY, ); - const [sendExecuteQuery, executeQueryResult] = executeQueryApi.useExecuteQueryMutation(); - const [sendExplainQuery, explainQueryResult] = explainQueryApi.useExplainQueryMutation(); - const [sendCancelQuery, cancelQueryResult] = cancelQueryApi.useCancelQueryMutation(); + const [sendExecuteQuery] = executeQueryApi.useExecuteQueryMutation(); + const [sendExplainQuery] = explainQueryApi.useExplainQueryMutation(); React.useEffect(() => { if (savedPath !== tenantName) { if (savedPath) { changeUserInput({input: ''}); + setQueryResult(); } setPath(tenantName); } - }, [changeUserInput, setPath, tenantName, savedPath]); + }, [changeUserInput, setPath, setQueryResult, tenantName, savedPath]); const [resultVisibilityState, dispatchResultVisibilityState] = React.useReducer( paneVisibilityToggleReducerCreator(DEFAULT_IS_QUERY_RESULT_COLLAPSED), @@ -161,7 +158,6 @@ function QueryEditor(props: QueryEditorProps) { setLastQueryExecutionSettings(querySettings); } const queryId = uuidv4(); - setResultType(RESULT_TYPES.EXECUTE); sendExecuteQuery({ query, @@ -171,9 +167,8 @@ function QueryEditor(props: QueryEditorProps) { enableTracingLevel, queryId, }); - setIsResultLoaded(true); + props.setShowPreview(false); - cancelQueryResult.reset(); // Don't save partial queries in history if (!text) { @@ -200,7 +195,6 @@ function QueryEditor(props: QueryEditorProps) { } const queryId = uuidv4(); - setResultType(RESULT_TYPES.EXPLAIN); sendExplainQuery({ query: input, @@ -210,23 +204,11 @@ function QueryEditor(props: QueryEditorProps) { queryId, }); - setIsResultLoaded(true); props.setShowPreview(false); - cancelQueryResult.reset(); dispatchResultVisibilityState(PaneVisibilityActionTypes.triggerExpand); }); - const currentQueryId = executeQueryResult.isLoading - ? executeQueryResult.originalArgs?.queryId - : explainQueryResult.originalArgs?.queryId; - - const handleStopButtonClick = React.useCallback(() => { - if (currentQueryId) { - sendCancelQuery({queryId: currentQueryId, database: tenantName}); - } - }, [currentQueryId, sendCancelQuery, tenantName]); - const handleSendQuery = useEventHandler(() => { if (lastUsedQueryAction === QUERY_ACTIONS.explain) { handleGetExplainQueryClick(); @@ -335,7 +317,7 @@ function QueryEditor(props: QueryEditorProps) {
| undefined; + result?: QueryResult; tenantName: string; path: string; showPreview?: boolean; } function Result({ - executeQueryData, - executeQueryError, - cancelQueryError, - explainQueryData, - explainQueryError, - explainQueryLoading, - executeResultLoading, - cancelQueryLoading, resultVisibilityState, onExpandResultHandler, onCollapseResultHandler, - onStopButtonClick, type, theme, - resultType, + result, tenantName, path, showPreview, @@ -466,40 +421,28 @@ function Result({ return ; } - if (resultType === RESULT_TYPES.EXECUTE) { + if (result?.type === ResultType.EXECUTE) { return ( ); } - if (resultType === RESULT_TYPES.EXPLAIN) { - const {plan, ast, simplifiedPlan} = explainQueryData || {}; - + if (result?.type === ResultType.EXPLAIN) { return ( ); } diff --git a/src/containers/Tenant/Query/i18n/en.json b/src/containers/Tenant/Query/i18n/en.json index 36c36e235..1af598ba4 100644 --- a/src/containers/Tenant/Query/i18n/en.json +++ b/src/containers/Tenant/Query/i18n/en.json @@ -49,6 +49,7 @@ "action.previous-query": "Previous query in history", "action.next-query": "Next query in history", "action.save-query": "Save query", + "action.stop": "Stop", "filter.text.placeholder": "Search by query text...", diff --git a/src/store/reducers/cancelQuery.ts b/src/store/reducers/cancelQuery.ts index cd65af085..3999f013d 100644 --- a/src/store/reducers/cancelQuery.ts +++ b/src/store/reducers/cancelQuery.ts @@ -29,6 +29,7 @@ export const cancelQueryApi = api.injectEndpoints({ } const data = parseQueryAPIExecuteResponse(response); + return {data}; } catch (error) { return {error}; diff --git a/src/store/reducers/executeQuery.ts b/src/store/reducers/executeQuery.ts index 449b6cb49..b73d611f5 100644 --- a/src/store/reducers/executeQuery.ts +++ b/src/store/reducers/executeQuery.ts @@ -3,18 +3,15 @@ import type {Reducer} from '@reduxjs/toolkit'; import {settingsManager} from '../../services/settings'; import {TracingLevelNumber} from '../../types/api/query'; import type {ExecuteActions, Schemas} from '../../types/api/query'; +import {ResultType} from '../../types/store/executeQuery'; import type { ExecuteQueryAction, ExecuteQueryState, ExecuteQueryStateSlice, QueryInHistory, + QueryResult, } from '../../types/store/executeQuery'; -import type { - IQueryResult, - QueryRequestParams, - QuerySettings, - QuerySyntax, -} from '../../types/store/query'; +import type {QueryRequestParams, QuerySettings, QuerySyntax} from '../../types/store/query'; import {QUERIES_HISTORY_KEY} from '../../utils/constants'; import {QUERY_SYNTAX, isQueryErrorResponse, parseQueryAPIExecuteResponse} from '../../utils/query'; import {isNumeric} from '../../utils/utils'; @@ -24,6 +21,7 @@ import {api} from './api'; const MAXIMUM_QUERIES_IN_HISTORY = 20; const CHANGE_USER_INPUT = 'query/CHANGE_USER_INPUT'; +const SET_QUERY_RESULT = 'query/SET_QUERY_RESULT'; const SAVE_QUERY_TO_HISTORY = 'query/SAVE_QUERY_TO_HISTORY'; const UPDATE_QUERY_IN_HISTORY = 'query/UPDATE_QUERY_IN_HISTORY'; const SET_QUERY_HISTORY_FILTER = 'query/SET_QUERY_HISTORY_FILTER'; @@ -65,6 +63,13 @@ const executeQuery: Reducer = ( }; } + case SET_QUERY_RESULT: { + return { + ...state, + result: action.data, + }; + } + case SAVE_QUERY_TO_HISTORY: { const {queryText, queryId} = action.data; @@ -190,7 +195,7 @@ interface QueryStats { export const executeQueryApi = api.injectEndpoints({ endpoints: (build) => ({ - executeQuery: build.mutation({ + executeQuery: build.mutation({ queryFn: async ( { query, @@ -205,6 +210,8 @@ export const executeQueryApi = api.injectEndpoints({ let action: ExecuteActions = 'execute'; let syntax: QuerySyntax = QUERY_SYNTAX.yql; + dispatch(setQueryResult({type: ResultType.EXECUTE, queryId, isLoading: true})); + if (querySettings.queryMode === 'pg') { action = 'execute-query'; syntax = QUERY_SYNTAX.pg; @@ -239,6 +246,14 @@ export const executeQueryApi = api.injectEndpoints({ ); if (isQueryErrorResponse(response)) { + dispatch( + setQueryResult({ + type: ResultType.EXECUTE, + error: response, + isLoading: false, + queryId, + }), + ); return {error: response}; } @@ -257,8 +272,24 @@ export const executeQueryApi = api.injectEndpoints({ } dispatch(updateQueryInHistory(queryStats, queryId)); - return {data}; + dispatch( + setQueryResult({ + type: ResultType.EXECUTE, + data, + isLoading: false, + queryId, + }), + ); + return {data: null}; } catch (error) { + dispatch( + setQueryResult({ + type: ResultType.EXECUTE, + error, + isLoading: false, + queryId, + }), + ); return {error}; } }, @@ -281,6 +312,13 @@ export function updateQueryInHistory(stats: QueryStats, queryId: string) { } as const; } +export function setQueryResult(data?: QueryResult) { + return { + type: SET_QUERY_RESULT, + data, + } as const; +} + export const goToPreviousQuery = () => { return { type: GO_TO_PREVIOUS_QUERY, diff --git a/src/store/reducers/explainQuery/explainQuery.ts b/src/store/reducers/explainQuery/explainQuery.ts index 67cefb794..c0f35ab54 100644 --- a/src/store/reducers/explainQuery/explainQuery.ts +++ b/src/store/reducers/explainQuery/explainQuery.ts @@ -1,11 +1,12 @@ import {TracingLevelNumber} from '../../../types/api/query'; import type {ExplainActions} from '../../../types/api/query'; +import {ResultType} from '../../../types/store/executeQuery'; import type {QueryRequestParams, QuerySettings, QuerySyntax} from '../../../types/store/query'; import {QUERY_SYNTAX, isQueryErrorResponse} from '../../../utils/query'; import {isNumeric} from '../../../utils/utils'; import {api} from '../api'; +import {setQueryResult} from '../executeQuery'; -import type {PreparedExplainResponse} from './types'; import {prepareExplainResponse} from './utils'; interface ExplainQueryParams extends QueryRequestParams { @@ -18,14 +19,16 @@ interface ExplainQueryParams extends QueryRequestParams { export const explainQueryApi = api.injectEndpoints({ endpoints: (build) => ({ - explainQuery: build.mutation({ + explainQuery: build.mutation({ queryFn: async ( {query, database, querySettings, enableTracingLevel, queryId}, - {signal}, + {signal, dispatch}, ) => { let action: ExplainActions = 'explain'; let syntax: QuerySyntax = QUERY_SYNTAX.yql; + dispatch(setQueryResult({type: ResultType.EXPLAIN, queryId, isLoading: true})); + if (querySettings?.queryMode === 'pg') { action = 'explain-query'; syntax = QUERY_SYNTAX.pg; @@ -58,12 +61,36 @@ export const explainQueryApi = api.injectEndpoints({ ); if (isQueryErrorResponse(response)) { + dispatch( + setQueryResult({ + type: ResultType.EXPLAIN, + error: response, + queryId, + isLoading: false, + }), + ); return {error: response}; } const data = prepareExplainResponse(response); - return {data}; + dispatch( + setQueryResult({ + type: ResultType.EXPLAIN, + data, + queryId, + isLoading: false, + }), + ); + return {data: null}; } catch (error) { + dispatch( + setQueryResult({ + type: ResultType.EXPLAIN, + error, + queryId, + isLoading: false, + }), + ); return {error}; } }, diff --git a/src/store/reducers/explainQuery/types.ts b/src/store/reducers/explainQuery/types.ts index 7b6cfbdea..29a3a3da2 100644 --- a/src/store/reducers/explainQuery/types.ts +++ b/src/store/reducers/explainQuery/types.ts @@ -15,6 +15,7 @@ export interface PreparedExplainResponse { tables?: PlanTable[]; version?: string; pristine?: QueryPlan | ScriptPlan; + DurationUs?: string | number; }; simplifiedPlan?: { plan?: SimplifiedPlanItem[]; diff --git a/src/types/store/executeQuery.ts b/src/types/store/executeQuery.ts index bbfdae689..62b88db1e 100644 --- a/src/types/store/executeQuery.ts +++ b/src/types/store/executeQuery.ts @@ -4,9 +4,13 @@ import type { goToPreviousQuery, saveQueryToHistory, setQueryHistoryFilter, + setQueryResult, setTenantPath, updateQueryInHistory, } from '../../store/reducers/executeQuery'; +import type {PreparedExplainResponse} from '../../store/reducers/explainQuery/types'; + +import type {IQueryResult} from './query'; export interface QueryInHistory { queryId?: string; @@ -16,9 +20,33 @@ export interface QueryInHistory { durationUs?: string | number; } +export enum ResultType { + EXECUTE = 'execute', + EXPLAIN = 'explain', +} + +interface CommonResultParams { + queryId: string; + isLoading: boolean; +} + +export type ExecuteQueryResult = { + type: ResultType.EXECUTE; + data?: IQueryResult; + error?: unknown; +} & CommonResultParams; + +export type ExplainQueryResult = { + type: ResultType.EXPLAIN; + data?: PreparedExplainResponse; + error?: unknown; +} & CommonResultParams; + +export type QueryResult = ExecuteQueryResult | ExplainQueryResult; + export interface ExecuteQueryState { - loading: boolean; input: string; + result?: QueryResult; history: { // String type for backward compatibility queries: QueryInHistory[]; @@ -32,6 +60,7 @@ export type ExecuteQueryAction = | ReturnType | ReturnType | ReturnType + | ReturnType | ReturnType | ReturnType | ReturnType