diff --git a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss b/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss deleted file mode 100644 index ba600a429..000000000 --- a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss +++ /dev/null @@ -1,89 +0,0 @@ -@import '../../../../styles/mixins.scss'; - -.ydb-query-execute-result { - &__result { - display: flex; - overflow: auto; - flex-grow: 1; - flex-direction: column; - - padding-left: 10px; - - @include query-data-table(); - & .data-table__table-wrapper { - padding-bottom: 0; - } - } - - &__row-count { - margin-left: var(--g-spacing-1); - } - - &__result-head { - margin-top: var(--g-spacing-4); - } - - &__result-wrapper { - display: flex; - flex-direction: column; - - width: 100%; - } - - &__result-tabs { - padding-left: 10px; - } - - &__error { - padding: 15px 10px; - } - - &__controls { - position: sticky; - z-index: 2; - top: 0; - - display: flex; - justify-content: space-between; - align-items: center; - - height: 53px; - padding: 12px 20px; - - border-bottom: 1px solid var(--g-color-line-generic); - background-color: var(--g-color-base-background); - } - - &__controls-right { - display: flex; - align-items: center; - gap: 12px; - - height: 100%; - } - - &__controls-left { - display: flex; - gap: 4px; - } - - &__inspector { - overflow: auto; - - width: 100%; - height: 100%; - padding: 15px 10px; - @include json-tree-styles(); - } - - &__explain-canvas-container { - overflow-y: auto; - - width: 100%; - height: 100%; - } - - &__elapsed-label { - margin-left: var(--g-spacing-3); - } -} diff --git a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx b/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx deleted file mode 100644 index 8ac6e4a50..000000000 --- a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx +++ /dev/null @@ -1,300 +0,0 @@ -import React from 'react'; - -import type {ControlGroupOption} from '@gravity-ui/uikit'; -import {ClipboardButton, RadioButton, Tabs, Text} from '@gravity-ui/uikit'; -import JSONTree from 'react-json-inspector'; - -import Divider from '../../../../components/Divider/Divider'; -import ElapsedTime from '../../../../components/ElapsedTime/ElapsedTime'; -import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton'; -import Fullscreen from '../../../../components/Fullscreen/Fullscreen'; -import {YDBGraph} from '../../../../components/Graph/Graph'; -import {LoaderWrapper} from '../../../../components/LoaderWrapper/LoaderWrapper'; -import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus'; -import {QueryResultTable} from '../../../../components/QueryResultTable/QueryResultTable'; -import {disableFullscreen} from '../../../../store/reducers/fullscreen'; -import type {QueryResult} from '../../../../store/reducers/query/types'; -import type {TKqpStatsQuery} from '../../../../types/api/query'; -import type {ValueOf} from '../../../../types/common'; -import {getArray} from '../../../../utils'; -import {cn} from '../../../../utils/cn'; -import {USE_SHOW_PLAN_SVG_KEY} from '../../../../utils/constants'; -import {getStringifiedData} from '../../../../utils/dataFormatters/dataFormatters'; -import {useSetting, 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'; -import {QuerySettingsBanner} from '../QuerySettingsBanner/QuerySettingsBanner'; -import {getPreparedResult} from '../utils/getPreparedResult'; -import {isQueryCancelledError} from '../utils/isQueryCancelledError'; - -import {PlanToSvgButton} from './PlanToSvgButton'; -import {TraceButton} from './TraceButton'; -import i18n from './i18n'; -import {getPlan} from './utils'; - -import './ExecuteResult.scss'; - -const b = cn('ydb-query-execute-result'); - -const resultOptionsIds = { - result: 'result', - stats: 'stats', - schema: 'schema', - simplified: 'simplified', -} as const; - -type SectionID = ValueOf; - -interface ExecuteResultProps { - result: QueryResult; - isResultsCollapsed?: boolean; - theme?: string; - tenantName: string; - onCollapseResults: VoidFunction; - onExpandResults: VoidFunction; -} - -export function ExecuteResult({ - result, - isResultsCollapsed, - theme, - tenantName, - onCollapseResults, - onExpandResults, -}: ExecuteResultProps) { - const [selectedResultSet, setSelectedResultSet] = React.useState(0); - const [activeSection, setActiveSection] = React.useState(resultOptionsIds.result); - const dispatch = useTypedDispatch(); - const [useShowPlanToSvg] = useSetting(USE_SHOW_PLAN_SVG_KEY); - - const {error, isLoading, queryId, data} = result; - - const stats: TKqpStatsQuery | undefined = data?.stats; - const resultsSetsCount = data?.resultSets?.length || 0; - const currentResult = data?.resultSets?.[selectedResultSet]; - const {plan, simplifiedPlan} = React.useMemo(() => getPlan(data), [data]); - - const resultOptions: ControlGroupOption[] = [ - {value: resultOptionsIds.result, content: i18n('action.result')}, - {value: resultOptionsIds.stats, content: i18n('action.stats')}, - ]; - if (plan) { - resultOptions.push({value: resultOptionsIds.schema, content: i18n('action.schema')}); - } - if (simplifiedPlan?.plan) { - resultOptions.push({ - value: resultOptionsIds.simplified, - content: i18n('action.explain-plan'), - }); - } - - const parsedError = parseQueryError(error); - - React.useEffect(() => { - return () => { - dispatch(disableFullscreen()); - }; - }, [dispatch]); - - const onSelectSection = (value: SectionID) => { - setActiveSection(value); - }; - - const renderResult = () => { - return ( -
- {resultsSetsCount > 1 && ( -
- ({ - id: String(item), - title: `Result #${item + 1}${data?.resultSets?.[item]?.truncated ? ' (T)' : ''}`, - }))} - activeTab={String(selectedResultSet)} - onSelectTab={(tabId) => setSelectedResultSet(Number(tabId))} - /> -
- )} - {currentResult && ( -
-
- - {currentResult?.truncated - ? i18n('title.truncated') - : i18n('title.result')} - - {currentResult.result && ( - {`(${currentResult.result.length})`} - )} -
- -
- )} -
- ); - }; - - const getStatsToCopy = () => { - switch (activeSection) { - case resultOptionsIds.result: { - const textResults = getPreparedResult(currentResult?.result); - return textResults; - } - case resultOptionsIds.stats: - return stats; - case resultOptionsIds.simplified: - return simplifiedPlan?.pristine; - default: - return undefined; - } - }; - - const renderClipboardButton = () => { - const statsToCopy = getStatsToCopy(); - const copyText = getStringifiedData(statsToCopy); - if (!copyText) { - return null; - } - return ( - - ); - }; - - const renderStats = () => { - return ( -
- true} - searchOptions={{ - debounceTime: 300, - }} - /> -
- ); - }; - - const renderSchema = () => { - const isEnoughDataForGraph = plan?.links && plan?.nodes && plan?.nodes.length; - - if (!isEnoughDataForGraph) { - return i18n('description.graph-is-not-supported'); - } - - return ( -
- -
- ); - }; - - const renderSimplified = () => { - const {plan} = simplifiedPlan ?? {}; - if (!plan) { - return null; - } - return ; - }; - - const renderIssues = () => { - if (!parsedError) { - return null; - } - - if (typeof parsedError === 'object') { - return ; - } - - return
{parsedError}
; - }; - - const renderResultSection = () => { - if (error && !isQueryCancelledError(error)) { - return renderIssues(); - } - if (activeSection === resultOptionsIds.result) { - return renderResult(); - } - if (activeSection === resultOptionsIds.stats) { - return renderStats(); - } - if (activeSection === resultOptionsIds.schema) { - return renderSchema(); - } - if (activeSection === resultOptionsIds.simplified) { - return renderSimplified(); - } - - return null; - }; - - return ( - -
-
- - {!error && !isLoading && ( - - {stats?.DurationUs !== undefined && ( - - )} - {resultOptions && activeSection && ( - - - - - )} - - )} - {isLoading ? ( - - - - - ) : null} - {data?.traceId ? ( - - ) : null} - {data?.plan && useShowPlanToSvg ? ( - - ) : null} -
-
- {renderClipboardButton()} - - -
-
- {isLoading || isQueryCancelledError(error) ? null : } - - {renderResultSection()} - -
- ); -} diff --git a/src/containers/Tenant/Query/ExecuteResult/utils.ts b/src/containers/Tenant/Query/ExecuteResult/utils.ts deleted file mode 100644 index fd5ec2b02..000000000 --- a/src/containers/Tenant/Query/ExecuteResult/utils.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {explainVersions} from '../../../../store/reducers/query/prepareQueryData'; -import type {IQueryResult} from '../../../../types/store/query'; -import {preparePlan, prepareSimplifiedPlan} from '../../../../utils/prepareQueryExplain'; -import {parseQueryExplainPlan} from '../../../../utils/query'; - -export function getPlan(data: IQueryResult | undefined) { - if (!data) { - return {}; - } - - const {plan} = data; - - if (plan) { - const queryPlan = parseQueryExplainPlan(plan); - const isSupportedVersion = queryPlan.meta.version === explainVersions.v2; - if (!isSupportedVersion) { - return {}; - } - - const {Plan: planWithStats, SimplifiedPlan: simplifiedPlan} = queryPlan; - - return { - plan: planWithStats - ? {...preparePlan(planWithStats), tables: queryPlan.tables} - : undefined, - simplifiedPlan: { - plan: simplifiedPlan ? prepareSimplifiedPlan([simplifiedPlan]) : undefined, - pristine: simplifiedPlan, - }, - }; - } - - const {stats} = data; - const planFromStats = stats?.Executions?.[0]?.TxPlansWithStats?.[0]; - if (!planFromStats) { - return {}; - } - try { - const planWithStats = JSON.parse(planFromStats); - return {plan: preparePlan(planWithStats)}; - } catch (e) { - return {}; - } -} diff --git a/src/containers/Tenant/Query/ExplainResult/ExplainResult.tsx b/src/containers/Tenant/Query/ExplainResult/ExplainResult.tsx deleted file mode 100644 index 4e18948ee..000000000 --- a/src/containers/Tenant/Query/ExplainResult/ExplainResult.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import React from 'react'; - -import {ClipboardButton, RadioButton} from '@gravity-ui/uikit'; - -import Divider from '../../../../components/Divider/Divider'; -import ElapsedTime from '../../../../components/ElapsedTime/ElapsedTime'; -import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton'; -import Fullscreen from '../../../../components/Fullscreen/Fullscreen'; -import {LoaderWrapper} from '../../../../components/LoaderWrapper/LoaderWrapper'; -import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus'; -import {disableFullscreen} from '../../../../store/reducers/fullscreen'; -import type {QueryResult} from '../../../../store/reducers/query/types'; -import type {ValueOf} from '../../../../types/common'; -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'; - -import {Ast} from './components/Ast/Ast'; -import {Graph} from './components/Graph/Graph'; -import {SimplifiedPlan} from './components/SimplifiedPlan/SimplifiedPlan'; -import {TextExplain} from './components/TextExplain/TextExplain'; -import i18n from './i18n'; - -import './ExplainResult.scss'; - -const b = cn('ydb-query-explain-result'); - -const EXPLAIN_OPTIONS_IDS = { - schema: 'schema', - json: 'json', - ast: 'ast', - simplified: 'simplified', -} as const; - -export type QueryExplainTab = ValueOf; - -const EXPLAIN_OPTIONS_NAMES: Record = { - [EXPLAIN_OPTIONS_IDS.schema]: i18n('action.schema'), - [EXPLAIN_OPTIONS_IDS.json]: i18n('action.json'), - [EXPLAIN_OPTIONS_IDS.ast]: i18n('action.ast'), - [EXPLAIN_OPTIONS_IDS.simplified]: i18n('action.explain-plan'), -}; - -const explainOptions = [ - {value: EXPLAIN_OPTIONS_IDS.schema, content: EXPLAIN_OPTIONS_NAMES[EXPLAIN_OPTIONS_IDS.schema]}, - { - value: EXPLAIN_OPTIONS_IDS.simplified, - content: EXPLAIN_OPTIONS_NAMES[EXPLAIN_OPTIONS_IDS.simplified], - }, - {value: EXPLAIN_OPTIONS_IDS.json, content: EXPLAIN_OPTIONS_NAMES[EXPLAIN_OPTIONS_IDS.json]}, - {value: EXPLAIN_OPTIONS_IDS.ast, content: EXPLAIN_OPTIONS_NAMES[EXPLAIN_OPTIONS_IDS.ast]}, -]; - -interface ExplainResultProps { - theme: string; - result: QueryResult; - tenantName: string; - isResultsCollapsed?: boolean; - onCollapseResults: VoidFunction; - onExpandResults: VoidFunction; -} - -export function ExplainResult({ - theme, - result, - tenantName, - onCollapseResults, - onExpandResults, - isResultsCollapsed, -}: ExplainResultProps) { - const dispatch = useTypedDispatch(); - const [activeOption, setActiveOption] = React.useState( - EXPLAIN_OPTIONS_IDS.schema, - ); - const [isPending, startTransition] = React.useTransition(); - const {error, isLoading, queryId} = result; - - const {preparedPlan: explain, ast, simplifiedPlan} = result.data || {}; - - React.useEffect(() => { - return () => { - dispatch(disableFullscreen()); - }; - }, [dispatch]); - - const renderStub = () => { - return ( -
- {i18n('description.empty-result', { - activeOption: EXPLAIN_OPTIONS_NAMES[activeOption], - })} -
- ); - }; - - const renderContent = () => { - if (isQueryCancelledError(error)) { - return null; - } - - if (error) { - return
{parseQueryErrorToString(error)}
; - } - - switch (activeOption) { - case EXPLAIN_OPTIONS_IDS.json: { - if (!explain?.pristine) { - return renderStub(); - } - return ; - } - case EXPLAIN_OPTIONS_IDS.ast: { - if (!ast) { - return renderStub(); - } - return ; - } - case EXPLAIN_OPTIONS_IDS.schema: { - if (!explain?.nodes?.length) { - return renderStub(); - } - return ; - } - case EXPLAIN_OPTIONS_IDS.simplified: { - const {plan} = simplifiedPlan ?? {}; - if (!plan?.length) { - return renderStub(); - } - return ; - } - default: - return null; - } - }; - - const getStatsToCopy = () => { - switch (activeOption) { - case EXPLAIN_OPTIONS_IDS.json: - return explain?.pristine; - case EXPLAIN_OPTIONS_IDS.ast: - return ast; - case EXPLAIN_OPTIONS_IDS.simplified: - return simplifiedPlan?.pristine; - default: - return undefined; - } - }; - - const statsToCopy = getStatsToCopy(); - const copyText = getStringifiedData(statsToCopy); - - return ( - -
-
- - - {!error && !isLoading && ( - - {explain?.DurationUs !== undefined && ( - - )} - - - { - startTransition(() => setActiveOption(tabId)); - }} - /> - - - )} - {isLoading ? ( - - - - - ) : null} -
-
- {copyText && ( - - )} - - -
-
- {isLoading || isQueryCancelledError(error) ? null : } - - {renderContent()} - -
- ); -} diff --git a/src/containers/Tenant/Query/ExplainResult/components/Graph/Graph.tsx b/src/containers/Tenant/Query/ExplainResult/components/Graph/Graph.tsx deleted file mode 100644 index 645f30ea2..000000000 --- a/src/containers/Tenant/Query/ExplainResult/components/Graph/Graph.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import {YDBGraph} from '../../../../../../components/Graph/Graph'; -import {explainVersions} from '../../../../../../store/reducers/query/prepareQueryData'; -import type {PreparedPlan} from '../../../../../../store/reducers/query/types'; -import {cn} from '../../../../../../utils/cn'; -import i18n from '../../i18n'; - -import './Graph.scss'; - -const b = cn('ydb-query-explain-graph'); - -interface GraphProps { - explain: PreparedPlan; - theme: string; -} - -export function Graph({explain, theme}: GraphProps) { - const {links, nodes, version} = explain ?? {}; - - const isSupportedVersion = version === explainVersions.v2; - const isEnoughDataForGraph = links && nodes && nodes.length; - - const content = - isSupportedVersion && isEnoughDataForGraph ? ( -
- -
- ) : ( -
{i18n('description.graph-is-not-supported')}
- ); - return content; -} diff --git a/src/containers/Tenant/Query/ExplainResult/i18n/en.json b/src/containers/Tenant/Query/ExplainResult/i18n/en.json deleted file mode 100644 index 15bd21a2e..000000000 --- a/src/containers/Tenant/Query/ExplainResult/i18n/en.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "description.empty-result": "There is no {{activeOption}} for the request", - "description.graph-is-not-supported": "Graph can not be rendered", - "action.schema": "Schema", - "action.explain-plan": "Explain Plan", - "action.json": "JSON", - "action.ast": "AST", - "action.copy": "Copy {{activeOption}}" -} diff --git a/src/containers/Tenant/Query/ExplainResult/i18n/index.ts b/src/containers/Tenant/Query/ExplainResult/i18n/index.ts deleted file mode 100644 index b1aa4c531..000000000 --- a/src/containers/Tenant/Query/ExplainResult/i18n/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {registerKeysets} from '../../../../../utils/i18n'; - -import en from './en.json'; - -const COMPONENT = 'ydb-explain-result'; - -export default registerKeysets(COMPONENT, {en}); diff --git a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx index ec87c4422..29cd198ad 100644 --- a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx @@ -48,10 +48,9 @@ import { PaneVisibilityActionTypes, paneVisibilityToggleReducerCreator, } from '../../utils/paneVisibilityToggleHelpers'; -import {ExecuteResult} from '../ExecuteResult/ExecuteResult'; -import {ExplainResult} from '../ExplainResult/ExplainResult'; import {Preview} from '../Preview/Preview'; import {QueryEditorControls} from '../QueryEditorControls/QueryEditorControls'; +import {QueryResultViewer} from '../QueryResult/QueryResultViewer'; import {QuerySettingsDialog} from '../QuerySettingsDialog/QuerySettingsDialog'; import {SAVE_QUERY_DIALOG} from '../SaveQuery/SaveQuery'; import i18n from '../i18n'; @@ -399,23 +398,11 @@ function Result({ return ; } - if (result?.type === 'execute') { + if (result) { return ( - - ); - } - - if (result?.type === 'explain') { - return ( - ; + +const RESULT_OPTIONS_TITLES: Record = { + get result() { + return i18n('action.result'); + }, + get schema() { + return i18n('action.schema'); + }, + get simplified() { + return i18n('action.explain-plan'); + }, + get json() { + return i18n('action.json'); + }, + get stats() { + return i18n('action.stats'); + }, + get ast() { + return i18n('action.ast'); + }, +}; + +const EXECUTE_SECTIONS: SectionID[] = ['result', 'schema', 'simplified', 'stats']; +const EXPLAIN_SECTIONS: SectionID[] = ['schema', 'json', 'simplified', 'ast']; + +interface ExecuteResultProps { + result: QueryResult; + resultType?: QueryAction; + isResultsCollapsed?: boolean; + theme?: string; + tenantName: string; + onCollapseResults: VoidFunction; + onExpandResults: VoidFunction; +} + +export function QueryResultViewer({ + result, + resultType = 'execute', + isResultsCollapsed, + theme, + tenantName, + onCollapseResults, + onExpandResults, +}: ExecuteResultProps) { + const dispatch = useTypedDispatch(); + + const isExecute = resultType === 'execute'; + const isExplain = resultType === 'explain'; + + const [selectedResultSet, setSelectedResultSet] = React.useState(0); + const [activeSection, setActiveSection] = React.useState(() => { + return isExecute ? RESULT_OPTIONS_IDS.result : RESULT_OPTIONS_IDS.schema; + }); + const [useShowPlanToSvg] = useSetting(USE_SHOW_PLAN_SVG_KEY); + + const {error, isLoading, queryId, data = {}} = result; + const {preparedPlan, simplifiedPlan, stats, resultSets, ast} = data; + + React.useEffect(() => { + if (resultType === 'execute' && !EXECUTE_SECTIONS.includes(activeSection)) { + setActiveSection('result'); + } + if (resultType === 'explain' && !EXPLAIN_SECTIONS.includes(activeSection)) { + setActiveSection('schema'); + } + }, [activeSection, resultType]); + + const radioButtonOptions: ControlGroupOption[] = React.useMemo(() => { + let sections: SectionID[] = []; + + if (isExecute) { + sections = EXECUTE_SECTIONS; + } else if (isExplain) { + sections = EXPLAIN_SECTIONS; + } + + return sections.map((section) => { + return { + value: section, + content: RESULT_OPTIONS_TITLES[section], + }; + }); + }, [isExecute, isExplain]); + + React.useEffect(() => { + return () => { + dispatch(disableFullscreen()); + }; + }, [dispatch]); + + const onSelectSection = (value: SectionID) => { + setActiveSection(value); + }; + + const getStatsToCopy = () => { + switch (activeSection) { + case RESULT_OPTIONS_IDS.result: { + const currentResult = data?.resultSets?.[selectedResultSet]; + const textResults = getPreparedResult(currentResult?.result); + return textResults; + } + case RESULT_OPTIONS_IDS.json: { + return preparedPlan?.pristine; + } + case RESULT_OPTIONS_IDS.simplified: + return simplifiedPlan?.pristine; + case RESULT_OPTIONS_IDS.stats: + return stats; + case RESULT_OPTIONS_IDS.ast: + return ast; + default: + return undefined; + } + }; + + const renderClipboardButton = () => { + const statsToCopy = getStatsToCopy(); + const copyText = getStringifiedData(statsToCopy); + if (!copyText) { + return null; + } + return ( + + ); + }; + + const renderStubMessage = () => { + return ( + + ); + }; + + const renderResultSection = () => { + if (error) { + return ; + } + if (activeSection === RESULT_OPTIONS_IDS.result) { + return ( + + ); + } + + if (activeSection === RESULT_OPTIONS_IDS.schema) { + if (!preparedPlan?.nodes?.length) { + return renderStubMessage(); + } + return ; + } + if (activeSection === RESULT_OPTIONS_IDS.json) { + if (!preparedPlan?.pristine) { + return renderStubMessage(); + } + return ; + } + if (activeSection === RESULT_OPTIONS_IDS.simplified) { + if (!simplifiedPlan?.plan?.length) { + return renderStubMessage(); + } + return ; + } + if (activeSection === RESULT_OPTIONS_IDS.stats) { + if (!stats) { + return renderStubMessage(); + } + return ; + } + if (activeSection === RESULT_OPTIONS_IDS.ast) { + if (!ast) { + return renderStubMessage(); + } + return ; + } + + return null; + }; + + const renderLeftControls = () => { + return ( +
+ + {!error && !isLoading && ( + + {valueIsDefined(stats?.DurationUs) ? ( + + ) : null} + {radioButtonOptions.length && activeSection ? ( + + + + + ) : null} + + )} + {isLoading ? ( + + + + + ) : null} + {data?.traceId && isExecute ? ( + + ) : null} + {data?.plan && useShowPlanToSvg && isExecute ? ( + + ) : null} +
+ ); + }; + + const renderRightControls = () => { + return ( +
+ {renderClipboardButton()} + + +
+ ); + }; + + return ( + +
+ {renderLeftControls()} + {renderRightControls()} +
+ {isLoading || isQueryCancelledError(error) ? null : } + + {renderResultSection()} + +
+ ); +} diff --git a/src/containers/Tenant/Query/ExplainResult/components/Ast/Ast.scss b/src/containers/Tenant/Query/QueryResult/components/Ast/Ast.scss similarity index 77% rename from src/containers/Tenant/Query/ExplainResult/components/Ast/Ast.scss rename to src/containers/Tenant/Query/QueryResult/components/Ast/Ast.scss index 78f130944..a0e8c2d95 100644 --- a/src/containers/Tenant/Query/ExplainResult/components/Ast/Ast.scss +++ b/src/containers/Tenant/Query/QueryResult/components/Ast/Ast.scss @@ -1,4 +1,4 @@ -.ydb-query-explain-ast { +.ydb-query-ast { overflow: hidden; width: 100%; diff --git a/src/containers/Tenant/Query/ExplainResult/components/Ast/Ast.tsx b/src/containers/Tenant/Query/QueryResult/components/Ast/Ast.tsx similarity index 92% rename from src/containers/Tenant/Query/ExplainResult/components/Ast/Ast.tsx rename to src/containers/Tenant/Query/QueryResult/components/Ast/Ast.tsx index a45832458..e56c9f6e5 100644 --- a/src/containers/Tenant/Query/ExplainResult/components/Ast/Ast.tsx +++ b/src/containers/Tenant/Query/QueryResult/components/Ast/Ast.tsx @@ -5,7 +5,7 @@ import {S_EXPRESSION_LANGUAGE_ID} from '../../../../../../utils/monaco/constats' import './Ast.scss'; -const b = cn('ydb-query-explain-ast'); +const b = cn('ydb-query-ast'); const EDITOR_OPTIONS = { automaticLayout: true, @@ -19,7 +19,7 @@ const EDITOR_OPTIONS = { interface AstProps { ast: string; - theme: string; + theme?: string; } export function Ast({ast, theme}: AstProps) { diff --git a/src/containers/Tenant/Query/ExplainResult/components/Graph/Graph.scss b/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss similarity index 53% rename from src/containers/Tenant/Query/ExplainResult/components/Graph/Graph.scss rename to src/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss index 7bb89884f..565424c0e 100644 --- a/src/containers/Tenant/Query/ExplainResult/components/Graph/Graph.scss +++ b/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss @@ -4,11 +4,5 @@ width: 100%; height: 100%; - &_hidden { - display: none; - } - } - &__text-message { - padding: 15px 20px; } } diff --git a/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.tsx b/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.tsx new file mode 100644 index 000000000..3c5ef8d46 --- /dev/null +++ b/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.tsx @@ -0,0 +1,30 @@ +import {YDBGraph} from '../../../../../../components/Graph/Graph'; +import type {PreparedPlan} from '../../../../../../store/reducers/query/types'; +import {cn} from '../../../../../../utils/cn'; +import i18n from '../../i18n'; +import {StubMessage} from '../Stub/Stub'; + +import './Graph.scss'; + +const b = cn('ydb-query-explain-graph'); + +interface GraphProps { + explain?: PreparedPlan; + theme?: string; +} + +export function Graph({explain = {}, theme}: GraphProps) { + const {links, nodes} = explain; + + const isEnoughDataForGraph = links && nodes && nodes.length; + + if (!isEnoughDataForGraph) { + return ; + } + + return ( +
+ +
+ ); +} diff --git a/src/containers/Tenant/Query/ExecuteResult/PlanToSvgButton.tsx b/src/containers/Tenant/Query/QueryResult/components/PlanToSvgButton/PlanToSvgButton.tsx similarity index 91% rename from src/containers/Tenant/Query/ExecuteResult/PlanToSvgButton.tsx rename to src/containers/Tenant/Query/QueryResult/components/PlanToSvgButton/PlanToSvgButton.tsx index 8d17c5106..1c2c50a8b 100644 --- a/src/containers/Tenant/Query/ExecuteResult/PlanToSvgButton.tsx +++ b/src/containers/Tenant/Query/QueryResult/components/PlanToSvgButton/PlanToSvgButton.tsx @@ -3,10 +3,9 @@ import React from 'react'; import {ArrowUpRightFromSquare} from '@gravity-ui/icons'; import {Button, Tooltip} from '@gravity-ui/uikit'; -import {planToSvgApi} from '../../../../store/reducers/planToSvg'; -import type {QueryPlan, ScriptPlan} from '../../../../types/api/query'; - -import i18n from './i18n'; +import {planToSvgApi} from '../../../../../../store/reducers/planToSvg'; +import type {QueryPlan, ScriptPlan} from '../../../../../../types/api/query'; +import i18n from '../../i18n'; function getButtonView(error: string | null, isLoading: boolean) { if (error) { diff --git a/src/containers/Tenant/Query/ExplainResult/components/TextExplain/TextExplain.scss b/src/containers/Tenant/Query/QueryResult/components/QueryJSONViewer/QueryJSONViewer.scss similarity index 54% rename from src/containers/Tenant/Query/ExplainResult/components/TextExplain/TextExplain.scss rename to src/containers/Tenant/Query/QueryResult/components/QueryJSONViewer/QueryJSONViewer.scss index cc7aa0893..7f154b2c9 100644 --- a/src/containers/Tenant/Query/ExplainResult/components/TextExplain/TextExplain.scss +++ b/src/containers/Tenant/Query/QueryResult/components/QueryJSONViewer/QueryJSONViewer.scss @@ -1,14 +1,12 @@ @import '../../../../../../styles/mixins.scss'; -.ydb-query-explain-text { +.ydb-query-json-viewer { &__inspector { overflow-y: auto; - padding: 15px 20px; + width: 100%; + height: 100%; + padding: 15px 10px; @include json-tree-styles(); - - &_fullscreen { - padding: 10px; - } } } diff --git a/src/containers/Tenant/Query/ExplainResult/components/TextExplain/TextExplain.tsx b/src/containers/Tenant/Query/QueryResult/components/QueryJSONViewer/QueryJSONViewer.tsx similarity index 54% rename from src/containers/Tenant/Query/ExplainResult/components/TextExplain/TextExplain.tsx rename to src/containers/Tenant/Query/QueryResult/components/QueryJSONViewer/QueryJSONViewer.tsx index 9c6955ee5..393ca7657 100644 --- a/src/containers/Tenant/Query/ExplainResult/components/TextExplain/TextExplain.tsx +++ b/src/containers/Tenant/Query/QueryResult/components/QueryJSONViewer/QueryJSONViewer.tsx @@ -1,21 +1,20 @@ import JSONTree from 'react-json-inspector'; -import type {QueryPlan, ScriptPlan} from '../../../../../../types/api/query'; import {cn} from '../../../../../../utils/cn'; -import './TextExplain.scss'; +import './QueryJSONViewer.scss'; import 'react-json-inspector/json-inspector.css'; -const b = cn('ydb-query-explain-text'); +const b = cn('ydb-query-json-viewer'); -interface TextExplainProps { - explain: QueryPlan | ScriptPlan; +interface QueryJSONViewerProps { + data?: object; } -export function TextExplain({explain}: TextExplainProps) { +export function QueryJSONViewer({data}: QueryJSONViewerProps) { return ( true} className={b('inspector')} searchOptions={{ diff --git a/src/containers/Tenant/Query/QueryResult/components/QueryResultError/QueryResultError.scss b/src/containers/Tenant/Query/QueryResult/components/QueryResultError/QueryResultError.scss new file mode 100644 index 000000000..07b1c5328 --- /dev/null +++ b/src/containers/Tenant/Query/QueryResult/components/QueryResultError/QueryResultError.scss @@ -0,0 +1,5 @@ +.ydb-query-result-error { + &__message { + padding: 15px 10px; + } +} diff --git a/src/containers/Tenant/Query/QueryResult/components/QueryResultError/QueryResultError.tsx b/src/containers/Tenant/Query/QueryResult/components/QueryResultError/QueryResultError.tsx new file mode 100644 index 000000000..ac3f1daa4 --- /dev/null +++ b/src/containers/Tenant/Query/QueryResult/components/QueryResultError/QueryResultError.tsx @@ -0,0 +1,24 @@ +import {cn} from '../../../../../../utils/cn'; +import {parseQueryError} from '../../../../../../utils/query'; +import {ResultIssues} from '../../../Issues/Issues'; +import {isQueryCancelledError} from '../../../utils/isQueryCancelledError'; + +import './QueryResultError.scss'; + +const b = cn('ydb-query-result-error '); + +export function QueryResultError({error}: {error: unknown}) { + const parsedError = parseQueryError(error); + + // "Stopped" message is displayd in QueryExecutionStatus + // There is no need to display "Query is cancelled" message too + if (!parsedError || isQueryCancelledError(error)) { + return null; + } + + if (typeof parsedError === 'object') { + return ; + } + + return
{parsedError}
; +} diff --git a/src/containers/Tenant/Query/QueryResult/components/ResultSetsViewer/ResultSetsViewer.scss b/src/containers/Tenant/Query/QueryResult/components/ResultSetsViewer/ResultSetsViewer.scss new file mode 100644 index 000000000..34db8dba6 --- /dev/null +++ b/src/containers/Tenant/Query/QueryResult/components/ResultSetsViewer/ResultSetsViewer.scss @@ -0,0 +1,36 @@ +@import '../../../../../../styles/mixins.scss'; + +.ydb-query-result-sets-viewer { + &__tabs { + padding-left: 10px; + } + + &__head { + margin-top: var(--g-spacing-4); + } + + &__row-count { + margin-left: var(--g-spacing-1); + } + + &__result-wrapper { + display: flex; + flex-direction: column; + + width: 100%; + } + + &__result { + display: flex; + overflow: auto; + flex-grow: 1; + flex-direction: column; + + padding-left: 10px; + + @include query-data-table(); + & .data-table__table-wrapper { + padding-bottom: 0; + } + } +} diff --git a/src/containers/Tenant/Query/QueryResult/components/ResultSetsViewer/ResultSetsViewer.tsx b/src/containers/Tenant/Query/QueryResult/components/ResultSetsViewer/ResultSetsViewer.tsx new file mode 100644 index 000000000..6bb77857a --- /dev/null +++ b/src/containers/Tenant/Query/QueryResult/components/ResultSetsViewer/ResultSetsViewer.tsx @@ -0,0 +1,78 @@ +import {Tabs, Text} from '@gravity-ui/uikit'; + +import {QueryResultTable} from '../../../../../../components/QueryResultTable'; +import type {ParsedResultSet} from '../../../../../../types/store/query'; +import {getArray} from '../../../../../../utils'; +import {cn} from '../../../../../../utils/cn'; +import i18n from '../../i18n'; + +import './ResultSetsViewer.scss'; + +const b = cn('ydb-query-result-sets-viewer'); + +interface ResultSetsViewerProps { + resultSets?: ParsedResultSet[]; + selectedResultSet: number; + setSelectedResultSet: (resultSet: number) => void; +} + +export function ResultSetsViewer({ + resultSets, + selectedResultSet, + setSelectedResultSet, +}: ResultSetsViewerProps) { + const resultsSetsCount = resultSets?.length || 0; + const currentResult = resultSets?.[selectedResultSet]; + + const renderTabs = () => { + if (resultsSetsCount > 1) { + const tabsItems = getArray(resultsSetsCount).map((item) => ({ + id: String(item), + title: `Result #${item + 1}${resultSets?.[item]?.truncated ? ' (T)' : ''}`, + })); + + return ( +
+ setSelectedResultSet(Number(tabId))} + /> +
+ ); + } + + return null; + }; + + const renderResultHeadWithCount = () => { + return ( +
+ + {currentResult?.truncated ? i18n('title.truncated') : i18n('title.result')} + + {currentResult?.result ? ( + {`(${currentResult?.result.length})`} + ) : null} +
+ ); + }; + + return ( +
+ {renderTabs()} + {currentResult ? ( +
+ {renderResultHeadWithCount()} + +
+ ) : null} +
+ ); +} diff --git a/src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/MetricsCell.tsx b/src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/MetricsCell.tsx similarity index 100% rename from src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/MetricsCell.tsx rename to src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/MetricsCell.tsx diff --git a/src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/OperationCell.tsx b/src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/OperationCell.tsx similarity index 100% rename from src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/OperationCell.tsx rename to src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/OperationCell.tsx diff --git a/src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/OperationParams.tsx b/src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/OperationParams.tsx similarity index 100% rename from src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/OperationParams.tsx rename to src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/OperationParams.tsx diff --git a/src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/SimplifiedPlan.scss b/src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/SimplifiedPlan.scss similarity index 98% rename from src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/SimplifiedPlan.scss rename to src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/SimplifiedPlan.scss index 403a589b9..2127858db 100644 --- a/src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/SimplifiedPlan.scss +++ b/src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/SimplifiedPlan.scss @@ -1,6 +1,6 @@ @import '../../../../../../styles/mixins.scss'; -.ydb-query-explain-simplified-plan { +.ydb-query-simplified-plan { $block: &; overflow: auto; diff --git a/src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/SimplifiedPlan.tsx b/src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/SimplifiedPlan.tsx similarity index 100% rename from src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/SimplifiedPlan.tsx rename to src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/SimplifiedPlan.tsx diff --git a/src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/__tests__/utils.test.ts b/src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/__tests__/utils.test.ts similarity index 100% rename from src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/__tests__/utils.test.ts rename to src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/__tests__/utils.test.ts diff --git a/src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/types.ts b/src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/types.ts similarity index 100% rename from src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/types.ts rename to src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/types.ts diff --git a/src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/utils.ts b/src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/utils.ts similarity index 96% rename from src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/utils.ts rename to src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/utils.ts index 399709ff0..8ef99dcfa 100644 --- a/src/containers/Tenant/Query/ExplainResult/components/SimplifiedPlan/utils.ts +++ b/src/containers/Tenant/Query/QueryResult/components/SimplifiedPlan/utils.ts @@ -3,7 +3,7 @@ import {cn} from '../../../../../../utils/cn'; import type {ExtendedSimplifiesPlanItem} from './types'; -export const block = cn('ydb-query-explain-simplified-plan'); +export const block = cn('ydb-query-simplified-plan'); export function getExtendedTreeNodes( items?: SimplifiedPlanItem[], diff --git a/src/containers/Tenant/Query/QueryResult/components/Stub/Stub.scss b/src/containers/Tenant/Query/QueryResult/components/Stub/Stub.scss new file mode 100644 index 000000000..336c27805 --- /dev/null +++ b/src/containers/Tenant/Query/QueryResult/components/Stub/Stub.scss @@ -0,0 +1,3 @@ +.ydb-query-result-stub-message { + padding: 15px 20px; +} diff --git a/src/containers/Tenant/Query/QueryResult/components/Stub/Stub.tsx b/src/containers/Tenant/Query/QueryResult/components/Stub/Stub.tsx new file mode 100644 index 000000000..95e8af0f4 --- /dev/null +++ b/src/containers/Tenant/Query/QueryResult/components/Stub/Stub.tsx @@ -0,0 +1,13 @@ +import {cn} from '../../../../../../utils/cn'; + +import './Stub.scss'; + +const b = cn('ydb-query-result-stub-message'); + +interface StubMessageProps { + message: string; +} + +export function StubMessage({message}: StubMessageProps) { + return
{message}
; +} diff --git a/src/containers/Tenant/Query/ExecuteResult/TraceButton.tsx b/src/containers/Tenant/Query/QueryResult/components/TraceButton/TraceButton.tsx similarity index 84% rename from src/containers/Tenant/Query/ExecuteResult/TraceButton.tsx rename to src/containers/Tenant/Query/QueryResult/components/TraceButton/TraceButton.tsx index a42a768ab..948b75c7c 100644 --- a/src/containers/Tenant/Query/ExecuteResult/TraceButton.tsx +++ b/src/containers/Tenant/Query/QueryResult/components/TraceButton/TraceButton.tsx @@ -3,11 +3,10 @@ import React from 'react'; import {ArrowUpRightFromSquare} from '@gravity-ui/icons'; import {Button} from '@gravity-ui/uikit'; -import {useClusterBaseInfo} from '../../../../store/reducers/cluster/cluster'; -import {traceApi} from '../../../../store/reducers/trace'; -import {replaceParams} from '../utils/replaceParams'; - -import i18n from './i18n'; +import {useClusterBaseInfo} from '../../../../../../store/reducers/cluster/cluster'; +import {traceApi} from '../../../../../../store/reducers/trace'; +import {replaceParams} from '../../../utils/replaceParams'; +import i18n from '../../i18n'; interface TraceUrlButtonProps { traceId: string; diff --git a/src/containers/Tenant/Query/ExecuteResult/i18n/en.json b/src/containers/Tenant/Query/QueryResult/i18n/en.json similarity index 78% rename from src/containers/Tenant/Query/ExecuteResult/i18n/en.json rename to src/containers/Tenant/Query/QueryResult/i18n/en.json index 5bcdee8d3..c8a4730c0 100644 --- a/src/containers/Tenant/Query/ExecuteResult/i18n/en.json +++ b/src/containers/Tenant/Query/QueryResult/i18n/en.json @@ -1,9 +1,12 @@ { "description.graph-is-not-supported": "Graph can not be rendered", + "description.empty-result": "There is no {{activeSection}} for the request", "action.result": "Result", "action.stats": "Stats", "action.schema": "Schema", "action.explain-plan": "Explain Plan", + "action.json": "JSON", + "action.ast": "AST", "action.copy": "Copy {{activeSection}}", "trace": "Trace", "title.truncated": "Truncated", diff --git a/src/containers/Tenant/Query/ExecuteResult/i18n/index.ts b/src/containers/Tenant/Query/QueryResult/i18n/index.ts similarity index 100% rename from src/containers/Tenant/Query/ExecuteResult/i18n/index.ts rename to src/containers/Tenant/Query/QueryResult/i18n/index.ts diff --git a/src/store/reducers/query/prepareQueryData.ts b/src/store/reducers/query/prepareQueryData.ts index b35751901..22d0eabe9 100644 --- a/src/store/reducers/query/prepareQueryData.ts +++ b/src/store/reducers/query/prepareQueryData.ts @@ -6,7 +6,7 @@ import {parseQueryAPIResponse, parseQueryExplainPlan} from '../../../utils/query import type {PreparedQueryData} from './types'; -export const explainVersions = { +const explainVersions = { v2: '0.2', }; @@ -16,47 +16,58 @@ export function prepareQueryData( response: ExplainResponse | ExecuteResponse | null, ): PreparedQueryData { const result = parseQueryAPIResponse(response); - const {plan: rawPlan} = result; + const {plan: rawPlan, stats} = result; - if (!rawPlan) { - return result; - } + if (rawPlan) { + const {tables, meta, Plan, SimplifiedPlan} = parseQueryExplainPlan(rawPlan); + + if (supportedExplainQueryVersions.indexOf(meta.version) === -1) { + // Do not prepare plan for not supported versions + return { + ...result, + preparedPlan: { + pristine: rawPlan, + version: meta.version, + }, + }; + } - const {tables, meta, Plan, SimplifiedPlan} = parseQueryExplainPlan(rawPlan); + let links: Link[] = []; + let nodes: GraphNode[] = []; + + if (Plan) { + const preparedPlan = preparePlan(Plan); + links = preparedPlan.links; + nodes = preparedPlan.nodes; + } + let preparedSimplifiedPlan; + if (SimplifiedPlan) { + preparedSimplifiedPlan = prepareSimplifiedPlan([SimplifiedPlan]); + } - if (supportedExplainQueryVersions.indexOf(meta.version) === -1) { - // Do not prepare plan for not supported versions return { ...result, preparedPlan: { - pristine: rawPlan, + links, + nodes, + tables, version: meta.version, + pristine: rawPlan, }, + simplifiedPlan: {plan: preparedSimplifiedPlan, pristine: SimplifiedPlan}, }; } - let links: Link[] = []; - let nodes: GraphNode[] = []; - - if (Plan) { - const preparedPlan = preparePlan(Plan); - links = preparedPlan.links; - nodes = preparedPlan.nodes; - } - let preparedSimplifiedPlan; - if (SimplifiedPlan) { - preparedSimplifiedPlan = prepareSimplifiedPlan([SimplifiedPlan]); + const planFromStats = stats?.Executions?.[0]?.TxPlansWithStats?.[0]; + if (planFromStats) { + try { + const planWithStats = JSON.parse(planFromStats); + return { + ...result, + preparedPlan: {...preparePlan(planWithStats), pristine: planWithStats}, + }; + } catch (e) {} } - return { - ...result, - preparedPlan: { - links, - nodes, - tables, - version: meta.version, - pristine: rawPlan, - }, - simplifiedPlan: {plan: preparedSimplifiedPlan, pristine: SimplifiedPlan}, - }; + return result; } diff --git a/tests/suites/tenant/queryEditor/models/QueryEditor.ts b/tests/suites/tenant/queryEditor/models/QueryEditor.ts index cae3c8ce9..67d387939 100644 --- a/tests/suites/tenant/queryEditor/models/QueryEditor.ts +++ b/tests/suites/tenant/queryEditor/models/QueryEditor.ts @@ -65,7 +65,7 @@ export class QueryEditor { this.explainButton = this.selector.getByRole('button', {name: ButtonNames.Explain}); this.gearButton = this.selector.locator('.ydb-query-editor-controls__gear-button'); this.executionStatus = this.selector.locator('.kv-query-execution-status'); - this.resultsControls = this.selector.locator('.ydb-query-execute-result__controls'); + this.resultsControls = this.selector.locator('.ydb-query-result__controls'); this.indicatorIcon = this.selector.locator( '.kv-query-execution-status__query-settings-icon', ); @@ -118,14 +118,14 @@ export class QueryEditor { async getExplainResult(type: ExplainResultType) { await this.selectResultTypeRadio(type); - const resultArea = this.selector.locator('.ydb-query-explain-result__result'); + const resultArea = this.selector.locator('.ydb-query-result__result'); switch (type) { case ExplainResultType.Schema: - return resultArea.locator('.canvas-container'); + return resultArea.locator('.ydb-query-explain-graph__canvas-container'); case ExplainResultType.JSON: - return resultArea.locator('.json-inspector'); + return resultArea.locator('.ydb-query-json-viewer__inspector'); case ExplainResultType.AST: - return resultArea.locator('.ydb-query-explain-ast'); + return resultArea.locator('.ydb-query-ast'); } } diff --git a/tests/suites/tenant/queryEditor/models/ResultTable.ts b/tests/suites/tenant/queryEditor/models/ResultTable.ts index 4417d34b4..8798d307b 100644 --- a/tests/suites/tenant/queryEditor/models/ResultTable.ts +++ b/tests/suites/tenant/queryEditor/models/ResultTable.ts @@ -26,9 +26,9 @@ export class ResultTable { private resultHead: Locator; constructor(selector: Locator) { - this.table = selector.locator('.ydb-query-execute-result__result'); + this.table = selector.locator('.ydb-query-result-sets-viewer__result'); this.preview = selector.locator('.kv-preview__result'); - this.resultHead = selector.locator('.ydb-query-execute-result__result-head'); + this.resultHead = selector.locator('.ydb-query-result-sets-viewer__head'); } async isVisible() { diff --git a/tests/suites/tenant/queryHistory/queryHistory.test.ts b/tests/suites/tenant/queryHistory/queryHistory.test.ts index 5eb971068..7e08853a3 100644 --- a/tests/suites/tenant/queryHistory/queryHistory.test.ts +++ b/tests/suites/tenant/queryHistory/queryHistory.test.ts @@ -78,7 +78,7 @@ test.describe('Query History', () => { await executeQueryWithKeybinding(page, browserName); // Wait for the query to be executed - await page.waitForSelector('.ydb-query-execute-result__result', {timeout: 10000}); + await page.waitForSelector('.ydb-query-result-sets-viewer__result', {timeout: 10000}); // Navigate to the history tab await page.click('text=History');