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

VTAdmin(web): Some tweaks in the workflow details #16706

Merged
merged 4 commits into from
Sep 8, 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
60 changes: 54 additions & 6 deletions web/vtadmin/src/components/routes/workflow/Workflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Link, Redirect, Route, Switch, useParams, useRouteMatch } from 'react-router-dom';
import { Link, Redirect, Route, Switch, useLocation, useParams, useRouteMatch } from 'react-router-dom';

import style from './Workflow.module.scss';

Expand All @@ -30,6 +30,7 @@ import { TabContainer } from '../../tabs/TabContainer';
import { Tab } from '../../tabs/Tab';
import { getStreams } from '../../../util/workflows';
import { Code } from '../../Code';
import { ShardLink } from '../../links/ShardLink';

interface RouteParams {
clusterID: string;
Expand All @@ -40,12 +41,21 @@ interface RouteParams {
export const Workflow = () => {
const { clusterID, keyspace, name } = useParams<RouteParams>();
const { path, url } = useRouteMatch();
const location = useLocation();

useDocumentTitle(`${name} (${keyspace})`);

const { data } = useWorkflow({ clusterID, keyspace, name });
const streams = getStreams(data);

const detailsURL = `${url}/details`;
const detailsTab = location.pathname === detailsURL;

let isReshard = false;
if (data && data.workflow) {
isReshard = data.workflow.workflow_type === 'Reshard';
}

return (
<div>
<WorkspaceHeader>
Expand All @@ -54,28 +64,66 @@ export const Workflow = () => {
</NavCrumbs>

<WorkspaceTitle className="font-mono">{name}</WorkspaceTitle>
{isReshard && (
<div className={style.headingMetaContainer}>
<div className={style.headingMeta}>
<span>
{data?.workflow?.source?.shards?.length! > 1 ? 'Source Shards: ' : 'Source Shard: '}
{data?.workflow?.source?.shards?.map((shard) => (
<code key={`${keyspace}/${shard}`}>
<ShardLink
className="mr-1"
clusterID={clusterID}
keyspace={keyspace}
shard={shard}
>
{`${keyspace}/${shard}`}
</ShardLink>
</code>
))}
</span>
<span>
{data?.workflow?.target?.shards?.length! > 1 ? 'Target Shards: ' : 'Target Shard: '}
{data?.workflow?.target?.shards?.map((shard) => (
<code key={`${keyspace}/${shard}`}>
<ShardLink
className="mr-1"
clusterID={clusterID}
keyspace={keyspace}
shard={shard}
>
{`${keyspace}/${shard}`}
</ShardLink>
</code>
))}
</span>
</div>
</div>
)}
<div className={style.headingMetaContainer}>
<div className={style.headingMeta} style={{ float: 'left' }}>
<span>
Cluster: <code>{clusterID}</code>
</span>
<span>
Target keyspace:{' '}
Target Keyspace:{' '}
<KeyspaceLink clusterID={clusterID} name={keyspace}>
<code>{keyspace}</code>
</KeyspaceLink>
</span>
</div>
<div style={{ float: 'right' }}>
<a href={`#workflowStreams`}>Scroll To Streams</a>
</div>
{detailsTab && (
<div style={{ float: 'right' }}>
<a href={`#workflowStreams`}>Scroll To Streams</a>
</div>
)}
</div>
</WorkspaceHeader>

<ContentContainer>
<TabContainer>
<Tab text="Streams" to={`${url}/streams`} count={streams.length} />
<Tab text="Details" to={`${url}/details`} />
<Tab text="Details" to={detailsURL} />
<Tab text="JSON" to={`${url}/json`} />
</TabContainer>

Expand Down
97 changes: 73 additions & 24 deletions web/vtadmin/src/components/routes/workflow/WorkflowDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import React, { useMemo } from 'react';
import { Link } from 'react-router-dom';

import { useWorkflow, useWorkflowStatus, useWorkflows } from '../../../hooks/api';
import { formatDateTime } from '../../../util/time';
import { formatDateTimeShort } from '../../../util/time';
import {
TableCopyState,
formatStreamKey,
getReverseWorkflow,
getStreamSource,
getStreamTarget,
getStreams,
getTableCopyStates,
} from '../../../util/workflows';
Expand All @@ -32,6 +34,10 @@ import { vtctldata } from '../../../proto/vtadmin';
import { DataCell } from '../../dataTable/DataCell';
import { StreamStatePip } from '../../pips/StreamStatePip';
import { ThrottleThresholdSeconds } from '../Workflows';
import { ShardLink } from '../../links/ShardLink';
import { Tooltip } from '../../tooltip/Tooltip';
import { TabletLink } from '../../links/TabletLink';
import { formatAlias } from '../../../util/tablets';

interface Props {
clusterID: string;
Expand All @@ -45,7 +51,7 @@ const LOG_COLUMNS = ['Type', 'State', 'Updated At', 'Message', 'Count'];

const TABLE_COPY_STATE_COLUMNS = ['Table Name', 'Total Bytes', 'Bytes Copied', 'Total Rows', 'Rows Copied'];

const STREAM_COLUMNS = ['Stream', 'State', 'Message', 'Transaction Timestamp', 'Database Name'];
const STREAM_COLUMNS = ['Stream', 'Source Shard', 'Target Shard', 'Message', 'Transaction Timestamp', 'Database Name'];

export const WorkflowDetails = ({ clusterID, keyspace, name }: Props) => {
const { data: workflowData } = useWorkflow({ clusterID, keyspace, name });
Expand Down Expand Up @@ -112,15 +118,20 @@ export const WorkflowDetails = ({ clusterID, keyspace, name }: Props) => {
const renderSummaryRows = (rows: (typeof workflowSummary)[]) => {
return rows.map((row) => {
const reverseWorkflow = row.reverseWorkflow;
let maxVReplicationLag = '-';
if (row.workflowData && row.workflowData.workflow?.max_v_replication_lag) {
maxVReplicationLag = `${row.workflowData.workflow?.max_v_replication_lag}`;
if (maxVReplicationLag === '1') {
maxVReplicationLag += ' second';
} else {
maxVReplicationLag += ' seconds';
}
}
return (
<tr key={reverseWorkflow?.workflow?.name}>
<DataCell>{row.streamSummary ? row.streamSummary : '-'}</DataCell>
<DataCell>{row.workflowStatus ? row.workflowStatus.traffic_state : '-'}</DataCell>
<DataCell>
{row.workflowData && row.workflowData.workflow?.max_v_replication_lag
? `${row.workflowData.workflow?.max_v_replication_lag}`
: '-'}
</DataCell>
<DataCell>{maxVReplicationLag}</DataCell>
<DataCell>
{reverseWorkflow ? (
<Link
Expand All @@ -140,6 +151,8 @@ export const WorkflowDetails = ({ clusterID, keyspace, name }: Props) => {

const renderStreamRows = (rows: typeof streams) => {
return rows.map((row) => {
const source = getStreamSource(row);
const target = getStreamTarget(row, keyspace);
const href =
row.tablet && row.id
? `/workflow/${clusterID}/${keyspace}/${name}/stream/${row.tablet.cell}/${row.tablet.uid}/${row.id}`
Expand All @@ -151,12 +164,22 @@ export const WorkflowDetails = ({ clusterID, keyspace, name }: Props) => {
return (
<tr key={row.key}>
<DataCell>
<StreamStatePip state={rowState} />{' '}
<Tooltip text={rowState!}>
<span>
<StreamStatePip state={rowState} />{' '}
</span>
</Tooltip>
<Link className="font-bold" to={href}>
{row.key}
</Link>
<div className="text-sm text-secondary">
Updated {formatDateTime(row.time_updated?.seconds)}
Tablet{' '}
<TabletLink alias={formatAlias(row.tablet)} clusterID={clusterID}>
{formatAlias(row.tablet)}
</TabletLink>
</div>
<div className="text-sm text-secondary">
Updated {formatDateTimeShort(row.time_updated?.seconds)}
</div>
{isThrottled ? (
<div className="text-sm text-secondary">
Expand All @@ -165,11 +188,32 @@ export const WorkflowDetails = ({ clusterID, keyspace, name }: Props) => {
</div>
) : null}
</DataCell>
<DataCell>{rowState}</DataCell>
<DataCell>
{source ? (
<ShardLink
clusterID={clusterID}
keyspace={row.binlog_source?.keyspace}
shard={row.binlog_source?.shard}
>
{source}
</ShardLink>
) : (
'-'
)}
</DataCell>
<DataCell>
{target ? (
<ShardLink clusterID={clusterID} keyspace={keyspace} shard={row.shard}>
{target}
</ShardLink>
) : (
'-'
)}
</DataCell>
<DataCell>{row.message ? row.message : '-'}</DataCell>
<DataCell>
{row.transaction_timestamp && row.transaction_timestamp.seconds
? formatDateTime(row.transaction_timestamp.seconds)
? `${formatDateTimeShort(row.transaction_timestamp.seconds)}`
: '-'}
</DataCell>
<DataCell>{row.db_name}</DataCell>
Expand All @@ -189,7 +233,7 @@ export const WorkflowDetails = ({ clusterID, keyspace, name }: Props) => {
<tr key={`${row.id}`}>
<DataCell>{`${row.type}`}</DataCell>
<DataCell>{`${row.state}`}</DataCell>
<DataCell>{`${formatDateTime(parseInt(`${row.updated_at?.seconds}`, 10))}`}</DataCell>
<DataCell>{`${formatDateTimeShort(parseInt(`${row.updated_at?.seconds}`, 10))}`}</DataCell>
<DataCell>{message}</DataCell>
<DataCell>{`${row.count}`}</DataCell>
</tr>
Expand Down Expand Up @@ -219,14 +263,15 @@ export const WorkflowDetails = ({ clusterID, keyspace, name }: Props) => {
};

return (
<div className="mt-12 mb-16">
<div className="mt-8 mb-16">
<DataTable
columns={SUMMARY_COLUMNS}
data={[workflowSummary]}
renderRows={renderSummaryRows}
pageSize={1}
title="Summary"
/>
<span id="workflowStreams"></span>
<DataTable
columns={STREAM_COLUMNS}
data={streams}
Expand All @@ -244,17 +289,21 @@ export const WorkflowDetails = ({ clusterID, keyspace, name }: Props) => {
/>
)}
<h3 className="mt-8 mb-4">Recent Logs</h3>
{streams.map((stream) => (
<div className="mt-2" key={stream.key}>
<DataTable
columns={LOG_COLUMNS}
data={orderBy(stream.logs, 'id', 'desc')}
renderRows={renderLogRows}
pageSize={10}
title={stream.key!}
/>
</div>
))}
{streams.length <= 8 ? (
streams.map((stream) => (
<div className="mt-2" key={stream.key}>
<DataTable
columns={LOG_COLUMNS}
data={orderBy(stream.logs, 'id', 'desc')}
renderRows={renderLogRows}
pageSize={10}
title={stream.key!}
/>
</div>
))
) : (
<span>Recent logs from streams are not displayed due to the large number of shards.</span>
)}
</div>
);
};
Loading
Loading