diff --git a/src/components/QueryExecutionStatus/QueryExecutionStatus.tsx b/src/components/QueryExecutionStatus/QueryExecutionStatus.tsx index 66d08ee4e..352d58780 100644 --- a/src/components/QueryExecutionStatus/QueryExecutionStatus.tsx +++ b/src/components/QueryExecutionStatus/QueryExecutionStatus.tsx @@ -14,14 +14,15 @@ const b = cn('kv-query-execution-status'); interface QueryExecutionStatusProps { className?: string; - error?: AxiosError | Record; + // TODO: Remove Record when ECONNABORTED error case is fully typed + error?: AxiosError | Record | string; } export const QueryExecutionStatus = ({className, error}: QueryExecutionStatusProps) => { let icon: ReactNode; let label: string; - if (error?.code === 'ECONNABORTED') { + if (typeof error === 'object' && error?.code === 'ECONNABORTED') { icon = ; label = 'Connection aborted'; } else { diff --git a/src/containers/Tenant/Diagnostics/Describe/Describe.tsx b/src/containers/Tenant/Diagnostics/Describe/Describe.tsx index 3e4691797..f62174970 100644 --- a/src/containers/Tenant/Diagnostics/Describe/Describe.tsx +++ b/src/containers/Tenant/Diagnostics/Describe/Describe.tsx @@ -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'; @@ -99,14 +98,14 @@ const Describe = ({tenant, type}: IDescribeProps) => { { + 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; }} /> diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/IssuesViewer/IssueTree.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/IssuesViewer/IssueTree.tsx index b4f7fcbb9..9c6182067 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/IssuesViewer/IssueTree.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/IssuesViewer/IssueTree.tsx @@ -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'; diff --git a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.js b/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx similarity index 66% rename from src/containers/Tenant/Query/ExecuteResult/ExecuteResult.js rename to src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx index c46c6ce16..cfe39c026 100644 --- a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.js +++ b/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx @@ -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'; @@ -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'; @@ -27,31 +29,51 @@ const b = cn('ydb-query-execute-result'); const resultOptionsIds = { result: 'result', stats: 'stats', -}; +} as const; + +type SectionID = ValueOf; 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(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 ( { const content = ( true} className={b('inspector')} searchOptions={{ @@ -86,8 +108,6 @@ export function ExecuteResult(props) { }; const renderResult = () => { - const {result} = props; - return ( {result} @@ -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 ( @@ -120,20 +140,20 @@ export function ExecuteResult(props) { ); } - if (error) { - return
{prepareQueryError(error)}
; - } + const parsedError = typeof error === 'string' ? error : prepareQueryError(error); + + return
{parsedError}
; }; return (
- + - {props.stats && !props.error && ( + {stats && !error && ( - +
- {activeSection === resultOptionsIds.result && !props.error && renderResult()} - {activeSection === resultOptionsIds.stats && !props.error && renderStats()} + {activeSection === resultOptionsIds.result && !error && renderResult()} + {activeSection === resultOptionsIds.stats && !error && renderStats()} {renderIssues()}
diff --git a/src/containers/Tenant/Query/Issues/Issues.tsx b/src/containers/Tenant/Query/Issues/Issues.tsx index 50f92481a..4b7d7426e 100644 --- a/src/containers/Tenant/Query/Issues/Issues.tsx +++ b/src/containers/Tenant/Query/Issues/Issues.tsx @@ -21,10 +21,9 @@ const blockIssue = cn('kv-issue'); interface ResultIssuesProps { data: ErrorResponse | string; - className: string; } -export default function ResultIssues({data, className}: ResultIssuesProps) { +export function ResultIssues({data}: ResultIssuesProps) { const [showIssues, setShowIssues] = React.useState(false); const issues = typeof data === 'string' ? undefined : data?.issues; @@ -59,22 +58,21 @@ export default function ResultIssues({data, className}: ResultIssuesProps) { )} - {hasIssues && showIssues && } + {hasIssues && showIssues && } ); } 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 ( -
+
{issues?.map((issue, index) => ( ))} diff --git a/src/containers/Tenant/Query/QueryDuration/QueryDuration.tsx b/src/containers/Tenant/Query/QueryDuration/QueryDuration.tsx index e514dd4f6..1b067640f 100644 --- a/src/containers/Tenant/Query/QueryDuration/QueryDuration.tsx +++ b/src/containers/Tenant/Query/QueryDuration/QueryDuration.tsx @@ -8,7 +8,7 @@ import i18n from '../i18n'; import './QueryDuration.scss'; interface QueryDurationProps { - duration?: string; + duration?: string | number; } const b = block('ydb-query-duration'); diff --git a/src/containers/Tenant/utils/paneVisibilityToggleHelpers.tsx b/src/containers/Tenant/utils/paneVisibilityToggleHelpers.tsx index e8dfc41cd..433fc0d00 100644 --- a/src/containers/Tenant/utils/paneVisibilityToggleHelpers.tsx +++ b/src/containers/Tenant/utils/paneVisibilityToggleHelpers.tsx @@ -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; } diff --git a/src/types/react-json-inspector.d.ts b/src/types/react-json-inspector.d.ts new file mode 100644 index 000000000..62b678c07 --- /dev/null +++ b/src/types/react-json-inspector.d.ts @@ -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; +}