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 3 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
50 changes: 44 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,56 @@ 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 clusterID={clusterID} keyspace={keyspace} shard={shard}>
{`${keyspace}/${shard} `}
beingnoble03 marked this conversation as resolved.
Show resolved Hide resolved
</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 clusterID={clusterID} keyspace={keyspace} shard={shard}>
{`${keyspace}/${shard} `}
beingnoble03 marked this conversation as resolved.
Show resolved Hide resolved
</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
99 changes: 74 additions & 25 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 @@ -243,18 +288,22 @@ export const WorkflowDetails = ({ clusterID, keyspace, name }: Props) => {
title="Table Copy State"
/>
)}
<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 && (
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like we should have some sort of else condition here where we display something to indicate that we've elided the otherwise expected UI element / display content due to the larger number of shards?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

Screenshot 2024-09-08 at 2 31 43 PM

(PS: Used streams.length <= 1 just for the preview)

<>
rohit-nayak-ps marked this conversation as resolved.
Show resolved Hide resolved
<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>
))}
</>
)}
</div>
);
};
Loading