Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

refactor: migrate ExecuteResult to ts #561

Merged
merged 3 commits into from
Oct 19, 2023
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
5 changes: 3 additions & 2 deletions src/components/QueryExecutionStatus/QueryExecutionStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ const b = cn('kv-query-execution-status');

interface QueryExecutionStatusProps {
className?: string;
error?: AxiosError | Record<string, any>;
// TODO: Remove Record<string, any> when ECONNABORTED error case is fully typed
error?: AxiosError | Record<string, any> | string;
Copy link
Member Author

@artemmufazalov artemmufazalov Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't remove Record<string, any>, it seems the case with aborted connection error isn't covered by types yet. I plan to reconsider it later, when full components chain will be converted to TS (Query -> QueryEditor -> ExecuteResult && ExplainResult)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets add TODO: to not forget?

}

export const QueryExecutionStatus = ({className, error}: QueryExecutionStatusProps) => {
let icon: ReactNode;
let label: string;

if (error?.code === 'ECONNABORTED') {
if (typeof error === 'object' && error?.code === 'ECONNABORTED') {
icon = <UiKitIcon data={questionIcon} size={16} />;
label = 'Connection aborted';
} else {
Expand Down
5 changes: 2 additions & 3 deletions src/containers/Tenant/Diagnostics/Describe/Describe.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {useCallback, useEffect, useState} from 'react';
import {shallowEqual, useDispatch} from 'react-redux';
import cn from 'bem-cn-lite';
// @ts-ignore
import JSONTree from 'react-json-inspector';
import 'react-json-inspector/json-inspector.css';

Expand Down Expand Up @@ -99,14 +98,14 @@ const Describe = ({tenant, type}: IDescribeProps) => {
<JSONTree
data={preparedDescribeData}
className={b('tree')}
onClick={({path}: {path: string}) => {
onClick={({path}) => {
const newValue = !(expandMap.get(path) || false);
expandMap.set(path, newValue);
}}
searchOptions={{
debounceTime: 300,
}}
isExpanded={(keypath: string) => {
isExpanded={(keypath) => {
return expandMap.get(keypath) || false;
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {useCallback, useState} from 'react';
import cn from 'bem-cn-lite';
import _omit from 'lodash/omit';

// @ts-ignore
import JSONTree from 'react-json-inspector';

import {TreeView} from 'ydb-ui-components';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {useEffect, useState} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import React, {type ReactNode, useEffect, useState} from 'react';
import {useDispatch} from 'react-redux';
import cn from 'bem-cn-lite';
import JSONTree from 'react-json-inspector';

Expand All @@ -11,13 +11,15 @@ import EnableFullscreenButton from '../../../../components/EnableFullscreenButto
import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus';

import type {ValueOf} from '../../../../types/common';
import type {IQueryResult, QueryErrorResponse} from '../../../../types/store/query';
import {disableFullscreen} from '../../../../store/reducers/fullscreen';

import {prepareQueryError} from '../../../../utils/query';
import {useTypedSelector} from '../../../../utils/hooks';

import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers';

import ResultIssues from '../Issues/Issues';
import {ResultIssues} from '../Issues/Issues';
import {QueryDuration} from '../QueryDuration/QueryDuration';

import './ExecuteResult.scss';
Expand All @@ -27,31 +29,51 @@ const b = cn('ydb-query-execute-result');
const resultOptionsIds = {
result: 'result',
stats: 'stats',
};
} as const;

type SectionID = ValueOf<typeof resultOptionsIds>;

const resultOptions = [
{value: resultOptionsIds.result, content: 'Result'},
{value: resultOptionsIds.stats, content: 'Stats'},
];

export function ExecuteResult(props) {
const [activeSection, setActiveSection] = useState(resultOptionsIds.result);
const isFullscreen = useSelector((state) => state.fullscreen);
interface ExecuteResultProps {
textResults: string;
result: ReactNode;
stats: IQueryResult['stats'] | undefined;
error: string | QueryErrorResponse | undefined;
copyDisabled?: boolean;
isResultsCollapsed?: boolean;
onCollapseResults: VoidFunction;
onExpandResults: VoidFunction;
}

export function ExecuteResult({
textResults,
result,
stats,
error,
copyDisabled,
isResultsCollapsed,
onCollapseResults,
onExpandResults,
}: ExecuteResultProps) {
const [activeSection, setActiveSection] = useState<SectionID>(resultOptionsIds.result);
const isFullscreen = useTypedSelector((state) => state.fullscreen);
const dispatch = useDispatch();

useEffect(() => {
return () => {
dispatch(disableFullscreen());
};
}, []);
}, [dispatch]);

const onSelectSection = (value) => {
setActiveSection(value);
const onSelectSection = (value: string) => {
setActiveSection(value as SectionID);
};

const renderClipboardButton = () => {
const {textResults, copyDisabled} = props;

return (
<CopyToClipboard
text={textResults}
Expand All @@ -65,7 +87,7 @@ export function ExecuteResult(props) {
const renderStats = () => {
const content = (
<JSONTree
data={props.stats}
data={stats}
isExpanded={() => true}
className={b('inspector')}
searchOptions={{
Expand All @@ -86,8 +108,6 @@ export function ExecuteResult(props) {
};

const renderResult = () => {
const {result} = props;

return (
<React.Fragment>
{result}
Expand All @@ -101,11 +121,11 @@ export function ExecuteResult(props) {
};

const renderIssues = () => {
const error = props.error;

const hasIssues = error?.data?.issues && Array.isArray(error.data.issues);
if (!error) {
return null;
}

if (hasIssues) {
if (typeof error === 'object' && error.data?.issues && Array.isArray(error.data.issues)) {
return (
<React.Fragment>
<ResultIssues data={error.data} />
Expand All @@ -120,20 +140,20 @@ export function ExecuteResult(props) {
);
}

if (error) {
return <div className={b('error')}>{prepareQueryError(error)}</div>;
}
const parsedError = typeof error === 'string' ? error : prepareQueryError(error);

return <div className={b('error')}>{parsedError}</div>;
};

return (
<React.Fragment>
<div className={b('controls')}>
<div className={b('controls-right')}>
<QueryExecutionStatus error={props.error} />
<QueryExecutionStatus error={error} />

{props.stats && !props.error && (
{stats && !error && (
<React.Fragment>
<QueryDuration duration={props.stats?.DurationUs} />
<QueryDuration duration={stats?.DurationUs} />
<Divider />
<RadioButton
options={resultOptions}
Expand All @@ -147,16 +167,16 @@ export function ExecuteResult(props) {
{renderClipboardButton()}
<EnableFullscreenButton />
<PaneVisibilityToggleButtons
onCollapse={props.onCollapseResults}
onExpand={props.onExpandResults}
isCollapsed={props.isResultsCollapsed}
onCollapse={onCollapseResults}
onExpand={onExpandResults}
isCollapsed={isResultsCollapsed}
initialDirection="bottom"
/>
</div>
</div>
<div className={b('result')}>
{activeSection === resultOptionsIds.result && !props.error && renderResult()}
{activeSection === resultOptionsIds.stats && !props.error && renderStats()}
{activeSection === resultOptionsIds.result && !error && renderResult()}
{activeSection === resultOptionsIds.stats && !error && renderStats()}
{renderIssues()}
</div>
</React.Fragment>
Expand Down
10 changes: 4 additions & 6 deletions src/containers/Tenant/Query/Issues/Issues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ const blockIssue = cn('kv-issue');

interface ResultIssuesProps {
data: ErrorResponse | string;
className: string;
}

export default function ResultIssues({data, className}: ResultIssuesProps) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

className wasn't used

export function ResultIssues({data}: ResultIssuesProps) {
const [showIssues, setShowIssues] = React.useState(false);

const issues = typeof data === 'string' ? undefined : data?.issues;
Expand Down Expand Up @@ -59,22 +58,21 @@ export default function ResultIssues({data, className}: ResultIssuesProps) {
</Button>
)}
</div>
{hasIssues && showIssues && <Issues issues={issues} className={className} />}
{hasIssues && showIssues && <Issues issues={issues} />}
</div>
);
}

interface IssuesProps {
className?: string;
issues: IssueMessage[] | null | undefined;
}
export function Issues({issues, className}: IssuesProps) {
export function Issues({issues}: IssuesProps) {
const mostSevereIssue = issues?.reduce((result, issue) => {
const severity = issue.severity ?? 10;
return Math.min(result, severity);
}, 10);
return (
<div className={blockIssues(null, className)}>
<div className={blockIssues(null)}>
{issues?.map((issue, index) => (
<Issue key={index} issue={issue} expanded={issue === mostSevereIssue} />
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import i18n from '../i18n';
import './QueryDuration.scss';

interface QueryDurationProps {
duration?: string;
duration?: string | number;
}

const b = block('ydb-query-duration');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function paneVisibilityToggleReducerCreator(isPaneCollapsedKey: string) {
interface ToggleButtonProps {
onCollapse: VoidFunction;
onExpand: VoidFunction;
isCollapsed: boolean;
isCollapsed?: boolean;
initialDirection?: 'right' | 'left' | 'top' | 'bottom';
className?: string;
}
Expand Down
21 changes: 21 additions & 0 deletions src/types/react-json-inspector.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
declare module 'react-json-inspector' {
// This typing is sufficient for current use cases, but some types are incompelete
class JSONTree extends React.Component<{
data?: object;
search?: boolean;
searchOptions?: {
debounceTime?: number;
};
onClick?: ({path: string, key: string, value: object}) => void;
validateQuery?: (query: string) => boolean;
isExpanded?: (keypath: string) => boolean;
filterOptions?: {
cacheResults?: bool;
ignoreCase?: bool;
};
query?: string;
verboseShowOriginal?: boolean;
className?: string;
}> {}
export default JSONTree;
}
Loading