Skip to content
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
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"url": "^0.11.3",
"use-query-params": "^2.2.1",
"web-vitals": "^1.1.2",
"ydb-ui-components": "^4.1.0",
"ydb-ui-components": "^4.2.0",
"zod": "^3.23.8"
},
"scripts": {
Expand Down
28 changes: 28 additions & 0 deletions src/components/AsyncReplicationState/AsyncReplicationState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {Label} from '@gravity-ui/uikit';

import type {TReplicationState} from '../../types/api/schema/replication';

interface AsyncReplicationStateProps {
state?: TReplicationState;
}

export function AsyncReplicationState({state}: AsyncReplicationStateProps) {
if (!state) {
return null;
}

if ('StandBy' in state) {
return <Label theme="info">Standby</Label>;
}
if ('Paused' in state) {
return <Label theme="info">Paused</Label>;
}
if ('Done' in state) {
return <Label theme="success">Done</Label>;
}
if ('Error' in state) {
return <Label theme="danger">Error</Label>;
}

return <Label size="s">Unknown</Label>;
}
1 change: 1 addition & 0 deletions src/components/AsyncReplicationState/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './AsyncReplicationState';
4 changes: 4 additions & 0 deletions src/containers/Tenant/Diagnostics/DiagnosticsPages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ const partitions = {
title: 'Partitions',
};

export const ASYNC_REPLICATION_PAGES = [overview, describe];

export const DATABASE_PAGES = [
overview,
topQueries,
Expand Down Expand Up @@ -117,6 +119,8 @@ const pathTypeToPages: Record<EPathType, Page[] | undefined> = {
[EPathType.EPathTypeExternalTable]: EXTERNAL_TABLE_PAGES,

[EPathType.EPathTypeView]: VIEW_PAGES,

[EPathType.EPathTypeReplication]: ASYNC_REPLICATION_PAGES,
};

export const getPagesByType = (type?: EPathType) => (type && pathTypeToPages[type]) || DIR_PAGES;
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {Flex, Text} from '@gravity-ui/uikit';

import {AsyncReplicationState} from '../../../../../components/AsyncReplicationState';
import {InfoViewer} from '../../../../../components/InfoViewer';
import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema';
import {useTypedSelector} from '../../../../../utils/hooks';
import {getEntityName} from '../../../utils';
import {AsyncReplicationPaths} from '../AsyncReplicationPaths';

import {Credentials} from './Credentials';
import i18n from './i18n';

interface AsyncReplicationProps {
data?: TEvDescribeSchemeResult;
}

/** Displays overview for Replication EPathType */
export function AsyncReplicationInfo({data}: AsyncReplicationProps) {
const entityName = getEntityName(data?.PathDescription);

const {error: schemaError} = useTypedSelector((state) => state.schema);

if (schemaError) {
return <div className="error">{schemaError.statusText}</div>;
}

if (!data) {
return (
<div className="error">
{i18n('noData')} {entityName}
</div>
);
}

return (
<Flex direction="column" gap="4">
<InfoViewer
title={entityName}
info={[
{
label: i18n('state.label'),
value: (
<AsyncReplicationState
state={data.PathDescription?.ReplicationDescription?.State}
/>
),
},
{
label: i18n('srcConnection.endpoint.label'),
value: (
<Text variant="code-inline-2">
{
data.PathDescription?.ReplicationDescription?.Config
?.SrcConnectionParams?.Endpoint
}
</Text>
),
},
{
label: i18n('srcConnection.database.label'),
value: (
<Text variant="code-inline-2">
{
data.PathDescription?.ReplicationDescription?.Config
?.SrcConnectionParams?.Database
}
</Text>
),
},
{
label: i18n('credentials.label'),
value: (
<Credentials
connection={
data.PathDescription?.ReplicationDescription?.Config
?.SrcConnectionParams
}
/>
),
},
]}
/>
<AsyncReplicationPaths config={data.PathDescription?.ReplicationDescription?.Config} />
</Flex>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {Label} from '@gravity-ui/uikit';

import type {TConnectionParams} from '../../../../../types/api/schema/replication';

interface CredentialsProps {
connection?: TConnectionParams;
}

export function Credentials({connection}: CredentialsProps) {
if (!connection) {
return null;
}

if (connection.StaticCredentials) {
return (
<Label value={connection.StaticCredentials.User} theme="normal">
user
</Label>
);
}

if ('OAuthToken' in connection) {
return 'OAuth';
}

return 'unknown';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"credentials.label": "Credentials",
"noData": "No data for entity:",
"srcConnection.database.label": "Source Database Path",
"srcConnection.endpoint.label": "Source Cluster Endpoint",
"state.label": "State"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {registerKeysets} from '../../../../../../utils/i18n';

import en from './en.json';

const COMPONENT = 'ydb-diagnostics-async-replication-info';

export default registerKeysets(COMPONENT, {en});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './AsyncReplicationInfo';
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@import '../../../../../styles/mixins.scss';

.ydb-async-replication-paths {
&__title {
@include info-viewer-title();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type {Column} from '@gravity-ui/react-data-table';
import {Text} from '@gravity-ui/uikit';

import {ResizeableDataTable} from '../../../../../components/ResizeableDataTable/ResizeableDataTable';
import type {TReplicationConfig, TTarget} from '../../../../../types/api/schema/replication';
import {cn} from '../../../../../utils/cn';
import {TENANT_OVERVIEW_TABLES_SETTINGS} from '../../../../../utils/constants';

import i18n from './i18n';

import './AsyncReplicationPaths.scss';

const b = cn('ydb-async-replication-paths');

interface AsyncReplicationPathsProps {
config?: TReplicationConfig;
}

const columns: Column<TTarget>[] = [
{
name: i18n('column.srcPath.name'),
render: ({row}) => row.SrcPath,
sortAccessor: (row) => row.SrcPath,
},
{
name: i18n('column.dstPath.name'),
render: ({row}) => row.DstPath,
sortAccessor: (row) => row.DstPath,
},
];

export function AsyncReplicationPaths({config}: AsyncReplicationPathsProps) {
if (!config) {
return null;
}

let content: React.ReactNode = i18n('noData');
if (config.Everything) {
content = (
<span>
{i18n('everythingWithPrefix')}{' '}
<Text variant="code-inline-2">{config.Everything?.DstPrefix ?? 'undefined'}</Text>.
</span>
);
}

if (config.Specific) {
content = (
<ResizeableDataTable
data={config.Specific.Targets}
settings={TENANT_OVERVIEW_TABLES_SETTINGS}
columns={columns}
/>
);
}

return (
<div className={b()}>
<div className={b('title')}>{i18n('title')}</div>
{content}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"column.dstPath.name": "Dist",
"column.srcPath.name": "Source",
"everythingWithPrefix": "Everything with prefix:",
"noData": "No data.",
"title": "Replicated Paths"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {registerKeysets} from '../../../../../../utils/i18n';

import en from './en.json';

const COMPONENT = 'ydb-diagnostics-async-replication-paths';

export default registerKeysets(COMPONENT, {en});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './AsyncReplicationPaths';
2 changes: 2 additions & 0 deletions src/containers/Tenant/Diagnostics/Overview/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
isTableType,
} from '../../utils/schema';

import {AsyncReplicationInfo} from './AsyncReplicationInfo';
import {ChangefeedInfo} from './ChangefeedInfo';
import {TableInfo} from './TableInfo';
import {TopicInfo} from './TopicInfo';
Expand Down Expand Up @@ -91,6 +92,7 @@ function Overview({type, tenantName}: OverviewProps) {
[EPathType.EPathTypeExternalTable]: () => <ExternalTableInfo data={data} />,
[EPathType.EPathTypeExternalDataSource]: () => <ExternalDataSourceInfo data={data} />,
[EPathType.EPathTypeView]: undefined,
[EPathType.EPathTypeReplication]: () => <AsyncReplicationInfo data={data} />,
};

return (
Expand Down
36 changes: 29 additions & 7 deletions src/containers/Tenant/ObjectSummary/ObjectSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import qs from 'qs';
import {useLocation} from 'react-router';
import {Link} from 'react-router-dom';

import {AsyncReplicationState} from '../../../components/AsyncReplicationState';
import {ClipboardButton} from '../../../components/ClipboardButton';
import InfoViewer from '../../../components/InfoViewer/InfoViewer';
import {
Expand Down Expand Up @@ -141,6 +142,12 @@ export function ObjectSummary({
};

const renderObjectOverview = () => {
const startTimeInMilliseconds = Number(currentSchemaData?.CreateStep);
const createdAt = startTimeInMilliseconds
? formatDateTime(startTimeInMilliseconds)
: 'unknown';
const createdAtLabel = 'Created At';

// verbose mapping to guarantee a correct render for new path types
// TS will error when a new type is added but not mapped here
const pathTypeToComponent: Record<EPathType, (() => React.ReactNode) | undefined> = {
Expand All @@ -163,19 +170,34 @@ export function ObjectSummary({
<ExternalDataSourceSummary data={currentObjectData} />
),
[EPathType.EPathTypeView]: undefined,
[EPathType.EPathTypeReplication]: () => (
<InfoViewer
info={[
{
label: createdAtLabel,
value: createdAt,
},
{
label: 'State',
value: (
<AsyncReplicationState
state={
currentObjectData?.PathDescription?.ReplicationDescription
?.State
}
/>
),
},
]}
/>
),
};

let component =
currentSchemaData?.PathType && pathTypeToComponent[currentSchemaData.PathType]?.();

if (!component) {
const startTimeInMilliseconds = Number(currentSchemaData?.CreateStep);
let createTime = '';
if (startTimeInMilliseconds) {
createTime = formatDateTime(startTimeInMilliseconds);
}

component = <InfoViewer info={[{label: 'Create time', value: createTime}]} />;
component = <InfoViewer info={[{label: createdAtLabel, value: createdAt}]} />;
}

return component;
Expand Down
Loading