Skip to content
Open
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: 5 additions & 1 deletion src/containers/Tenant/Diagnostics/Overview/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ function Overview({type, path, database, databaseFullPath}: OverviewProps) {
/>
),
[EPathType.EPathTypeStreamingQuery]: () => (
<StreamingQueryInfo path={path} database={database} />
<StreamingQueryInfo path={path} database={database} data={data} />
),
};

Expand All @@ -129,6 +129,10 @@ function Overview({type, path, database, databaseFullPath}: OverviewProps) {

const content = pathTypeToComponent[type]?.();

if (isStreamingQuery) {
return content;
}

const commonInfo = (
<SchemaObjectInfoContainer data={currentData ?? undefined} type={type} path={path} />
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface SchemaObjectInfoProps {
path: string;
itemsAfterType?: YDBDefinitionListItem[];
additionalItems?: YDBDefinitionListItem[];
createdContent?: string;
}

export function SchemaObjectInfo({
Expand All @@ -26,6 +27,7 @@ export function SchemaObjectInfo({
path,
itemsAfterType,
additionalItems,
createdContent,
}: SchemaObjectInfoProps) {
const showAdministrativeFields = useUserPermissions()?.IsAdministrationAllowed === true;
const items = React.useMemo(
Expand All @@ -37,8 +39,17 @@ export function SchemaObjectInfo({
itemsAfterType,
additionalItems,
showAdministrativeFields,
createdContent,
}),
[additionalItems, data, fallbackType, itemsAfterType, path, showAdministrativeFields],
[
additionalItems,
createdContent,
data,
fallbackType,
itemsAfterType,
path,
showAdministrativeFields,
],
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ interface PrepareSchemaObjectInfoItemsParams {
itemsAfterType?: YDBDefinitionListItem[];
additionalItems?: YDBDefinitionListItem[];
showAdministrativeFields?: boolean;
createdContent?: string;
}

function isPresent(value: string | number | undefined): value is string | number {
Expand Down Expand Up @@ -83,6 +84,7 @@ export function prepareSchemaObjectInfoItems({
itemsAfterType = [],
additionalItems = [],
showAdministrativeFields = false,
createdContent,
}: PrepareSchemaObjectInfoItemsParams): YDBDefinitionListItem[] {
const self = data?.PathDescription?.Self;
let pathId = self?.PathId;
Expand Down Expand Up @@ -116,10 +118,10 @@ export function prepareSchemaObjectInfoItems({
);
}

if (Number(createStep)) {
if (createdContent || Number(createStep)) {
items.push({
name: tenantKeyset('field_created'),
content: formatDateTime(createStep),
content: createdContent || formatDateTime(createStep),
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import React from 'react';

import {dateTimeParse} from '@gravity-ui/date-utils';
import {Label} from '@gravity-ui/uikit';

import {Loader} from '../../../../../components/Loader';
import type {YDBDefinitionListItem} from '../../../../../components/YDBDefinitionList/YDBDefinitionList';
import {YDBDefinitionList} from '../../../../../components/YDBDefinitionList/YDBDefinitionList';
import {YQLCodePreview} from '../../../../../components/YQLCodePreview/YQLCodePreview';
import {streamingQueriesApi} from '../../../../../store/reducers/streamingQuery/streamingQuery';
import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema';
import {EPathType} from '../../../../../types/api/schema';
import type {IQueryResult} from '../../../../../types/store/query';
import {cn} from '../../../../../utils/cn';
import {EMPTY_DATA_PLACEHOLDER} from '../../../../../utils/constants';
import {
getStringifiedData,
stripIndentByFirstLine,
trimOuterEmptyLines,
} from '../../../../../utils/dataFormatters/dataFormatters';
import {parseIssuesData} from '../../../../../utils/query';
import {ResultIssuesModal} from '../../../Query/Issues/Issues';
import {SchemaObjectInfo} from '../SchemaObjectInfo/SchemaObjectInfo';

import i18n from './i18n';

Expand All @@ -24,26 +29,41 @@ import './StreamingQueryInfo.scss';
interface StreamingQueryProps {
database: string;
path: string;
data?: TEvDescribeSchemeResult;
}

const b = cn('ydb-streaming-query-info');

export function StreamingQueryInfo({database, path}: StreamingQueryProps) {
const {data: sysData, isFetching} = streamingQueriesApi.useGetStreamingQueryInfoQuery(
export function StreamingQueryInfo({database, path, data}: StreamingQueryProps) {
const {currentData: sysData, isFetching} = streamingQueriesApi.useGetStreamingQueryInfoQuery(
{database, path},
{skip: !database || !path},
);
const loading = isFetching && sysData === undefined;

if (loading) {
return <Loader size="s" className={b('loader')} />;
}

const {items, queryText} = prepareStreamingQueryItems(sysData);

const row = sysData?.resultSets?.[0]?.result?.[0];
const createdContent =
formatLifecycleValue(
row?.CreatedAt,
row?.CreatedBy,
data?.PathDescription?.Self?.CreateStep,
) ?? (row && 'CreatedAt' in row ? EMPTY_DATA_PLACEHOLDER : undefined);

return (
<React.Fragment>
<YDBDefinitionList items={items} />
<SchemaObjectInfo
data={data}
fallbackType={EPathType.EPathTypeStreamingQuery}
path={path}
createdContent={createdContent}
/>
{loading ? (
<Loader size="s" className={b('loader')} />
) : (
<YDBDefinitionList items={items} />
)}
{queryText ? (
<YQLCodePreview title={i18n('field_query-text')} text={queryText} />
) : null}
Expand Down Expand Up @@ -78,14 +98,25 @@ function prepareStreamingQueryItems(sysData?: IQueryResult) {
return {items: [], queryText: undefined};
}

const info: YDBDefinitionListItem[] = [];
const state = getStringifiedData(sysData.resultSets?.[0]?.result?.[0]?.State);

const queryText = getStringifiedData(sysData.resultSets?.[0]?.result?.[0]?.Text);
const row = sysData.resultSets?.[0]?.result?.[0];
const lifecycleFields = [
{name: i18n('field_started'), timestamp: 'StartedAt', user: 'StartedBy'},
{name: i18n('field_modified'), timestamp: 'ModifiedAt', user: 'ModifiedBy'},
{name: i18n('field_stopped'), timestamp: 'FinishedAt', user: 'StoppedBy'},
];
Comment on lines +102 to +106
const info: YDBDefinitionListItem[] = lifecycleFields
.filter(({timestamp}) => row && timestamp in row)
.map(({name, timestamp, user}) => ({
name,
content: formatLifecycleValue(row?.[timestamp], row?.[user]) ?? EMPTY_DATA_PLACEHOLDER,
}));
const state = getStringifiedData(row?.Status);

const queryText = getStringifiedData(row?.Text);
let normalizedQueryText = trimOuterEmptyLines(queryText);
normalizedQueryText = stripIndentByFirstLine(normalizedQueryText);

const errorRaw = sysData.resultSets?.[0]?.result?.[0]?.Error;
const errorRaw = row?.Issues;

// We use custom error check, because error type can be non-standard
const errorData = parseIssuesData(errorRaw);
Expand All @@ -104,3 +135,25 @@ function prepareStreamingQueryItems(sysData?: IQueryResult) {

return {items: info, queryText: normalizedQueryText};
}

function formatLifecycleValue(
timestamp: string | number | null | undefined,
user: string | number | null | undefined,
fallback?: string | number,
) {
const milliseconds = typeof timestamp === 'string' ? Date.parse(timestamp) : NaN;
const dateMilliseconds =
Number.isFinite(milliseconds) && milliseconds > 0 ? milliseconds : Number(fallback);
Comment on lines +144 to +146

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Numeric dates show as missing

If a query returns a numeric StartedAt, ModifiedAt, or FinishedAt, formatLifecycleValue skips the date and the page shows —. The query parser keeps numbers unchanged. Parse numeric timestamps before using the fallback.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/containers/Tenant/Diagnostics/Overview/StreamingQueryInfo/StreamingQueryInfo.tsx
Line: 144-146

Comment:
**Numeric dates show as missing**

If a query returns a numeric `StartedAt`, `ModifiedAt`, or `FinishedAt`, `formatLifecycleValue` skips the date and the page shows `—`. The query parser keeps numbers unchanged. Parse numeric timestamps before using the fallback.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code

const date =
Number.isFinite(dateMilliseconds) && dateMilliseconds > 0
? dateTimeParse(dateMilliseconds)?.format('YYYY-MM-DD HH:mm:ss')
: undefined;

if (!date && !user) {
return undefined;
}

return user
? i18n('value_date-by-user', {date: date || EMPTY_DATA_PLACEHOLDER, user: String(user)})
: date;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"field_query-state": "State",
"field_query-error": "Error",
"field_query-text": "Text"
"field_query-text": "Text",
"field_started": "Started",
"field_modified": "Modified",
"field_stopped": "Stopped",
"value_date-by-user": "{{date}} by {{user}}"
}
6 changes: 2 additions & 4 deletions src/store/reducers/streamingQuery/streamingQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ import {isQueryErrorResponse, parseQueryAPIResponse} from '../../../utils/query'
import {api} from '../api';

function getStreamingQueryInfoSQL(path: string) {
// Select available columns so older backends without lifecycle fields remain supported.
const safePath = path.replace(/'/g, "''");
return `${QUERY_TECHNICAL_MARK}
SELECT
Status AS State,
Issues AS Error,
Text
SELECT * WITHOUT Plan, Ast
FROM \`.sys/streaming_queries\`
WHERE Path = '${safePath}'
LIMIT 1`;
Expand Down
4 changes: 2 additions & 2 deletions tests/suites/tenant/diagnostics/tabs/info.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -777,8 +777,8 @@ test.describe('Diagnostics Info tab', async () => {
{
rows: [['RUNNING', '{}', 'SELECT 1;']],
columns: [
{name: 'State', type: 'Utf8?'},
{name: 'Error', type: 'Utf8?'},
{name: 'Status', type: 'Utf8?'},
{name: 'Issues', type: 'Utf8?'},
Comment on lines +780 to +781
{name: 'Text', type: 'Utf8?'},
],
Comment on lines 778 to 783

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 New lifecycle fields go untested

The updated test returns only Status, Issues, and Text. It never checks the new lifecycle rows or the created-date fallback, so a broken field name or date format can pass. Add a response and assertions for those fields.

Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/suites/tenant/diagnostics/tabs/info.test.ts
Line: 778-783

Comment:
**New lifecycle fields go untested**

The updated test returns only `Status`, `Issues`, and `Text`. It never checks the new lifecycle rows or the created-date fallback, so a broken field name or date format can pass. Add a response and assertions for those fields.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Claude Code

},
Expand Down
Loading