Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

chore(Queries): query result component refactoring [YTFRONT-4423] #819

Merged
merged 3 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/ui/src/ui/UIFactory/default-ui-factory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {uiSettings} from '../config/ui-settings';
import YT from '../config/yt-config';
import {DefaultSubjectLinkLazy} from '../components/SubjectLink/lazy';
import type {SubjectCardProps} from '../components/SubjectLink/SubjectLink';
import {CUSTOM_QUERY_REQULT_TAB} from '../pages/query-tracker/QueryResultsVisualization';
import {QUERY_RESULT_CHART_TAB} from '../pages/query-tracker/QueryResultsVisualization';

import {UIFactory} from './index';

Expand Down Expand Up @@ -265,8 +265,8 @@ export const defaultUIFactory: UIFactory = {
return undefined;
},

getCustomQueryResultTab() {
return CUSTOM_QUERY_REQULT_TAB;
getQueryResultChartTab() {
return QUERY_RESULT_CHART_TAB;
},

getExternalSettings() {
Expand Down
12 changes: 6 additions & 6 deletions packages/ui/src/ui/UIFactory/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ export type ExtraTab = {
position: {before: TabName} | {after: TabName};
};

export type QueryResultChartTab = {
title: string;
renderContent: (params: {query: QueryItem}) => React.ReactNode;
};

export interface UIFactory {
getClusterAppearance(cluster?: string): undefined | ClusterAppearance;

Expand Down Expand Up @@ -383,12 +388,7 @@ export interface UIFactory {

renderRolesLink(params: {cluster: string; login: string; className?: string}): React.ReactNode;

getCustomQueryResultTab():
| undefined
| {
title: string;
renderContent: (params: {query: QueryItem}) => React.ReactNode;
};
getQueryResultChartTab(): QueryResultChartTab | undefined;

getExternalSettings(params: {
cluster: string;
Expand Down
55 changes: 0 additions & 55 deletions packages/ui/src/ui/pages/query-tracker/Plan/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import type {DataSet, Edge, Network, Node} from 'vis-network';
import type {GraphColors} from './GraphColors';

import {DateTime} from 'luxon';
import {parseTablePath} from './services/tables';
import {genNavigationUrl} from '../../../utils/navigation/navigation';

export type ParsableDate = string | number | Date | DateTime | {} | undefined | null;

Expand Down Expand Up @@ -586,59 +584,6 @@ export function getConnectedEdges(
return connectedEdges;
}

export function usePrepareNode(operationIdToCluster: Map<string, string>) {
return React.useCallback((node: ProcessedNode) => {
if (node.type === 'in' || node.type === 'out') {
const table = parseTablePath(node.title ?? '');
if (table) {
node.url = genNavigationUrl({cluster: table.cluster, path: table.path});
}
} else if (node.progress?.remoteId) {
const id = node.progress?.remoteId.split('/').pop();

if (!id) {
node.url = getOperationUrl(node);

return node;
}

const cluster = operationIdToCluster.has(id)
? operationIdToCluster.get(id)
: node.progress?.remoteData?.cluster_name;

if (cluster) {
node.url = buildOperationUrl(cluster, id);
}
}

return node;
}, []);
}

function getOperationUrl(node: ProcessedNode) {
const remoteId = node.progress?.remoteId;
if (!remoteId) {
return undefined;
}
const idParts = remoteId.split('/');
const cluster = idParts[0];
const clusterName = cluster.split('.')[0];
const url = buildOperationUrl(clusterName, idParts[1], idParts[2]);
return url ? url : undefined;
}

function buildOperationUrl(cluster: string, operation: string, tag?: string) {
let uri = '';

if (tag === undefined) {
uri = `/operations/${encodeURIComponent(operation)}`;
} else if (tag === 'filter') {
uri = `/operations?type=all&state=all&filter=${encodeURIComponent(operation)}`;
}

return `/${cluster.split('.')[0]}${uri}`;
}

export function drawRunningIcon(progress: NodeProgress | undefined, {operation}: GraphColors) {
const div = document.createElement('div');
const colors: string[] = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, {FC, useCallback} from 'react';
import Plan from '../Plan/Plan';
import {ProcessedNode} from '../Plan/utils';
import {parseTablePath} from '../Plan/services/tables';
import {genNavigationUrl} from '../../../utils/navigation/navigation';
import {buildOperationUrl} from './helpers/buildOperationUrl';
import {getOperationUrl} from './helpers/getOperationUrl';

type Props = {
isActive: boolean;
operationIdToCluster: Map<string, string>;
};

export const PlanContainer: FC<Props> = ({isActive, operationIdToCluster}) => {
const handlePrepareNode = useCallback(
(node: ProcessedNode) => {
if (node.type === 'in' || node.type === 'out') {
const table = parseTablePath(node.title ?? '');
if (table) {
node.url = genNavigationUrl({cluster: table.cluster, path: table.path});
}
return node;
}

if (node.progress?.remoteId) {
const id = node.progress?.remoteId.split('/').pop();

if (!id) {
node.url = getOperationUrl(node);
return node;
}

const cluster = operationIdToCluster.has(id)
? operationIdToCluster.get(id)
: node.progress?.remoteData?.cluster_name;

if (cluster) {
node.url = buildOperationUrl(cluster, id);
}
}

return node;
},
[operationIdToCluster],
);

return <Plan isActive={isActive} prepareNode={handlePrepareNode} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {FC} from 'react';
import UIFactory from '../../../UIFactory';
import {QueryItem} from '../module/api';

type Props = {
query: QueryItem;
};

export const QueryChartTab: FC<Props> = (props) => {
return UIFactory.getQueryResultChartTab()?.renderContent(props) || null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React, {FC, useEffect} from 'react';
import {QueryItem} from '../module/api';
import {useDispatch} from 'react-redux';
import {loadQueryResult} from '../module/query_result/actions';
import {QueryResultsView} from '../QueryResultsView';

type Props = {
query: QueryItem;
activeResultParams?: {queryId: string; resultIndex: number};
};

export const QueryResultContainer: FC<Props> = ({query, activeResultParams}) => {
const dispatch = useDispatch();

useEffect(() => {
if (activeResultParams) {
dispatch(loadQueryResult(activeResultParams.queryId, activeResultParams.resultIndex));
}
}, [activeResultParams, dispatch]);

return <QueryResultsView query={query} index={activeResultParams?.resultIndex || 0} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const buildOperationUrl = (cluster: string, operation: string, tag?: string) => {
let uri = '';

if (tag === undefined) {
uri = `/operations/${encodeURIComponent(operation)}`;
} else if (tag === 'filter') {
uri = `/operations?type=all&state=all&filter=${encodeURIComponent(operation)}`;
}

return `/${cluster.split('.')[0]}${uri}`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {YQLSstatistics} from '../../module/api';

export const extractOperationIdToCluster = (
statistics: YQLSstatistics | undefined,
): Map<string, string> => {
const clusterNames: Map<string, string> = new Map();

if (!statistics) return clusterNames;

const traverse = (obj: YQLSstatistics) => {
for (const key in obj) {
if (key === '_cluster_name') {
clusterNames.set(obj._id, obj._cluster_name);
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
traverse(obj[key]);
}
}
};

traverse(statistics);

return clusterNames;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {ProcessedNode} from '../../Plan/utils';
import {buildOperationUrl} from './buildOperationUrl';

export const getOperationUrl = (node: ProcessedNode) => {
const remoteId = node.progress?.remoteId;
if (!remoteId) {
return undefined;
}
const idParts = remoteId.split('/');
const cluster = idParts[0];
const clusterName = cluster.split('.')[0];
const url = buildOperationUrl(clusterName, idParts[1], idParts[2]);
return url ? url : undefined;
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export enum QueryResultTab {
RESULT = 'result',
STATISTIC = 'statistic',
PROGRESS = 'progress',
CUSTOM_TAB = 'custom-tab',
CHART_TAB = 'chart-tab',
}

const isResultTab = (tabId: string) => tabId.startsWith('result/');
Expand Down Expand Up @@ -90,12 +90,12 @@ export const useQueryResultTabs = (
if (query.state === QueryStatus.FAILED) {
items.unshift({id: QueryResultTab.ERROR, title: 'Error'});
} else if (query.state === QueryStatus.COMPLETED) {
const customQueryResultTab = UIFactory.getCustomQueryResultTab();
const queryResultChartTab = UIFactory.getQueryResultChartTab();

if (customQueryResultTab && query.result_count) {
if (queryResultChartTab && query.result_count) {
items.unshift({
id: QueryResultTab.CUSTOM_TAB,
title: customQueryResultTab.title,
id: QueryResultTab.CHART_TAB,
title: queryResultChartTab.title,
});
}

Expand Down
84 changes: 14 additions & 70 deletions packages/ui/src/ui/pages/query-tracker/QueryResults/index.tsx
Original file line number Diff line number Diff line change
@@ -1,84 +1,37 @@
import React, {useEffect} from 'react';
import React, {ReactNode} from 'react';
import block from 'bem-cn-lite';
import {QueryItem, YQLSstatistics} from '../module/api';
import {QueryItem} from '../module/api';
import {Tabs} from '@gravity-ui/uikit';
import {useDispatch} from 'react-redux';
import {QueryResultsView} from '../QueryResultsView';
import {QueryMetaInfo} from './QueryMetaRow';
import QueryMetaTable from '../QueryMetaTable';
import {loadQueryResult} from '../module/query_result/actions';
import {QueryResultActions} from './QueryResultActions';
import {QueryResultTab, useQueryResultTabs} from './hooks/useQueryResultTabs';
import {YQLStatisticsTable} from '../QueryResultsView/YQLStatistics';
import NotRenderUntilFirstVisible from '../NotRenderUntilFirstVisible/NotRenderUntilFirstVisible';
import {PlanProvider} from '../Plan/PlanContext';
import Plan from '../Plan/Plan';
import {usePrepareNode} from '../Plan/utils';
import PlanActions from '../Plan/PlanActions';
import {QueryResultContainer} from './QueryResultContainer';
import {QueryChartTab} from './QueryChartTab';
import {PlanContainer} from './PlanContainer';
import {extractOperationIdToCluster} from './helpers/extractOperationIdToCluster';

import './index.scss';
import {ErrorTree} from './ErrorTree';
import {QueryProgress} from './QueryResultActions/QueryProgress';
import UIFactory from '../../../UIFactory';

const b = block('query-results');

function QueryResultContainer({
query,
activeResultParams,
}: {
type Props = {
query: QueryItem;
activeResultParams?: {queryId: string; resultIndex: number};
}) {
const dispatch = useDispatch();
useEffect(() => {
if (activeResultParams) {
dispatch(loadQueryResult(activeResultParams.queryId, activeResultParams.resultIndex));
}
}, [activeResultParams, dispatch]);
return <QueryResultsView query={query} index={activeResultParams?.resultIndex || 0} />;
}

function CustomQueryTabContainer({query}: {query: QueryItem}) {
const customQueryResultTab = UIFactory.getCustomQueryResultTab();

if (!customQueryResultTab) {
return null;
}

return customQueryResultTab.renderContent({query});
}

function extractOperationIdToCluster(obj: YQLSstatistics | undefined): Map<string, string> {
const clusterNames: Map<string, string> = new Map();

if (!obj) return clusterNames;

const traverse = (o: YQLSstatistics) => {
for (const key in o) {
if (key === '_cluster_name') {
clusterNames.set(o._id, o._cluster_name);
} else if (typeof o[key] === 'object' && o[key] !== null) {
traverse(o[key]);
}
}
};

traverse(obj);

return clusterNames;
}
className: string;
toolbar: ReactNode;
minimized: boolean;
};

export const QueryResults = React.memo(function QueryResults({
export const QueryResults = React.memo<Props>(function QueryResults({
query,
className,
toolbar,
minimized = false,
}: {
query: QueryItem;
className: string;
toolbar: React.ReactChild;
minimized: boolean;
}) {
const [tabs, setTab, {activeTabId, category, activeResultParams}] = useQueryResultTabs(query);
const operationIdToCluster = React.useMemo(
Expand Down Expand Up @@ -130,10 +83,10 @@ export const QueryResults = React.memo(function QueryResults({
/>
</NotRenderUntilFirstVisible>
<NotRenderUntilFirstVisible
hide={category !== QueryResultTab.CUSTOM_TAB}
hide={category !== QueryResultTab.CHART_TAB}
className={b('result-wrap')}
>
<CustomQueryTabContainer query={query} />
<QueryChartTab query={query} />
</NotRenderUntilFirstVisible>
{category === QueryResultTab.ERROR && <ErrorTree rootError={query.error} />}
{category === QueryResultTab.META && <QueryMetaTable query={query} />}
Expand All @@ -154,12 +107,3 @@ export const QueryResults = React.memo(function QueryResults({
</div>
);
});

interface PlanContainerProps {
isActive: boolean;
operationIdToCluster: Map<string, string>;
}

function PlanContainer({isActive, operationIdToCluster}: PlanContainerProps) {
return <Plan isActive={isActive} prepareNode={usePrepareNode(operationIdToCluster)} />;
}
Loading
Loading