From 9c2dbd54909dd64b02f42feae53786a648bec0fc Mon Sep 17 00:00:00 2001 From: Rohit Nayak Date: Mon, 1 Jul 2024 23:28:04 +0200 Subject: [PATCH 01/15] Add throttled status to workflow/streams Signed-off-by: Rohit Nayak --- .../src/components/pips/Pip.module.scss | 4 + web/vtadmin/src/components/pips/Pip.tsx | 2 +- .../src/components/pips/StreamStatePip.tsx | 1 + .../src/components/routes/Workflows.tsx | 80 +++++++++++++------ .../routes/workflow/Workflow.module.scss | 8 ++ .../components/routes/workflow/Workflow.tsx | 67 +++++++++------- .../routes/workflow/WorkflowStreams.tsx | 49 +++++++----- 7 files changed, 135 insertions(+), 76 deletions(-) diff --git a/web/vtadmin/src/components/pips/Pip.module.scss b/web/vtadmin/src/components/pips/Pip.module.scss index ece8042179f..341ed6ad314 100644 --- a/web/vtadmin/src/components/pips/Pip.module.scss +++ b/web/vtadmin/src/components/pips/Pip.module.scss @@ -35,4 +35,8 @@ &.danger { background: var(--colorError50); } + + &.throttled { + background: var(--grey900); + } } diff --git a/web/vtadmin/src/components/pips/Pip.tsx b/web/vtadmin/src/components/pips/Pip.tsx index 197249967aa..13d0e71fb3f 100644 --- a/web/vtadmin/src/components/pips/Pip.tsx +++ b/web/vtadmin/src/components/pips/Pip.tsx @@ -22,7 +22,7 @@ interface Props { state?: PipState; } -export type PipState = 'primary' | 'success' | 'warning' | 'danger' | null | undefined; +export type PipState = 'primary' | 'success' | 'warning' | 'danger' | 'throttled' | null | undefined; export const Pip = ({ className, state }: Props) => { return
; diff --git a/web/vtadmin/src/components/pips/StreamStatePip.tsx b/web/vtadmin/src/components/pips/StreamStatePip.tsx index e68d358b654..be0a7f9157b 100644 --- a/web/vtadmin/src/components/pips/StreamStatePip.tsx +++ b/web/vtadmin/src/components/pips/StreamStatePip.tsx @@ -25,6 +25,7 @@ const STREAM_STATES: { [key: string]: PipState } = { Error: 'danger', Running: 'success', Stopped: null, + Throttled: 'throttled', }; export const StreamStatePip = ({ state }: Props) => { diff --git a/web/vtadmin/src/components/routes/Workflows.tsx b/web/vtadmin/src/components/routes/Workflows.tsx index b9a85a8e3e4..9d1ac64ae98 100644 --- a/web/vtadmin/src/components/routes/Workflows.tsx +++ b/web/vtadmin/src/components/routes/Workflows.tsx @@ -13,34 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { groupBy, orderBy } from 'lodash-es'; +import {groupBy, orderBy} from 'lodash-es'; import * as React from 'react'; -import { Link } from 'react-router-dom'; +import {Link} from 'react-router-dom'; import style from './Workflows.module.scss'; -import { useWorkflows } from '../../hooks/api'; -import { useDocumentTitle } from '../../hooks/useDocumentTitle'; -import { DataCell } from '../dataTable/DataCell'; -import { DataTable } from '../dataTable/DataTable'; -import { useSyncedURLParam } from '../../hooks/useSyncedURLParam'; -import { filterNouns } from '../../util/filterNouns'; -import { getStreams, getTimeUpdated } from '../../util/workflows'; -import { formatDateTime, formatRelativeTime } from '../../util/time'; -import { StreamStatePip } from '../pips/StreamStatePip'; -import { ContentContainer } from '../layout/ContentContainer'; -import { WorkspaceHeader } from '../layout/WorkspaceHeader'; -import { WorkspaceTitle } from '../layout/WorkspaceTitle'; -import { DataFilter } from '../dataTable/DataFilter'; -import { Tooltip } from '../tooltip/Tooltip'; -import { KeyspaceLink } from '../links/KeyspaceLink'; -import { QueryLoadingPlaceholder } from '../placeholders/QueryLoadingPlaceholder'; -import { UseQueryResult } from 'react-query'; +import {useWorkflows} from '../../hooks/api'; +import {useDocumentTitle} from '../../hooks/useDocumentTitle'; +import {DataCell} from '../dataTable/DataCell'; +import {DataTable} from '../dataTable/DataTable'; +import {useSyncedURLParam} from '../../hooks/useSyncedURLParam'; +import {filterNouns} from '../../util/filterNouns'; +import {getStreams, getTimeUpdated} from '../../util/workflows'; +import {formatDateTime, formatRelativeTime} from '../../util/time'; +import {StreamStatePip} from '../pips/StreamStatePip'; +import {ContentContainer} from '../layout/ContentContainer'; +import {WorkspaceHeader} from '../layout/WorkspaceHeader'; +import {WorkspaceTitle} from '../layout/WorkspaceTitle'; +import {DataFilter} from '../dataTable/DataFilter'; +import {Tooltip} from '../tooltip/Tooltip'; +import {KeyspaceLink} from '../links/KeyspaceLink'; +import {QueryLoadingPlaceholder} from '../placeholders/QueryLoadingPlaceholder'; +import {UseQueryResult} from 'react-query'; +import {vttime} from "../../proto/vtadmin"; export const Workflows = () => { useDocumentTitle('Workflows'); const workflowsQuery = useWorkflows(); - const { value: filter, updateValue: updateFilter } = useSyncedURLParam('filter'); + const {value: filter, updateValue: updateFilter} = useSyncedURLParam('filter'); const sortedData = React.useMemo(() => { const mapped = (workflowsQuery.data || []).map((workflow) => ({ @@ -67,7 +68,6 @@ export const Workflows = () => { row.clusterID && row.keyspace && row.name ? `/workflow/${row.clusterID}/${row.keyspace}/${row.name}` : null; - return ( @@ -112,17 +112,46 @@ export const Workflows = () => { {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} {['Error', 'Copying', 'Running', 'Stopped'].map((streamState) => { if (streamState in row.streams) { + var numThrottled = 0 + var throttledApp = "" + var throttledFrom: vttime.ITime | null | undefined const streamCount = row.streams[streamState].length; + var streamDescription: string + switch (streamState) { + case 'Error': + streamDescription = 'failed' + break + case 'Running': + const running = row.streams['Running'] + if (running !== undefined && running !== null) { + for (const stream of running) { + if (stream?.throttler_status?.component_throttled !== null && stream?.throttler_status?.component_throttled !== undefined) { + numThrottled++ + throttledApp = stream?.throttler_status?.component_throttled + throttledFrom = stream?.throttler_status?.time_throttled + } + } + } + if (numThrottled > 0) { + streamDescription = '' + streamState = 'Throttled' + } else { + streamDescription = streamState + } + break; + default: + streamDescription = streamState.toLocaleLowerCase(); + } const tooltip = [ streamCount, - streamState === 'Error' ? 'failed' : streamState.toLocaleLowerCase(), + streamDescription, streamCount === 1 ? 'stream' : 'streams', + numThrottled > 0 ? '(' + numThrottled + ' throttled by ' + throttledApp + ' ' + formatRelativeTime(throttledFrom?.seconds) + ')' : '' ].join(' '); - return ( - {streamCount} + {streamCount} ); @@ -131,6 +160,7 @@ export const Workflows = () => { - + ); })}
@@ -164,7 +194,7 @@ export const Workflows = () => { renderRows={renderRows} /> - + ); diff --git a/web/vtadmin/src/components/routes/workflow/Workflow.module.scss b/web/vtadmin/src/components/routes/workflow/Workflow.module.scss index 65691d00e4c..c8f5e86853c 100644 --- a/web/vtadmin/src/components/routes/workflow/Workflow.module.scss +++ b/web/vtadmin/src/components/routes/workflow/Workflow.module.scss @@ -32,3 +32,11 @@ content: none; } } + +.headingMetaContainer div { + width: '100%'; + display: 'flex'; + justifyContent: 'space-between'; + paddingTop: '0px'; + paddingBottom: '0px'; + } diff --git a/web/vtadmin/src/components/routes/workflow/Workflow.tsx b/web/vtadmin/src/components/routes/workflow/Workflow.tsx index 5abbcb25e83..ade15e279a0 100644 --- a/web/vtadmin/src/components/routes/workflow/Workflow.tsx +++ b/web/vtadmin/src/components/routes/workflow/Workflow.tsx @@ -13,22 +13,22 @@ * 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, useParams, useRouteMatch} from 'react-router-dom'; import style from './Workflow.module.scss'; -import { useWorkflow } from '../../../hooks/api'; -import { NavCrumbs } from '../../layout/NavCrumbs'; -import { WorkspaceHeader } from '../../layout/WorkspaceHeader'; -import { WorkspaceTitle } from '../../layout/WorkspaceTitle'; -import { useDocumentTitle } from '../../../hooks/useDocumentTitle'; -import { KeyspaceLink } from '../../links/KeyspaceLink'; -import { WorkflowStreams } from './WorkflowStreams'; -import { ContentContainer } from '../../layout/ContentContainer'; -import { TabContainer } from '../../tabs/TabContainer'; -import { Tab } from '../../tabs/Tab'; -import { getStreams } from '../../../util/workflows'; -import { Code } from '../../Code'; +import {useWorkflow} from '../../../hooks/api'; +import {NavCrumbs} from '../../layout/NavCrumbs'; +import {WorkspaceHeader} from '../../layout/WorkspaceHeader'; +import {WorkspaceTitle} from '../../layout/WorkspaceTitle'; +import {useDocumentTitle} from '../../../hooks/useDocumentTitle'; +import {KeyspaceLink} from '../../links/KeyspaceLink'; +import {WorkflowStreams} from './WorkflowStreams'; +import {ContentContainer} from '../../layout/ContentContainer'; +import {TabContainer} from '../../tabs/TabContainer'; +import {Tab} from '../../tabs/Tab'; +import {getStreams} from '../../../util/workflows'; +import {Code} from '../../Code'; interface RouteParams { clusterID: string; @@ -37,12 +37,12 @@ interface RouteParams { } export const Workflow = () => { - const { clusterID, keyspace, name } = useParams(); - const { path, url } = useRouteMatch(); + const {clusterID, keyspace, name} = useParams(); + const {path, url} = useRouteMatch(); useDocumentTitle(`${name} (${keyspace})`); - const { data } = useWorkflow({ clusterID, keyspace, name }); + const {data} = useWorkflow({clusterID, keyspace, name}); const streams = getStreams(data); return ( @@ -53,35 +53,40 @@ export const Workflow = () => { {name} -
- - Cluster: {clusterID} - - - Target keyspace:{' '} - - {keyspace} - - +
+
+ + Cluster: {clusterID} + + + Target keyspace:{' '} + + {keyspace} + + +
+
+ Streams +
- - + + - + - + - +
diff --git a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx index 6c9bcae2dfa..f3fb70173dd 100644 --- a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx +++ b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx @@ -14,21 +14,21 @@ * limitations under the License. */ -import { orderBy, groupBy } from 'lodash-es'; -import React, { useMemo } from 'react'; -import { Link } from 'react-router-dom'; +import {groupBy, orderBy} from 'lodash-es'; +import React, {useMemo} from 'react'; +import {Link} from 'react-router-dom'; -import { useWorkflow } from '../../../hooks/api'; -import { formatAlias } from '../../../util/tablets'; -import { formatDateTime } from '../../../util/time'; -import { getStreams, formatStreamKey, getStreamSource, getStreamTarget } from '../../../util/workflows'; -import { DataCell } from '../../dataTable/DataCell'; -import { DataTable } from '../../dataTable/DataTable'; -import { TabletLink } from '../../links/TabletLink'; -import { StreamStatePip } from '../../pips/StreamStatePip'; -import { WorkflowStreamsLagChart } from '../../charts/WorkflowStreamsLagChart'; -import { ShardLink } from '../../links/ShardLink'; -import { env } from '../../../util/env'; +import {useWorkflow} from '../../../hooks/api'; +import {formatAlias} from '../../../util/tablets'; +import {formatDateTime, formatRelativeTime} from '../../../util/time'; +import {formatStreamKey, getStreams, getStreamSource, getStreamTarget} from '../../../util/workflows'; +import {DataCell} from '../../dataTable/DataCell'; +import {DataTable} from '../../dataTable/DataTable'; +import {TabletLink} from '../../links/TabletLink'; +import {StreamStatePip} from '../../pips/StreamStatePip'; +import {WorkflowStreamsLagChart} from '../../charts/WorkflowStreamsLagChart'; +import {ShardLink} from '../../links/ShardLink'; +import {env} from '../../../util/env'; interface Props { clusterID: string; @@ -38,8 +38,8 @@ interface Props { const COLUMNS = ['Stream', 'Source', 'Target', 'Tablet']; -export const WorkflowStreams = ({ clusterID, keyspace, name }: Props) => { - const { data } = useWorkflow({ clusterID, keyspace, name }); +export const WorkflowStreams = ({clusterID, keyspace, name}: Props) => { + const {data} = useWorkflow({clusterID, keyspace, name}); const streams = useMemo(() => { const rows = getStreams(data).map((stream) => ({ @@ -65,13 +65,24 @@ export const WorkflowStreams = ({ clusterID, keyspace, name }: Props) => { return ( - {' '} + {' '} {row.key}
Updated {formatDateTime(row.time_updated?.seconds)}
+ {row?.throttler_status?.component_throttled ? ( +
+ + Throttled: + + {row.throttler_status?.component_throttled} + ({formatRelativeTime(row.throttler_status?.time_throttled?.seconds)}) +
+ ) : null + }
{source ? ( @@ -110,11 +121,11 @@ export const WorkflowStreams = ({ clusterID, keyspace, name }: Props) => { {env().VITE_ENABLE_EXPERIMENTAL_TABLET_DEBUG_VARS && ( <>

Stream VReplication Lag

- + )} -

Streams

+

Streams

{/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} {['Error', 'Copying', 'Running', 'Stopped'].map((streamState) => { if (!Array.isArray(streamsByState[streamState])) { From 578c39ff215abd265edad65798f29f0f77494ff8 Mon Sep 17 00:00:00 2001 From: Rohit Nayak Date: Mon, 1 Jul 2024 23:33:25 +0200 Subject: [PATCH 02/15] Minor refactor Signed-off-by: Rohit Nayak --- .../src/components/routes/workflow/WorkflowStreams.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx index f3fb70173dd..f31663ca0a8 100644 --- a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx +++ b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx @@ -61,11 +61,11 @@ export const WorkflowStreams = ({clusterID, keyspace, name}: Props) => { const source = getStreamSource(row); const target = getStreamTarget(row, keyspace); - + const rowState = row?.throttler_status?.component_throttled ? 'Throttled' : row.state; return ( - {' '} + {' '} {row.key} From d75ec73793c5787f22a6e46bac42477c1b500ac9 Mon Sep 17 00:00:00 2001 From: Rohit Nayak Date: Tue, 2 Jul 2024 08:44:24 +0200 Subject: [PATCH 03/15] Prettify changed files Signed-off-by: Rohit Nayak --- .../src/components/routes/Workflows.tsx | 375 ++++++++++-------- .../routes/workflow/Workflow.module.scss | 36 +- .../components/routes/workflow/Workflow.tsx | 137 ++++--- .../routes/workflow/WorkflowStreams.tsx | 254 ++++++------ 4 files changed, 428 insertions(+), 374 deletions(-) diff --git a/web/vtadmin/src/components/routes/Workflows.tsx b/web/vtadmin/src/components/routes/Workflows.tsx index 9d1ac64ae98..817751bbf55 100644 --- a/web/vtadmin/src/components/routes/Workflows.tsx +++ b/web/vtadmin/src/components/routes/Workflows.tsx @@ -13,189 +13,216 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {groupBy, orderBy} from 'lodash-es'; -import * as React from 'react'; -import {Link} from 'react-router-dom'; +import { groupBy, orderBy } from "lodash-es"; +import * as React from "react"; +import { Link } from "react-router-dom"; -import style from './Workflows.module.scss'; -import {useWorkflows} from '../../hooks/api'; -import {useDocumentTitle} from '../../hooks/useDocumentTitle'; -import {DataCell} from '../dataTable/DataCell'; -import {DataTable} from '../dataTable/DataTable'; -import {useSyncedURLParam} from '../../hooks/useSyncedURLParam'; -import {filterNouns} from '../../util/filterNouns'; -import {getStreams, getTimeUpdated} from '../../util/workflows'; -import {formatDateTime, formatRelativeTime} from '../../util/time'; -import {StreamStatePip} from '../pips/StreamStatePip'; -import {ContentContainer} from '../layout/ContentContainer'; -import {WorkspaceHeader} from '../layout/WorkspaceHeader'; -import {WorkspaceTitle} from '../layout/WorkspaceTitle'; -import {DataFilter} from '../dataTable/DataFilter'; -import {Tooltip} from '../tooltip/Tooltip'; -import {KeyspaceLink} from '../links/KeyspaceLink'; -import {QueryLoadingPlaceholder} from '../placeholders/QueryLoadingPlaceholder'; -import {UseQueryResult} from 'react-query'; -import {vttime} from "../../proto/vtadmin"; +import style from "./Workflows.module.scss"; +import { useWorkflows } from "../../hooks/api"; +import { useDocumentTitle } from "../../hooks/useDocumentTitle"; +import { DataCell } from "../dataTable/DataCell"; +import { DataTable } from "../dataTable/DataTable"; +import { useSyncedURLParam } from "../../hooks/useSyncedURLParam"; +import { filterNouns } from "../../util/filterNouns"; +import { getStreams, getTimeUpdated } from "../../util/workflows"; +import { formatDateTime, formatRelativeTime } from "../../util/time"; +import { StreamStatePip } from "../pips/StreamStatePip"; +import { ContentContainer } from "../layout/ContentContainer"; +import { WorkspaceHeader } from "../layout/WorkspaceHeader"; +import { WorkspaceTitle } from "../layout/WorkspaceTitle"; +import { DataFilter } from "../dataTable/DataFilter"; +import { Tooltip } from "../tooltip/Tooltip"; +import { KeyspaceLink } from "../links/KeyspaceLink"; +import { QueryLoadingPlaceholder } from "../placeholders/QueryLoadingPlaceholder"; +import { UseQueryResult } from "react-query"; +import { vttime } from "../../proto/vtadmin"; export const Workflows = () => { - useDocumentTitle('Workflows'); - const workflowsQuery = useWorkflows(); + useDocumentTitle("Workflows"); + const workflowsQuery = useWorkflows(); - const {value: filter, updateValue: updateFilter} = useSyncedURLParam('filter'); + const { value: filter, updateValue: updateFilter } = + useSyncedURLParam("filter"); - const sortedData = React.useMemo(() => { - const mapped = (workflowsQuery.data || []).map((workflow) => ({ - clusterID: workflow.cluster?.id, - clusterName: workflow.cluster?.name, - keyspace: workflow.keyspace, - name: workflow.workflow?.name, - source: workflow.workflow?.source?.keyspace, - sourceShards: workflow.workflow?.source?.shards, - streams: groupBy(getStreams(workflow), 'state'), - target: workflow.workflow?.target?.keyspace, - targetShards: workflow.workflow?.target?.shards, - timeUpdated: getTimeUpdated(workflow), - workflowType: workflow.workflow?.workflow_type, - workflowSubType: workflow.workflow?.workflow_sub_type, - })); - const filtered = filterNouns(filter, mapped); - return orderBy(filtered, ['name', 'clusterName', 'source', 'target']); - }, [workflowsQuery.data, filter]); + const sortedData = React.useMemo(() => { + const mapped = (workflowsQuery.data || []).map((workflow) => ({ + clusterID: workflow.cluster?.id, + clusterName: workflow.cluster?.name, + keyspace: workflow.keyspace, + name: workflow.workflow?.name, + source: workflow.workflow?.source?.keyspace, + sourceShards: workflow.workflow?.source?.shards, + streams: groupBy(getStreams(workflow), "state"), + target: workflow.workflow?.target?.keyspace, + targetShards: workflow.workflow?.target?.shards, + timeUpdated: getTimeUpdated(workflow), + workflowType: workflow.workflow?.workflow_type, + workflowSubType: workflow.workflow?.workflow_sub_type, + })); + const filtered = filterNouns(filter, mapped); + return orderBy(filtered, ["name", "clusterName", "source", "target"]); + }, [workflowsQuery.data, filter]); - const renderRows = (rows: typeof sortedData) => - rows.map((row, idx) => { - const href = - row.clusterID && row.keyspace && row.name - ? `/workflow/${row.clusterID}/${row.keyspace}/${row.name}` - : null; - return ( - - -
{href ? {row.name} : row.name}
- {row.workflowType && ( -
- {row.workflowType} - {row.workflowSubType && row.workflowSubType !== 'None' && ( - {' (' + row.workflowSubType + ')'} - )} -
- )} -
{row.clusterName}
-
- - {row.source ? ( - <> - - {row.source} - -
{(row.sourceShards || []).join(', ')}
- - ) : ( - N/A - )} -
- - {row.target ? ( - <> - - {row.target} - -
{(row.targetShards || []).join(', ')}
- - ) : ( - N/A - )} -
+ const renderRows = (rows: typeof sortedData) => + rows.map((row, idx) => { + const href = + row.clusterID && row.keyspace && row.name + ? `/workflow/${row.clusterID}/${row.keyspace}/${row.name}` + : null; + return ( + + +
+ {href ? {row.name} : row.name} +
+ {row.workflowType && ( +
+ {row.workflowType} + {row.workflowSubType && row.workflowSubType !== "None" && ( + + {" (" + row.workflowSubType + ")"} + + )} +
+ )} +
{row.clusterName}
+
+ + {row.source ? ( + <> + + {row.source} + +
+ {(row.sourceShards || []).join(", ")} +
+ + ) : ( + N/A + )} +
+ + {row.target ? ( + <> + + {row.target} + +
+ {(row.targetShards || []).join(", ")} +
+ + ) : ( + N/A + )} +
- -
- {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} - {['Error', 'Copying', 'Running', 'Stopped'].map((streamState) => { - if (streamState in row.streams) { - var numThrottled = 0 - var throttledApp = "" - var throttledFrom: vttime.ITime | null | undefined - const streamCount = row.streams[streamState].length; - var streamDescription: string - switch (streamState) { - case 'Error': - streamDescription = 'failed' - break - case 'Running': - const running = row.streams['Running'] - if (running !== undefined && running !== null) { - for (const stream of running) { - if (stream?.throttler_status?.component_throttled !== null && stream?.throttler_status?.component_throttled !== undefined) { - numThrottled++ - throttledApp = stream?.throttler_status?.component_throttled - throttledFrom = stream?.throttler_status?.time_throttled - } - } - } - if (numThrottled > 0) { - streamDescription = '' - streamState = 'Throttled' - } else { - streamDescription = streamState - } - break; - default: - streamDescription = streamState.toLocaleLowerCase(); - } - const tooltip = [ - streamCount, - streamDescription, - streamCount === 1 ? 'stream' : 'streams', - numThrottled > 0 ? '(' + numThrottled + ' throttled by ' + throttledApp + ' ' + formatRelativeTime(throttledFrom?.seconds) + ')' : '' - ].join(' '); - return ( - - - {streamCount} - - - ); - } - return ( - - - - + +
+ {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} + {["Error", "Copying", "Running", "Stopped"].map((streamState) => { + if (streamState in row.streams) { + var numThrottled = 0; + var throttledApp = ""; + var throttledFrom: vttime.ITime | null | undefined; + const streamCount = row.streams[streamState].length; + var streamDescription: string; + switch (streamState) { + case "Error": + streamDescription = "failed"; + break; + case "Running": + const running = row.streams["Running"]; + if (running !== undefined && running !== null) { + for (const stream of running) { + if ( + stream?.throttler_status?.component_throttled !== + null && + stream?.throttler_status?.component_throttled !== + undefined + ) { + numThrottled++; + throttledApp = + stream?.throttler_status?.component_throttled; + throttledFrom = + stream?.throttler_status?.time_throttled; + } + } + } + if (numThrottled > 0) { + streamDescription = ""; + streamState = "Throttled"; + } else { + streamDescription = streamState; + } + break; + default: + streamDescription = streamState.toLocaleLowerCase(); + } + const tooltip = [ + streamCount, + streamDescription, + streamCount === 1 ? "stream" : "streams", + numThrottled > 0 + ? "(" + + numThrottled + + " throttled by " + + throttledApp + + " " + + formatRelativeTime(throttledFrom?.seconds) + + ")" + : "", + ].join(" "); + return ( + + + {streamCount} + + + ); + } + return ( + + - + + ); + })} +
+
- ); - })} -
-
+ +
+ {formatDateTime(row.timeUpdated)} +
+
+ {formatRelativeTime(row.timeUpdated)} +
+
+ + ); + }); - -
{formatDateTime(row.timeUpdated)}
-
{formatRelativeTime(row.timeUpdated)}
-
- - ); - }); + return ( +
+ + Workflows + + + updateFilter(e.target.value)} + onClear={() => updateFilter("")} + placeholder="Filter workflows" + value={filter || ""} + /> - return ( -
- - Workflows - - - updateFilter(e.target.value)} - onClear={() => updateFilter('')} - placeholder="Filter workflows" - value={filter || ''} - /> + - - - - -
- ); + +
+
+ ); }; diff --git a/web/vtadmin/src/components/routes/workflow/Workflow.module.scss b/web/vtadmin/src/components/routes/workflow/Workflow.module.scss index c8f5e86853c..b17616baef1 100644 --- a/web/vtadmin/src/components/routes/workflow/Workflow.module.scss +++ b/web/vtadmin/src/components/routes/workflow/Workflow.module.scss @@ -14,29 +14,29 @@ * limitations under the License. */ .headingMeta { - display: flex; + display: flex; } .headingMeta span { - display: inline-block; - line-height: 2; + display: inline-block; + line-height: 2; - &::after { - color: var(--colorScaffoldingHighlight); - content: '/'; - display: inline-block; - margin: 0 1.2rem; - } + &::after { + color: var(--colorScaffoldingHighlight); + content: "/"; + display: inline-block; + margin: 0 1.2rem; + } - &:last-child::after { - content: none; - } + &:last-child::after { + content: none; + } } .headingMetaContainer div { - width: '100%'; - display: 'flex'; - justifyContent: 'space-between'; - paddingTop: '0px'; - paddingBottom: '0px'; - } + width: "100%"; + display: "flex"; + justifycontent: "space-between"; + paddingtop: "0px"; + paddingbottom: "0px"; +} diff --git a/web/vtadmin/src/components/routes/workflow/Workflow.tsx b/web/vtadmin/src/components/routes/workflow/Workflow.tsx index ade15e279a0..33b5cec3912 100644 --- a/web/vtadmin/src/components/routes/workflow/Workflow.tsx +++ b/web/vtadmin/src/components/routes/workflow/Workflow.tsx @@ -13,82 +13,93 @@ * 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, + useParams, + useRouteMatch, +} from "react-router-dom"; -import style from './Workflow.module.scss'; +import style from "./Workflow.module.scss"; -import {useWorkflow} from '../../../hooks/api'; -import {NavCrumbs} from '../../layout/NavCrumbs'; -import {WorkspaceHeader} from '../../layout/WorkspaceHeader'; -import {WorkspaceTitle} from '../../layout/WorkspaceTitle'; -import {useDocumentTitle} from '../../../hooks/useDocumentTitle'; -import {KeyspaceLink} from '../../links/KeyspaceLink'; -import {WorkflowStreams} from './WorkflowStreams'; -import {ContentContainer} from '../../layout/ContentContainer'; -import {TabContainer} from '../../tabs/TabContainer'; -import {Tab} from '../../tabs/Tab'; -import {getStreams} from '../../../util/workflows'; -import {Code} from '../../Code'; +import { useWorkflow } from "../../../hooks/api"; +import { NavCrumbs } from "../../layout/NavCrumbs"; +import { WorkspaceHeader } from "../../layout/WorkspaceHeader"; +import { WorkspaceTitle } from "../../layout/WorkspaceTitle"; +import { useDocumentTitle } from "../../../hooks/useDocumentTitle"; +import { KeyspaceLink } from "../../links/KeyspaceLink"; +import { WorkflowStreams } from "./WorkflowStreams"; +import { ContentContainer } from "../../layout/ContentContainer"; +import { TabContainer } from "../../tabs/TabContainer"; +import { Tab } from "../../tabs/Tab"; +import { getStreams } from "../../../util/workflows"; +import { Code } from "../../Code"; interface RouteParams { - clusterID: string; - keyspace: string; - name: string; + clusterID: string; + keyspace: string; + name: string; } export const Workflow = () => { - const {clusterID, keyspace, name} = useParams(); - const {path, url} = useRouteMatch(); + const { clusterID, keyspace, name } = useParams(); + const { path, url } = useRouteMatch(); - useDocumentTitle(`${name} (${keyspace})`); + useDocumentTitle(`${name} (${keyspace})`); - const {data} = useWorkflow({clusterID, keyspace, name}); - const streams = getStreams(data); + const { data } = useWorkflow({ clusterID, keyspace, name }); + const streams = getStreams(data); - return ( -
- - - Workflows - + return ( +
+ + + Workflows + - {name} -
-
- - Cluster: {clusterID} - - - Target keyspace:{' '} - - {keyspace} - - -
-
- Streams -
-
-
+ {name} +
+
+ + Cluster: {clusterID} + + + Target keyspace:{" "} + + {keyspace} + + +
+
+ Streams +
+
+ - - - - - + + + + + - - - - + + + + - - - + + + - - - -
- ); + + + +
+ ); }; diff --git a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx index f31663ca0a8..7982c7108a4 100644 --- a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx +++ b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx @@ -14,137 +14,153 @@ * limitations under the License. */ -import {groupBy, orderBy} from 'lodash-es'; -import React, {useMemo} from 'react'; -import {Link} from 'react-router-dom'; +import { groupBy, orderBy } from "lodash-es"; +import React, { useMemo } from "react"; +import { Link } from "react-router-dom"; -import {useWorkflow} from '../../../hooks/api'; -import {formatAlias} from '../../../util/tablets'; -import {formatDateTime, formatRelativeTime} from '../../../util/time'; -import {formatStreamKey, getStreams, getStreamSource, getStreamTarget} from '../../../util/workflows'; -import {DataCell} from '../../dataTable/DataCell'; -import {DataTable} from '../../dataTable/DataTable'; -import {TabletLink} from '../../links/TabletLink'; -import {StreamStatePip} from '../../pips/StreamStatePip'; -import {WorkflowStreamsLagChart} from '../../charts/WorkflowStreamsLagChart'; -import {ShardLink} from '../../links/ShardLink'; -import {env} from '../../../util/env'; +import { useWorkflow } from "../../../hooks/api"; +import { formatAlias } from "../../../util/tablets"; +import { formatDateTime, formatRelativeTime } from "../../../util/time"; +import { + formatStreamKey, + getStreams, + getStreamSource, + getStreamTarget, +} from "../../../util/workflows"; +import { DataCell } from "../../dataTable/DataCell"; +import { DataTable } from "../../dataTable/DataTable"; +import { TabletLink } from "../../links/TabletLink"; +import { StreamStatePip } from "../../pips/StreamStatePip"; +import { WorkflowStreamsLagChart } from "../../charts/WorkflowStreamsLagChart"; +import { ShardLink } from "../../links/ShardLink"; +import { env } from "../../../util/env"; interface Props { - clusterID: string; - keyspace: string; - name: string; + clusterID: string; + keyspace: string; + name: string; } -const COLUMNS = ['Stream', 'Source', 'Target', 'Tablet']; +const COLUMNS = ["Stream", "Source", "Target", "Tablet"]; -export const WorkflowStreams = ({clusterID, keyspace, name}: Props) => { - const {data} = useWorkflow({clusterID, keyspace, name}); +export const WorkflowStreams = ({ clusterID, keyspace, name }: Props) => { + const { data } = useWorkflow({ clusterID, keyspace, name }); - const streams = useMemo(() => { - const rows = getStreams(data).map((stream) => ({ - key: formatStreamKey(stream), - ...stream, - })); + const streams = useMemo(() => { + const rows = getStreams(data).map((stream) => ({ + key: formatStreamKey(stream), + ...stream, + })); - return orderBy(rows, 'streamKey'); - }, [data]); + return orderBy(rows, "streamKey"); + }, [data]); - const streamsByState = groupBy(streams, 'state'); + const streamsByState = groupBy(streams, "state"); - const renderRows = (rows: typeof streams) => { - return rows.map((row) => { - const href = - row.tablet && row.id - ? `/workflow/${clusterID}/${keyspace}/${name}/stream/${row.tablet.cell}/${row.tablet.uid}/${row.id}` - : null; + const renderRows = (rows: typeof streams) => { + return rows.map((row) => { + const href = + row.tablet && row.id + ? `/workflow/${clusterID}/${keyspace}/${name}/stream/${row.tablet.cell}/${row.tablet.uid}/${row.id}` + : null; - const source = getStreamSource(row); - const target = getStreamTarget(row, keyspace); - const rowState = row?.throttler_status?.component_throttled ? 'Throttled' : row.state; - return ( - - - {' '} - - {row.key} - -
- Updated {formatDateTime(row.time_updated?.seconds)} -
- {row?.throttler_status?.component_throttled ? ( -
- - Throttled: - - {row.throttler_status?.component_throttled} - ({formatRelativeTime(row.throttler_status?.time_throttled?.seconds)}) -
- ) : null - } -
- - {source ? ( - - {source} - - ) : ( - N/A - )} - - - {target ? ( - - {target} - - ) : ( - N/A - )} - - - - {formatAlias(row.tablet)} - - - - ); - }); - }; - - return ( -
- {env().VITE_ENABLE_EXPERIMENTAL_TABLET_DEBUG_VARS && ( - <> -

Stream VReplication Lag

- - + const source = getStreamSource(row); + const target = getStreamTarget(row, keyspace); + const rowState = row?.throttler_status?.component_throttled + ? "Throttled" + : row.state; + return ( + + + {" "} + + {row.key} + +
+ Updated {formatDateTime(row.time_updated?.seconds)} +
+ {row?.throttler_status?.component_throttled ? ( +
+ Throttled: + {row.throttler_status?.component_throttled}( + {formatRelativeTime( + row.throttler_status?.time_throttled?.seconds + )} + ) +
+ ) : null} +
+ + {source ? ( + + {source} + + ) : ( + N/A + )} + + + {target ? ( + + {target} + + ) : ( + N/A )} + + + + {formatAlias(row.tablet)} + + + + ); + }); + }; + + return ( +
+ {env().VITE_ENABLE_EXPERIMENTAL_TABLET_DEBUG_VARS && ( + <> +

Stream VReplication Lag

+ + + )} -

Streams

- {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} - {['Error', 'Copying', 'Running', 'Stopped'].map((streamState) => { - if (!Array.isArray(streamsByState[streamState])) { - return null; - } +

+ Streams +

+ {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} + {["Error", "Copying", "Running", "Stopped"].map((streamState) => { + if (!Array.isArray(streamsByState[streamState])) { + return null; + } - return ( -
- -
- ); - })} -
- ); + return ( +
+ +
+ ); + })} +
+ ); }; From e4c63be1990fd5422881bb8c18075a94b2060cb3 Mon Sep 17 00:00:00 2001 From: Rohit Nayak Date: Tue, 2 Jul 2024 08:51:07 +0200 Subject: [PATCH 04/15] Try prettify again: command is npm run lint:prettier -- --write src/components/routes/workflow/Workflow.module.scss src/components/routes/workflow/Workflow.tsx src/components/routes/workflow/WorkflowStreams.tsx src/components/routes/Workflows.tsx Signed-off-by: Rohit Nayak --- .../src/components/routes/Workflows.tsx | 385 +++++++++--------- .../routes/workflow/Workflow.module.scss | 34 +- .../components/routes/workflow/Workflow.tsx | 137 +++---- .../routes/workflow/WorkflowStreams.tsx | 250 ++++++------ 4 files changed, 380 insertions(+), 426 deletions(-) diff --git a/web/vtadmin/src/components/routes/Workflows.tsx b/web/vtadmin/src/components/routes/Workflows.tsx index 817751bbf55..cda2fd7bc6e 100644 --- a/web/vtadmin/src/components/routes/Workflows.tsx +++ b/web/vtadmin/src/components/routes/Workflows.tsx @@ -13,216 +13,199 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { groupBy, orderBy } from "lodash-es"; -import * as React from "react"; -import { Link } from "react-router-dom"; +import { groupBy, orderBy } from 'lodash-es'; +import * as React from 'react'; +import { Link } from 'react-router-dom'; -import style from "./Workflows.module.scss"; -import { useWorkflows } from "../../hooks/api"; -import { useDocumentTitle } from "../../hooks/useDocumentTitle"; -import { DataCell } from "../dataTable/DataCell"; -import { DataTable } from "../dataTable/DataTable"; -import { useSyncedURLParam } from "../../hooks/useSyncedURLParam"; -import { filterNouns } from "../../util/filterNouns"; -import { getStreams, getTimeUpdated } from "../../util/workflows"; -import { formatDateTime, formatRelativeTime } from "../../util/time"; -import { StreamStatePip } from "../pips/StreamStatePip"; -import { ContentContainer } from "../layout/ContentContainer"; -import { WorkspaceHeader } from "../layout/WorkspaceHeader"; -import { WorkspaceTitle } from "../layout/WorkspaceTitle"; -import { DataFilter } from "../dataTable/DataFilter"; -import { Tooltip } from "../tooltip/Tooltip"; -import { KeyspaceLink } from "../links/KeyspaceLink"; -import { QueryLoadingPlaceholder } from "../placeholders/QueryLoadingPlaceholder"; -import { UseQueryResult } from "react-query"; -import { vttime } from "../../proto/vtadmin"; +import style from './Workflows.module.scss'; +import { useWorkflows } from '../../hooks/api'; +import { useDocumentTitle } from '../../hooks/useDocumentTitle'; +import { DataCell } from '../dataTable/DataCell'; +import { DataTable } from '../dataTable/DataTable'; +import { useSyncedURLParam } from '../../hooks/useSyncedURLParam'; +import { filterNouns } from '../../util/filterNouns'; +import { getStreams, getTimeUpdated } from '../../util/workflows'; +import { formatDateTime, formatRelativeTime } from '../../util/time'; +import { StreamStatePip } from '../pips/StreamStatePip'; +import { ContentContainer } from '../layout/ContentContainer'; +import { WorkspaceHeader } from '../layout/WorkspaceHeader'; +import { WorkspaceTitle } from '../layout/WorkspaceTitle'; +import { DataFilter } from '../dataTable/DataFilter'; +import { Tooltip } from '../tooltip/Tooltip'; +import { KeyspaceLink } from '../links/KeyspaceLink'; +import { QueryLoadingPlaceholder } from '../placeholders/QueryLoadingPlaceholder'; +import { UseQueryResult } from 'react-query'; +import { vttime } from '../../proto/vtadmin'; export const Workflows = () => { - useDocumentTitle("Workflows"); - const workflowsQuery = useWorkflows(); + useDocumentTitle('Workflows'); + const workflowsQuery = useWorkflows(); - const { value: filter, updateValue: updateFilter } = - useSyncedURLParam("filter"); + const { value: filter, updateValue: updateFilter } = useSyncedURLParam('filter'); - const sortedData = React.useMemo(() => { - const mapped = (workflowsQuery.data || []).map((workflow) => ({ - clusterID: workflow.cluster?.id, - clusterName: workflow.cluster?.name, - keyspace: workflow.keyspace, - name: workflow.workflow?.name, - source: workflow.workflow?.source?.keyspace, - sourceShards: workflow.workflow?.source?.shards, - streams: groupBy(getStreams(workflow), "state"), - target: workflow.workflow?.target?.keyspace, - targetShards: workflow.workflow?.target?.shards, - timeUpdated: getTimeUpdated(workflow), - workflowType: workflow.workflow?.workflow_type, - workflowSubType: workflow.workflow?.workflow_sub_type, - })); - const filtered = filterNouns(filter, mapped); - return orderBy(filtered, ["name", "clusterName", "source", "target"]); - }, [workflowsQuery.data, filter]); + const sortedData = React.useMemo(() => { + const mapped = (workflowsQuery.data || []).map((workflow) => ({ + clusterID: workflow.cluster?.id, + clusterName: workflow.cluster?.name, + keyspace: workflow.keyspace, + name: workflow.workflow?.name, + source: workflow.workflow?.source?.keyspace, + sourceShards: workflow.workflow?.source?.shards, + streams: groupBy(getStreams(workflow), 'state'), + target: workflow.workflow?.target?.keyspace, + targetShards: workflow.workflow?.target?.shards, + timeUpdated: getTimeUpdated(workflow), + workflowType: workflow.workflow?.workflow_type, + workflowSubType: workflow.workflow?.workflow_sub_type, + })); + const filtered = filterNouns(filter, mapped); + return orderBy(filtered, ['name', 'clusterName', 'source', 'target']); + }, [workflowsQuery.data, filter]); - const renderRows = (rows: typeof sortedData) => - rows.map((row, idx) => { - const href = - row.clusterID && row.keyspace && row.name - ? `/workflow/${row.clusterID}/${row.keyspace}/${row.name}` - : null; - return ( - - -
- {href ? {row.name} : row.name} -
- {row.workflowType && ( -
- {row.workflowType} - {row.workflowSubType && row.workflowSubType !== "None" && ( - - {" (" + row.workflowSubType + ")"} - - )} -
- )} -
{row.clusterName}
-
- - {row.source ? ( - <> - - {row.source} - -
- {(row.sourceShards || []).join(", ")} -
- - ) : ( - N/A - )} -
- - {row.target ? ( - <> - - {row.target} - -
- {(row.targetShards || []).join(", ")} -
- - ) : ( - N/A - )} -
+ const renderRows = (rows: typeof sortedData) => + rows.map((row, idx) => { + const href = + row.clusterID && row.keyspace && row.name + ? `/workflow/${row.clusterID}/${row.keyspace}/${row.name}` + : null; + return ( + + +
{href ? {row.name} : row.name}
+ {row.workflowType && ( +
+ {row.workflowType} + {row.workflowSubType && row.workflowSubType !== 'None' && ( + {' (' + row.workflowSubType + ')'} + )} +
+ )} +
{row.clusterName}
+
+ + {row.source ? ( + <> + + {row.source} + +
{(row.sourceShards || []).join(', ')}
+ + ) : ( + N/A + )} +
+ + {row.target ? ( + <> + + {row.target} + +
{(row.targetShards || []).join(', ')}
+ + ) : ( + N/A + )} +
- -
- {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} - {["Error", "Copying", "Running", "Stopped"].map((streamState) => { - if (streamState in row.streams) { - var numThrottled = 0; - var throttledApp = ""; - var throttledFrom: vttime.ITime | null | undefined; - const streamCount = row.streams[streamState].length; - var streamDescription: string; - switch (streamState) { - case "Error": - streamDescription = "failed"; - break; - case "Running": - const running = row.streams["Running"]; - if (running !== undefined && running !== null) { - for (const stream of running) { - if ( - stream?.throttler_status?.component_throttled !== - null && - stream?.throttler_status?.component_throttled !== - undefined - ) { - numThrottled++; - throttledApp = - stream?.throttler_status?.component_throttled; - throttledFrom = - stream?.throttler_status?.time_throttled; - } - } - } - if (numThrottled > 0) { - streamDescription = ""; - streamState = "Throttled"; - } else { - streamDescription = streamState; - } - break; - default: - streamDescription = streamState.toLocaleLowerCase(); - } - const tooltip = [ - streamCount, - streamDescription, - streamCount === 1 ? "stream" : "streams", - numThrottled > 0 - ? "(" + - numThrottled + - " throttled by " + - throttledApp + - " " + - formatRelativeTime(throttledFrom?.seconds) + - ")" - : "", - ].join(" "); - return ( - - - {streamCount} - - - ); - } - return ( - - - - - ); - })} -
-
+ +
+ {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} + {['Error', 'Copying', 'Running', 'Stopped'].map((streamState) => { + if (streamState in row.streams) { + var numThrottled = 0; + var throttledApp = ''; + var throttledFrom: vttime.ITime | null | undefined; + const streamCount = row.streams[streamState].length; + var streamDescription: string; + switch (streamState) { + case 'Error': + streamDescription = 'failed'; + break; + case 'Running': + const running = row.streams['Running']; + if (running !== undefined && running !== null) { + for (const stream of running) { + if ( + stream?.throttler_status?.component_throttled !== null && + stream?.throttler_status?.component_throttled !== undefined + ) { + numThrottled++; + throttledApp = stream?.throttler_status?.component_throttled; + throttledFrom = stream?.throttler_status?.time_throttled; + } + } + } + if (numThrottled > 0) { + streamDescription = ''; + streamState = 'Throttled'; + } else { + streamDescription = streamState; + } + break; + default: + streamDescription = streamState.toLocaleLowerCase(); + } + const tooltip = [ + streamCount, + streamDescription, + streamCount === 1 ? 'stream' : 'streams', + numThrottled > 0 + ? '(' + + numThrottled + + ' throttled by ' + + throttledApp + + ' ' + + formatRelativeTime(throttledFrom?.seconds) + + ')' + : '', + ].join(' '); + return ( + + + {streamCount} + + + ); + } + return ( + + - + + ); + })} +
+
- -
- {formatDateTime(row.timeUpdated)} -
-
- {formatRelativeTime(row.timeUpdated)} -
-
- - ); - }); + +
{formatDateTime(row.timeUpdated)}
+
{formatRelativeTime(row.timeUpdated)}
+
+ + ); + }); - return ( -
- - Workflows - - - updateFilter(e.target.value)} - onClear={() => updateFilter("")} - placeholder="Filter workflows" - value={filter || ""} - /> + return ( +
+ + Workflows + + + updateFilter(e.target.value)} + onClear={() => updateFilter('')} + placeholder="Filter workflows" + value={filter || ''} + /> - + - - -
- ); + +
+
+ ); }; diff --git a/web/vtadmin/src/components/routes/workflow/Workflow.module.scss b/web/vtadmin/src/components/routes/workflow/Workflow.module.scss index b17616baef1..ad589714ee0 100644 --- a/web/vtadmin/src/components/routes/workflow/Workflow.module.scss +++ b/web/vtadmin/src/components/routes/workflow/Workflow.module.scss @@ -14,29 +14,29 @@ * limitations under the License. */ .headingMeta { - display: flex; + display: flex; } .headingMeta span { - display: inline-block; - line-height: 2; - - &::after { - color: var(--colorScaffoldingHighlight); - content: "/"; display: inline-block; - margin: 0 1.2rem; - } + line-height: 2; + + &::after { + color: var(--colorScaffoldingHighlight); + content: '/'; + display: inline-block; + margin: 0 1.2rem; + } - &:last-child::after { - content: none; - } + &:last-child::after { + content: none; + } } .headingMetaContainer div { - width: "100%"; - display: "flex"; - justifycontent: "space-between"; - paddingtop: "0px"; - paddingbottom: "0px"; + width: '100%'; + display: 'flex'; + justifycontent: 'space-between'; + paddingtop: '0px'; + paddingbottom: '0px'; } diff --git a/web/vtadmin/src/components/routes/workflow/Workflow.tsx b/web/vtadmin/src/components/routes/workflow/Workflow.tsx index 33b5cec3912..32f624dd3f3 100644 --- a/web/vtadmin/src/components/routes/workflow/Workflow.tsx +++ b/web/vtadmin/src/components/routes/workflow/Workflow.tsx @@ -13,93 +13,82 @@ * 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, useParams, useRouteMatch } from 'react-router-dom'; -import style from "./Workflow.module.scss"; +import style from './Workflow.module.scss'; -import { useWorkflow } from "../../../hooks/api"; -import { NavCrumbs } from "../../layout/NavCrumbs"; -import { WorkspaceHeader } from "../../layout/WorkspaceHeader"; -import { WorkspaceTitle } from "../../layout/WorkspaceTitle"; -import { useDocumentTitle } from "../../../hooks/useDocumentTitle"; -import { KeyspaceLink } from "../../links/KeyspaceLink"; -import { WorkflowStreams } from "./WorkflowStreams"; -import { ContentContainer } from "../../layout/ContentContainer"; -import { TabContainer } from "../../tabs/TabContainer"; -import { Tab } from "../../tabs/Tab"; -import { getStreams } from "../../../util/workflows"; -import { Code } from "../../Code"; +import { useWorkflow } from '../../../hooks/api'; +import { NavCrumbs } from '../../layout/NavCrumbs'; +import { WorkspaceHeader } from '../../layout/WorkspaceHeader'; +import { WorkspaceTitle } from '../../layout/WorkspaceTitle'; +import { useDocumentTitle } from '../../../hooks/useDocumentTitle'; +import { KeyspaceLink } from '../../links/KeyspaceLink'; +import { WorkflowStreams } from './WorkflowStreams'; +import { ContentContainer } from '../../layout/ContentContainer'; +import { TabContainer } from '../../tabs/TabContainer'; +import { Tab } from '../../tabs/Tab'; +import { getStreams } from '../../../util/workflows'; +import { Code } from '../../Code'; interface RouteParams { - clusterID: string; - keyspace: string; - name: string; + clusterID: string; + keyspace: string; + name: string; } export const Workflow = () => { - const { clusterID, keyspace, name } = useParams(); - const { path, url } = useRouteMatch(); + const { clusterID, keyspace, name } = useParams(); + const { path, url } = useRouteMatch(); - useDocumentTitle(`${name} (${keyspace})`); + useDocumentTitle(`${name} (${keyspace})`); - const { data } = useWorkflow({ clusterID, keyspace, name }); - const streams = getStreams(data); + const { data } = useWorkflow({ clusterID, keyspace, name }); + const streams = getStreams(data); - return ( -
- - - Workflows - + return ( +
+ + + Workflows + - {name} -
-
- - Cluster: {clusterID} - - - Target keyspace:{" "} - - {keyspace} - - -
-
- Streams -
-
-
+ {name} +
+
+ + Cluster: {clusterID} + + + Target keyspace:{' '} + + {keyspace} + + +
+
+ Streams +
+
+ - - - - - + + + + + - - - - + + + + - - - + + + - - - -
- ); + + + +
+ ); }; diff --git a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx index 7982c7108a4..63246462dfd 100644 --- a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx +++ b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx @@ -14,153 +14,135 @@ * limitations under the License. */ -import { groupBy, orderBy } from "lodash-es"; -import React, { useMemo } from "react"; -import { Link } from "react-router-dom"; +import { groupBy, orderBy } from 'lodash-es'; +import React, { useMemo } from 'react'; +import { Link } from 'react-router-dom'; -import { useWorkflow } from "../../../hooks/api"; -import { formatAlias } from "../../../util/tablets"; -import { formatDateTime, formatRelativeTime } from "../../../util/time"; -import { - formatStreamKey, - getStreams, - getStreamSource, - getStreamTarget, -} from "../../../util/workflows"; -import { DataCell } from "../../dataTable/DataCell"; -import { DataTable } from "../../dataTable/DataTable"; -import { TabletLink } from "../../links/TabletLink"; -import { StreamStatePip } from "../../pips/StreamStatePip"; -import { WorkflowStreamsLagChart } from "../../charts/WorkflowStreamsLagChart"; -import { ShardLink } from "../../links/ShardLink"; -import { env } from "../../../util/env"; +import { useWorkflow } from '../../../hooks/api'; +import { formatAlias } from '../../../util/tablets'; +import { formatDateTime, formatRelativeTime } from '../../../util/time'; +import { formatStreamKey, getStreams, getStreamSource, getStreamTarget } from '../../../util/workflows'; +import { DataCell } from '../../dataTable/DataCell'; +import { DataTable } from '../../dataTable/DataTable'; +import { TabletLink } from '../../links/TabletLink'; +import { StreamStatePip } from '../../pips/StreamStatePip'; +import { WorkflowStreamsLagChart } from '../../charts/WorkflowStreamsLagChart'; +import { ShardLink } from '../../links/ShardLink'; +import { env } from '../../../util/env'; interface Props { - clusterID: string; - keyspace: string; - name: string; + clusterID: string; + keyspace: string; + name: string; } -const COLUMNS = ["Stream", "Source", "Target", "Tablet"]; +const COLUMNS = ['Stream', 'Source', 'Target', 'Tablet']; export const WorkflowStreams = ({ clusterID, keyspace, name }: Props) => { - const { data } = useWorkflow({ clusterID, keyspace, name }); + const { data } = useWorkflow({ clusterID, keyspace, name }); - const streams = useMemo(() => { - const rows = getStreams(data).map((stream) => ({ - key: formatStreamKey(stream), - ...stream, - })); + const streams = useMemo(() => { + const rows = getStreams(data).map((stream) => ({ + key: formatStreamKey(stream), + ...stream, + })); - return orderBy(rows, "streamKey"); - }, [data]); + return orderBy(rows, 'streamKey'); + }, [data]); - const streamsByState = groupBy(streams, "state"); + const streamsByState = groupBy(streams, 'state'); - const renderRows = (rows: typeof streams) => { - return rows.map((row) => { - const href = - row.tablet && row.id - ? `/workflow/${clusterID}/${keyspace}/${name}/stream/${row.tablet.cell}/${row.tablet.uid}/${row.id}` - : null; + const renderRows = (rows: typeof streams) => { + return rows.map((row) => { + const href = + row.tablet && row.id + ? `/workflow/${clusterID}/${keyspace}/${name}/stream/${row.tablet.cell}/${row.tablet.uid}/${row.id}` + : null; - const source = getStreamSource(row); - const target = getStreamTarget(row, keyspace); - const rowState = row?.throttler_status?.component_throttled - ? "Throttled" - : row.state; - return ( - - - {" "} - - {row.key} - -
- Updated {formatDateTime(row.time_updated?.seconds)} -
- {row?.throttler_status?.component_throttled ? ( -
- Throttled: - {row.throttler_status?.component_throttled}( - {formatRelativeTime( - row.throttler_status?.time_throttled?.seconds - )} - ) -
- ) : null} -
- - {source ? ( - - {source} - - ) : ( - N/A - )} - - - {target ? ( - - {target} - - ) : ( - N/A - )} - - - - {formatAlias(row.tablet)} - - - - ); - }); - }; + const source = getStreamSource(row); + const target = getStreamTarget(row, keyspace); + const rowState = row?.throttler_status?.component_throttled ? 'Throttled' : row.state; + return ( + + + {' '} + + {row.key} + +
+ Updated {formatDateTime(row.time_updated?.seconds)} +
+ {row?.throttler_status?.component_throttled ? ( +
+ Throttled: + {row.throttler_status?.component_throttled}( + {formatRelativeTime(row.throttler_status?.time_throttled?.seconds)}) +
+ ) : null} +
+ + {source ? ( + + {source} + + ) : ( + N/A + )} + + + {target ? ( + + {target} + + ) : ( + N/A + )} + + + + {formatAlias(row.tablet)} + + + + ); + }); + }; - return ( -
- {env().VITE_ENABLE_EXPERIMENTAL_TABLET_DEBUG_VARS && ( - <> -

Stream VReplication Lag

- - - )} + return ( +
+ {env().VITE_ENABLE_EXPERIMENTAL_TABLET_DEBUG_VARS && ( + <> +

Stream VReplication Lag

+ + + )} -

- Streams -

- {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} - {["Error", "Copying", "Running", "Stopped"].map((streamState) => { - if (!Array.isArray(streamsByState[streamState])) { - return null; - } +

+ Streams +

+ {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} + {['Error', 'Copying', 'Running', 'Stopped'].map((streamState) => { + if (!Array.isArray(streamsByState[streamState])) { + return null; + } - return ( -
- -
- ); - })} -
- ); + return ( +
+ +
+ ); + })} +
+ ); }; From 765c9b5b2c4bfa2d17d387dacaddb3125e0dc752 Mon Sep 17 00:00:00 2001 From: Rohit Nayak Date: Tue, 2 Jul 2024 08:57:39 +0200 Subject: [PATCH 05/15] Update styles to use correct attributes Signed-off-by: Rohit Nayak --- .../src/components/routes/workflow/Workflow.module.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/vtadmin/src/components/routes/workflow/Workflow.module.scss b/web/vtadmin/src/components/routes/workflow/Workflow.module.scss index ad589714ee0..0aa70d66828 100644 --- a/web/vtadmin/src/components/routes/workflow/Workflow.module.scss +++ b/web/vtadmin/src/components/routes/workflow/Workflow.module.scss @@ -36,7 +36,7 @@ .headingMetaContainer div { width: '100%'; display: 'flex'; - justifycontent: 'space-between'; - paddingtop: '0px'; - paddingbottom: '0px'; + justify-content: 'space-between'; + padding-top: '0px'; + padding-bottom: '0px'; } From 28ade16eb3d27d95aee621d39d31714e8a187d2e Mon Sep 17 00:00:00 2001 From: Rohit Nayak Date: Wed, 3 Jul 2024 11:29:02 +0200 Subject: [PATCH 06/15] Only show recently throttled streams, since the throttled component/time columns don't get cleared once it is no longer throttled Signed-off-by: Rohit Nayak --- .../src/components/routes/Workflows.tsx | 76 ++++++++++--------- .../routes/workflow/WorkflowStreams.tsx | 42 +++++----- 2 files changed, 64 insertions(+), 54 deletions(-) diff --git a/web/vtadmin/src/components/routes/Workflows.tsx b/web/vtadmin/src/components/routes/Workflows.tsx index cda2fd7bc6e..7a76c78ad89 100644 --- a/web/vtadmin/src/components/routes/Workflows.tsx +++ b/web/vtadmin/src/components/routes/Workflows.tsx @@ -13,35 +13,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { groupBy, orderBy } from 'lodash-es'; +import {groupBy, orderBy} from 'lodash-es'; import * as React from 'react'; -import { Link } from 'react-router-dom'; +import {Link} from 'react-router-dom'; import style from './Workflows.module.scss'; -import { useWorkflows } from '../../hooks/api'; -import { useDocumentTitle } from '../../hooks/useDocumentTitle'; -import { DataCell } from '../dataTable/DataCell'; -import { DataTable } from '../dataTable/DataTable'; -import { useSyncedURLParam } from '../../hooks/useSyncedURLParam'; -import { filterNouns } from '../../util/filterNouns'; -import { getStreams, getTimeUpdated } from '../../util/workflows'; -import { formatDateTime, formatRelativeTime } from '../../util/time'; -import { StreamStatePip } from '../pips/StreamStatePip'; -import { ContentContainer } from '../layout/ContentContainer'; -import { WorkspaceHeader } from '../layout/WorkspaceHeader'; -import { WorkspaceTitle } from '../layout/WorkspaceTitle'; -import { DataFilter } from '../dataTable/DataFilter'; -import { Tooltip } from '../tooltip/Tooltip'; -import { KeyspaceLink } from '../links/KeyspaceLink'; -import { QueryLoadingPlaceholder } from '../placeholders/QueryLoadingPlaceholder'; -import { UseQueryResult } from 'react-query'; -import { vttime } from '../../proto/vtadmin'; +import {useWorkflows} from '../../hooks/api'; +import {useDocumentTitle} from '../../hooks/useDocumentTitle'; +import {DataCell} from '../dataTable/DataCell'; +import {DataTable} from '../dataTable/DataTable'; +import {useSyncedURLParam} from '../../hooks/useSyncedURLParam'; +import {filterNouns} from '../../util/filterNouns'; +import {getStreams, getTimeUpdated} from '../../util/workflows'; +import {formatDateTime, formatRelativeTime} from '../../util/time'; +import {StreamStatePip} from '../pips/StreamStatePip'; +import {ContentContainer} from '../layout/ContentContainer'; +import {WorkspaceHeader} from '../layout/WorkspaceHeader'; +import {WorkspaceTitle} from '../layout/WorkspaceTitle'; +import {DataFilter} from '../dataTable/DataFilter'; +import {Tooltip} from '../tooltip/Tooltip'; +import {KeyspaceLink} from '../links/KeyspaceLink'; +import {QueryLoadingPlaceholder} from '../placeholders/QueryLoadingPlaceholder'; +import {UseQueryResult} from 'react-query'; +import {vttime} from '../../proto/vtadmin'; + +export const ThrottleThresholdSeconds = 60; export const Workflows = () => { useDocumentTitle('Workflows'); const workflowsQuery = useWorkflows(); - const { value: filter, updateValue: updateFilter } = useSyncedURLParam('filter'); + const {value: filter, updateValue: updateFilter} = useSyncedURLParam('filter'); const sortedData = React.useMemo(() => { const mapped = (workflowsQuery.data || []).map((workflow) => ({ @@ -113,7 +115,7 @@ export const Workflows = () => { {['Error', 'Copying', 'Running', 'Stopped'].map((streamState) => { if (streamState in row.streams) { var numThrottled = 0; - var throttledApp = ''; + var throttledApp: string | undefined = ''; var throttledFrom: vttime.ITime | null | undefined; const streamCount = row.streams[streamState].length; var streamDescription: string; @@ -126,12 +128,18 @@ export const Workflows = () => { if (running !== undefined && running !== null) { for (const stream of running) { if ( - stream?.throttler_status?.component_throttled !== null && - stream?.throttler_status?.component_throttled !== undefined + stream?.throttler_status?.time_throttled !== null && + stream?.throttler_status?.time_throttled !== undefined && + // If the stream has been throttled for more than 5 seconds, show it as throttled. + Number(stream?.throttler_status?.time_throttled?.seconds) > (Date.now() / 1000 - ThrottleThresholdSeconds) ) { numThrottled++; - throttledApp = stream?.throttler_status?.component_throttled; - throttledFrom = stream?.throttler_status?.time_throttled; + // In case of multiple streams, show the first throttled app and time. + // The detail page will show each stream separately. + if (numThrottled === 1) { + throttledApp = stream?.throttler_status?.component_throttled?.toString(); + throttledFrom = stream?.throttler_status?.time_throttled; + } } } } @@ -151,18 +159,18 @@ export const Workflows = () => { streamCount === 1 ? 'stream' : 'streams', numThrottled > 0 ? '(' + - numThrottled + - ' throttled by ' + - throttledApp + - ' ' + - formatRelativeTime(throttledFrom?.seconds) + - ')' + numThrottled + + ' throttled in ' + + throttledApp + + ' ' + + formatRelativeTime(throttledFrom?.seconds) + + ')' : '', ].join(' '); return ( - {streamCount} + {streamCount} ); @@ -204,7 +212,7 @@ export const Workflows = () => { renderRows={renderRows} /> - + ); diff --git a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx index 63246462dfd..068af1b982c 100644 --- a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx +++ b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx @@ -14,21 +14,22 @@ * limitations under the License. */ -import { groupBy, orderBy } from 'lodash-es'; -import React, { useMemo } from 'react'; -import { Link } from 'react-router-dom'; +import {groupBy, orderBy} from 'lodash-es'; +import React, {useMemo} from 'react'; +import {Link} from 'react-router-dom'; -import { useWorkflow } from '../../../hooks/api'; -import { formatAlias } from '../../../util/tablets'; -import { formatDateTime, formatRelativeTime } from '../../../util/time'; -import { formatStreamKey, getStreams, getStreamSource, getStreamTarget } from '../../../util/workflows'; -import { DataCell } from '../../dataTable/DataCell'; -import { DataTable } from '../../dataTable/DataTable'; -import { TabletLink } from '../../links/TabletLink'; -import { StreamStatePip } from '../../pips/StreamStatePip'; -import { WorkflowStreamsLagChart } from '../../charts/WorkflowStreamsLagChart'; -import { ShardLink } from '../../links/ShardLink'; -import { env } from '../../../util/env'; +import {useWorkflow} from '../../../hooks/api'; +import {formatAlias} from '../../../util/tablets'; +import {formatDateTime, formatRelativeTime} from '../../../util/time'; +import {formatStreamKey, getStreams, getStreamSource, getStreamTarget} from '../../../util/workflows'; +import {DataCell} from '../../dataTable/DataCell'; +import {DataTable} from '../../dataTable/DataTable'; +import {TabletLink} from '../../links/TabletLink'; +import {StreamStatePip} from '../../pips/StreamStatePip'; +import {WorkflowStreamsLagChart} from '../../charts/WorkflowStreamsLagChart'; +import {ShardLink} from '../../links/ShardLink'; +import {env} from '../../../util/env'; +import {ThrottleThresholdSeconds} from "../Workflows"; interface Props { clusterID: string; @@ -38,8 +39,8 @@ interface Props { const COLUMNS = ['Stream', 'Source', 'Target', 'Tablet']; -export const WorkflowStreams = ({ clusterID, keyspace, name }: Props) => { - const { data } = useWorkflow({ clusterID, keyspace, name }); +export const WorkflowStreams = ({clusterID, keyspace, name}: Props) => { + const {data} = useWorkflow({clusterID, keyspace, name}); const streams = useMemo(() => { const rows = getStreams(data).map((stream) => ({ @@ -61,18 +62,19 @@ export const WorkflowStreams = ({ clusterID, keyspace, name }: Props) => { const source = getStreamSource(row); const target = getStreamTarget(row, keyspace); - const rowState = row?.throttler_status?.component_throttled ? 'Throttled' : row.state; + var isThrottled = Number(row?.throttler_status?.time_throttled?.seconds) > (Date.now() / 1000 - ThrottleThresholdSeconds) + const rowState = isThrottled ? 'Throttled' : row.state; return ( - {' '} + {' '} {row.key}
Updated {formatDateTime(row.time_updated?.seconds)}
- {row?.throttler_status?.component_throttled ? ( + {isThrottled ? (
Throttled: {row.throttler_status?.component_throttled}( @@ -117,7 +119,7 @@ export const WorkflowStreams = ({ clusterID, keyspace, name }: Props) => { {env().VITE_ENABLE_EXPERIMENTAL_TABLET_DEBUG_VARS && ( <>

Stream VReplication Lag

- + )} From ec0b95c3de328e2a881b75157892de41c8423290 Mon Sep 17 00:00:00 2001 From: Rohit Nayak Date: Wed, 3 Jul 2024 13:00:23 +0200 Subject: [PATCH 07/15] Prettier Signed-off-by: Rohit Nayak --- .../components/routes/workflow/Workflow.tsx | 137 +++++----- .../routes/workflow/WorkflowStreams.tsx | 256 ++++++++++-------- 2 files changed, 211 insertions(+), 182 deletions(-) diff --git a/web/vtadmin/src/components/routes/workflow/Workflow.tsx b/web/vtadmin/src/components/routes/workflow/Workflow.tsx index 32f624dd3f3..33b5cec3912 100644 --- a/web/vtadmin/src/components/routes/workflow/Workflow.tsx +++ b/web/vtadmin/src/components/routes/workflow/Workflow.tsx @@ -13,82 +13,93 @@ * 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, + useParams, + useRouteMatch, +} from "react-router-dom"; -import style from './Workflow.module.scss'; +import style from "./Workflow.module.scss"; -import { useWorkflow } from '../../../hooks/api'; -import { NavCrumbs } from '../../layout/NavCrumbs'; -import { WorkspaceHeader } from '../../layout/WorkspaceHeader'; -import { WorkspaceTitle } from '../../layout/WorkspaceTitle'; -import { useDocumentTitle } from '../../../hooks/useDocumentTitle'; -import { KeyspaceLink } from '../../links/KeyspaceLink'; -import { WorkflowStreams } from './WorkflowStreams'; -import { ContentContainer } from '../../layout/ContentContainer'; -import { TabContainer } from '../../tabs/TabContainer'; -import { Tab } from '../../tabs/Tab'; -import { getStreams } from '../../../util/workflows'; -import { Code } from '../../Code'; +import { useWorkflow } from "../../../hooks/api"; +import { NavCrumbs } from "../../layout/NavCrumbs"; +import { WorkspaceHeader } from "../../layout/WorkspaceHeader"; +import { WorkspaceTitle } from "../../layout/WorkspaceTitle"; +import { useDocumentTitle } from "../../../hooks/useDocumentTitle"; +import { KeyspaceLink } from "../../links/KeyspaceLink"; +import { WorkflowStreams } from "./WorkflowStreams"; +import { ContentContainer } from "../../layout/ContentContainer"; +import { TabContainer } from "../../tabs/TabContainer"; +import { Tab } from "../../tabs/Tab"; +import { getStreams } from "../../../util/workflows"; +import { Code } from "../../Code"; interface RouteParams { - clusterID: string; - keyspace: string; - name: string; + clusterID: string; + keyspace: string; + name: string; } export const Workflow = () => { - const { clusterID, keyspace, name } = useParams(); - const { path, url } = useRouteMatch(); + const { clusterID, keyspace, name } = useParams(); + const { path, url } = useRouteMatch(); - useDocumentTitle(`${name} (${keyspace})`); + useDocumentTitle(`${name} (${keyspace})`); - const { data } = useWorkflow({ clusterID, keyspace, name }); - const streams = getStreams(data); + const { data } = useWorkflow({ clusterID, keyspace, name }); + const streams = getStreams(data); - return ( -
- - - Workflows - + return ( +
+ + + Workflows + - {name} -
-
- - Cluster: {clusterID} - - - Target keyspace:{' '} - - {keyspace} - - -
-
- Streams -
-
-
+ {name} +
+
+ + Cluster: {clusterID} + + + Target keyspace:{" "} + + {keyspace} + + +
+
+ Streams +
+
+ - - - - - + + + + + - - - - + + + + - - - + + + - - - -
- ); + + + +
+ ); }; diff --git a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx index 068af1b982c..6dd16c613a6 100644 --- a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx +++ b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx @@ -14,137 +14,155 @@ * limitations under the License. */ -import {groupBy, orderBy} from 'lodash-es'; -import React, {useMemo} from 'react'; -import {Link} from 'react-router-dom'; +import { groupBy, orderBy } from "lodash-es"; +import React, { useMemo } from "react"; +import { Link } from "react-router-dom"; -import {useWorkflow} from '../../../hooks/api'; -import {formatAlias} from '../../../util/tablets'; -import {formatDateTime, formatRelativeTime} from '../../../util/time'; -import {formatStreamKey, getStreams, getStreamSource, getStreamTarget} from '../../../util/workflows'; -import {DataCell} from '../../dataTable/DataCell'; -import {DataTable} from '../../dataTable/DataTable'; -import {TabletLink} from '../../links/TabletLink'; -import {StreamStatePip} from '../../pips/StreamStatePip'; -import {WorkflowStreamsLagChart} from '../../charts/WorkflowStreamsLagChart'; -import {ShardLink} from '../../links/ShardLink'; -import {env} from '../../../util/env'; -import {ThrottleThresholdSeconds} from "../Workflows"; +import { useWorkflow } from "../../../hooks/api"; +import { formatAlias } from "../../../util/tablets"; +import { formatDateTime, formatRelativeTime } from "../../../util/time"; +import { + formatStreamKey, + getStreams, + getStreamSource, + getStreamTarget, +} from "../../../util/workflows"; +import { DataCell } from "../../dataTable/DataCell"; +import { DataTable } from "../../dataTable/DataTable"; +import { TabletLink } from "../../links/TabletLink"; +import { StreamStatePip } from "../../pips/StreamStatePip"; +import { WorkflowStreamsLagChart } from "../../charts/WorkflowStreamsLagChart"; +import { ShardLink } from "../../links/ShardLink"; +import { env } from "../../../util/env"; +import { ThrottleThresholdSeconds } from "../Workflows"; interface Props { - clusterID: string; - keyspace: string; - name: string; + clusterID: string; + keyspace: string; + name: string; } -const COLUMNS = ['Stream', 'Source', 'Target', 'Tablet']; +const COLUMNS = ["Stream", "Source", "Target", "Tablet"]; -export const WorkflowStreams = ({clusterID, keyspace, name}: Props) => { - const {data} = useWorkflow({clusterID, keyspace, name}); +export const WorkflowStreams = ({ clusterID, keyspace, name }: Props) => { + const { data } = useWorkflow({ clusterID, keyspace, name }); - const streams = useMemo(() => { - const rows = getStreams(data).map((stream) => ({ - key: formatStreamKey(stream), - ...stream, - })); + const streams = useMemo(() => { + const rows = getStreams(data).map((stream) => ({ + key: formatStreamKey(stream), + ...stream, + })); - return orderBy(rows, 'streamKey'); - }, [data]); + return orderBy(rows, "streamKey"); + }, [data]); - const streamsByState = groupBy(streams, 'state'); + const streamsByState = groupBy(streams, "state"); - const renderRows = (rows: typeof streams) => { - return rows.map((row) => { - const href = - row.tablet && row.id - ? `/workflow/${clusterID}/${keyspace}/${name}/stream/${row.tablet.cell}/${row.tablet.uid}/${row.id}` - : null; + const renderRows = (rows: typeof streams) => { + return rows.map((row) => { + const href = + row.tablet && row.id + ? `/workflow/${clusterID}/${keyspace}/${name}/stream/${row.tablet.cell}/${row.tablet.uid}/${row.id}` + : null; - const source = getStreamSource(row); - const target = getStreamTarget(row, keyspace); - var isThrottled = Number(row?.throttler_status?.time_throttled?.seconds) > (Date.now() / 1000 - ThrottleThresholdSeconds) - const rowState = isThrottled ? 'Throttled' : row.state; - return ( - - - {' '} - - {row.key} - -
- Updated {formatDateTime(row.time_updated?.seconds)} -
- {isThrottled ? ( -
- Throttled: - {row.throttler_status?.component_throttled}( - {formatRelativeTime(row.throttler_status?.time_throttled?.seconds)}) -
- ) : null} -
- - {source ? ( - - {source} - - ) : ( - N/A - )} - - - {target ? ( - - {target} - - ) : ( - N/A - )} - - - - {formatAlias(row.tablet)} - - - - ); - }); - }; - - return ( -
- {env().VITE_ENABLE_EXPERIMENTAL_TABLET_DEBUG_VARS && ( - <> -

Stream VReplication Lag

- - + const source = getStreamSource(row); + const target = getStreamTarget(row, keyspace); + var isThrottled = + Number(row?.throttler_status?.time_throttled?.seconds) > + Date.now() / 1000 - ThrottleThresholdSeconds; + const rowState = isThrottled ? "Throttled" : row.state; + return ( + + + {" "} + + {row.key} + +
+ Updated {formatDateTime(row.time_updated?.seconds)} +
+ {isThrottled ? ( +
+ Throttled: + {row.throttler_status?.component_throttled}( + {formatRelativeTime( + row.throttler_status?.time_throttled?.seconds + )} + ) +
+ ) : null} +
+ + {source ? ( + + {source} + + ) : ( + N/A + )} + + + {target ? ( + + {target} + + ) : ( + N/A )} + + + + {formatAlias(row.tablet)} + + + + ); + }); + }; + + return ( +
+ {env().VITE_ENABLE_EXPERIMENTAL_TABLET_DEBUG_VARS && ( + <> +

Stream VReplication Lag

+ + + )} -

- Streams -

- {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} - {['Error', 'Copying', 'Running', 'Stopped'].map((streamState) => { - if (!Array.isArray(streamsByState[streamState])) { - return null; - } +

+ Streams +

+ {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} + {["Error", "Copying", "Running", "Stopped"].map((streamState) => { + if (!Array.isArray(streamsByState[streamState])) { + return null; + } - return ( -
- -
- ); - })} -
- ); + return ( +
+ +
+ ); + })} +
+ ); }; From 7ce669085fd55672fd8b7da90ced5abcec0bf31e Mon Sep 17 00:00:00 2001 From: Rohit Nayak Date: Wed, 3 Jul 2024 21:54:46 +0200 Subject: [PATCH 08/15] Change Streams link text Signed-off-by: Rohit Nayak --- web/vtadmin/src/components/routes/workflow/Workflow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/vtadmin/src/components/routes/workflow/Workflow.tsx b/web/vtadmin/src/components/routes/workflow/Workflow.tsx index 33b5cec3912..3d32c5f9e8b 100644 --- a/web/vtadmin/src/components/routes/workflow/Workflow.tsx +++ b/web/vtadmin/src/components/routes/workflow/Workflow.tsx @@ -73,7 +73,7 @@ export const Workflow = () => {
From 60be644e10499024f66f35578004663c10b31aef Mon Sep 17 00:00:00 2001 From: Rohit Nayak Date: Wed, 3 Jul 2024 22:00:28 +0200 Subject: [PATCH 09/15] Don't display time info in tooltip Signed-off-by: Rohit Nayak --- web/vtadmin/src/components/routes/Workflows.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/web/vtadmin/src/components/routes/Workflows.tsx b/web/vtadmin/src/components/routes/Workflows.tsx index 7a76c78ad89..59a66ec0aac 100644 --- a/web/vtadmin/src/components/routes/Workflows.tsx +++ b/web/vtadmin/src/components/routes/Workflows.tsx @@ -116,7 +116,6 @@ export const Workflows = () => { if (streamState in row.streams) { var numThrottled = 0; var throttledApp: string | undefined = ''; - var throttledFrom: vttime.ITime | null | undefined; const streamCount = row.streams[streamState].length; var streamDescription: string; switch (streamState) { @@ -130,7 +129,7 @@ export const Workflows = () => { if ( stream?.throttler_status?.time_throttled !== null && stream?.throttler_status?.time_throttled !== undefined && - // If the stream has been throttled for more than 5 seconds, show it as throttled. + // If the stream has been throttled recently, treat it as throttled. Number(stream?.throttler_status?.time_throttled?.seconds) > (Date.now() / 1000 - ThrottleThresholdSeconds) ) { numThrottled++; @@ -138,7 +137,6 @@ export const Workflows = () => { // The detail page will show each stream separately. if (numThrottled === 1) { throttledApp = stream?.throttler_status?.component_throttled?.toString(); - throttledFrom = stream?.throttler_status?.time_throttled; } } } @@ -161,10 +159,7 @@ export const Workflows = () => { ? '(' + numThrottled + ' throttled in ' + - throttledApp + - ' ' + - formatRelativeTime(throttledFrom?.seconds) + - ')' + throttledApp + ')' : '', ].join(' '); return ( From 60f6c6160770723abb05d9c9d147dc84f746b0a8 Mon Sep 17 00:00:00 2001 From: Rohit Nayak Date: Wed, 3 Jul 2024 22:04:21 +0200 Subject: [PATCH 10/15] Don't display time info in stream status Signed-off-by: Rohit Nayak --- .../src/components/routes/workflow/WorkflowStreams.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx index 6dd16c613a6..6a948e084f4 100644 --- a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx +++ b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx @@ -83,12 +83,8 @@ export const WorkflowStreams = ({ clusterID, keyspace, name }: Props) => { {isThrottled ? (
- Throttled: - {row.throttler_status?.component_throttled}( - {formatRelativeTime( - row.throttler_status?.time_throttled?.seconds - )} - ) + Throttled: + in {row.throttler_status?.component_throttled}
) : null}
From cb764bc1fa736ecad93211b64eb2b35fe5249aee Mon Sep 17 00:00:00 2001 From: Rohit Nayak Date: Wed, 3 Jul 2024 22:22:20 +0200 Subject: [PATCH 11/15] Also handle Copying state Signed-off-by: Rohit Nayak --- web/vtadmin/src/components/routes/Workflows.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/web/vtadmin/src/components/routes/Workflows.tsx b/web/vtadmin/src/components/routes/Workflows.tsx index 59a66ec0aac..1f09e87e4ab 100644 --- a/web/vtadmin/src/components/routes/Workflows.tsx +++ b/web/vtadmin/src/components/routes/Workflows.tsx @@ -35,7 +35,7 @@ import {Tooltip} from '../tooltip/Tooltip'; import {KeyspaceLink} from '../links/KeyspaceLink'; import {QueryLoadingPlaceholder} from '../placeholders/QueryLoadingPlaceholder'; import {UseQueryResult} from 'react-query'; -import {vttime} from '../../proto/vtadmin'; +import {vtctldata, vttime} from '../../proto/vtadmin'; export const ThrottleThresholdSeconds = 60; @@ -123,9 +123,10 @@ export const Workflows = () => { streamDescription = 'failed'; break; case 'Running': - const running = row.streams['Running']; - if (running !== undefined && running !== null) { - for (const stream of running) { + case 'Copying': + const streams = row.streams[streamState]; + if (streams !== undefined && streams !== null) { + for (const stream of streams) { if ( stream?.throttler_status?.time_throttled !== null && stream?.throttler_status?.time_throttled !== undefined && @@ -142,11 +143,9 @@ export const Workflows = () => { } } if (numThrottled > 0) { - streamDescription = ''; streamState = 'Throttled'; - } else { - streamDescription = streamState; } + streamDescription = streamState.toLocaleLowerCase(); break; default: streamDescription = streamState.toLocaleLowerCase(); From 5cd70c84d3745e1f2aec0fb76fc7fab52863e811 Mon Sep 17 00:00:00 2001 From: Rohit Nayak Date: Wed, 3 Jul 2024 22:52:03 +0200 Subject: [PATCH 12/15] Fix stream description Signed-off-by: Rohit Nayak --- web/vtadmin/src/components/routes/Workflows.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/vtadmin/src/components/routes/Workflows.tsx b/web/vtadmin/src/components/routes/Workflows.tsx index 1f09e87e4ab..325a1a4754b 100644 --- a/web/vtadmin/src/components/routes/Workflows.tsx +++ b/web/vtadmin/src/components/routes/Workflows.tsx @@ -142,10 +142,10 @@ export const Workflows = () => { } } } + streamDescription = streamState.toLocaleLowerCase(); if (numThrottled > 0) { streamState = 'Throttled'; } - streamDescription = streamState.toLocaleLowerCase(); break; default: streamDescription = streamState.toLocaleLowerCase(); From 187ba0d96249b75aeb69b48dbb6f7ac1d89ae16d Mon Sep 17 00:00:00 2001 From: Rohit Nayak Date: Wed, 3 Jul 2024 23:09:55 +0200 Subject: [PATCH 13/15] Prettier Signed-off-by: Rohit Nayak --- .../src/components/routes/Workflows.tsx | 387 +++++++++--------- 1 file changed, 203 insertions(+), 184 deletions(-) diff --git a/web/vtadmin/src/components/routes/Workflows.tsx b/web/vtadmin/src/components/routes/Workflows.tsx index 325a1a4754b..702867b3ff4 100644 --- a/web/vtadmin/src/components/routes/Workflows.tsx +++ b/web/vtadmin/src/components/routes/Workflows.tsx @@ -13,201 +13,220 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {groupBy, orderBy} from 'lodash-es'; -import * as React from 'react'; -import {Link} from 'react-router-dom'; +import { groupBy, orderBy } from "lodash-es"; +import * as React from "react"; +import { Link } from "react-router-dom"; -import style from './Workflows.module.scss'; -import {useWorkflows} from '../../hooks/api'; -import {useDocumentTitle} from '../../hooks/useDocumentTitle'; -import {DataCell} from '../dataTable/DataCell'; -import {DataTable} from '../dataTable/DataTable'; -import {useSyncedURLParam} from '../../hooks/useSyncedURLParam'; -import {filterNouns} from '../../util/filterNouns'; -import {getStreams, getTimeUpdated} from '../../util/workflows'; -import {formatDateTime, formatRelativeTime} from '../../util/time'; -import {StreamStatePip} from '../pips/StreamStatePip'; -import {ContentContainer} from '../layout/ContentContainer'; -import {WorkspaceHeader} from '../layout/WorkspaceHeader'; -import {WorkspaceTitle} from '../layout/WorkspaceTitle'; -import {DataFilter} from '../dataTable/DataFilter'; -import {Tooltip} from '../tooltip/Tooltip'; -import {KeyspaceLink} from '../links/KeyspaceLink'; -import {QueryLoadingPlaceholder} from '../placeholders/QueryLoadingPlaceholder'; -import {UseQueryResult} from 'react-query'; -import {vtctldata, vttime} from '../../proto/vtadmin'; +import style from "./Workflows.module.scss"; +import { useWorkflows } from "../../hooks/api"; +import { useDocumentTitle } from "../../hooks/useDocumentTitle"; +import { DataCell } from "../dataTable/DataCell"; +import { DataTable } from "../dataTable/DataTable"; +import { useSyncedURLParam } from "../../hooks/useSyncedURLParam"; +import { filterNouns } from "../../util/filterNouns"; +import { getStreams, getTimeUpdated } from "../../util/workflows"; +import { formatDateTime, formatRelativeTime } from "../../util/time"; +import { StreamStatePip } from "../pips/StreamStatePip"; +import { ContentContainer } from "../layout/ContentContainer"; +import { WorkspaceHeader } from "../layout/WorkspaceHeader"; +import { WorkspaceTitle } from "../layout/WorkspaceTitle"; +import { DataFilter } from "../dataTable/DataFilter"; +import { Tooltip } from "../tooltip/Tooltip"; +import { KeyspaceLink } from "../links/KeyspaceLink"; +import { QueryLoadingPlaceholder } from "../placeholders/QueryLoadingPlaceholder"; +import { UseQueryResult } from "react-query"; +import { vtctldata, vttime } from "../../proto/vtadmin"; export const ThrottleThresholdSeconds = 60; export const Workflows = () => { - useDocumentTitle('Workflows'); - const workflowsQuery = useWorkflows(); + useDocumentTitle("Workflows"); + const workflowsQuery = useWorkflows(); - const {value: filter, updateValue: updateFilter} = useSyncedURLParam('filter'); + const { value: filter, updateValue: updateFilter } = + useSyncedURLParam("filter"); - const sortedData = React.useMemo(() => { - const mapped = (workflowsQuery.data || []).map((workflow) => ({ - clusterID: workflow.cluster?.id, - clusterName: workflow.cluster?.name, - keyspace: workflow.keyspace, - name: workflow.workflow?.name, - source: workflow.workflow?.source?.keyspace, - sourceShards: workflow.workflow?.source?.shards, - streams: groupBy(getStreams(workflow), 'state'), - target: workflow.workflow?.target?.keyspace, - targetShards: workflow.workflow?.target?.shards, - timeUpdated: getTimeUpdated(workflow), - workflowType: workflow.workflow?.workflow_type, - workflowSubType: workflow.workflow?.workflow_sub_type, - })); - const filtered = filterNouns(filter, mapped); - return orderBy(filtered, ['name', 'clusterName', 'source', 'target']); - }, [workflowsQuery.data, filter]); + const sortedData = React.useMemo(() => { + const mapped = (workflowsQuery.data || []).map((workflow) => ({ + clusterID: workflow.cluster?.id, + clusterName: workflow.cluster?.name, + keyspace: workflow.keyspace, + name: workflow.workflow?.name, + source: workflow.workflow?.source?.keyspace, + sourceShards: workflow.workflow?.source?.shards, + streams: groupBy(getStreams(workflow), "state"), + target: workflow.workflow?.target?.keyspace, + targetShards: workflow.workflow?.target?.shards, + timeUpdated: getTimeUpdated(workflow), + workflowType: workflow.workflow?.workflow_type, + workflowSubType: workflow.workflow?.workflow_sub_type, + })); + const filtered = filterNouns(filter, mapped); + return orderBy(filtered, ["name", "clusterName", "source", "target"]); + }, [workflowsQuery.data, filter]); - const renderRows = (rows: typeof sortedData) => - rows.map((row, idx) => { - const href = - row.clusterID && row.keyspace && row.name - ? `/workflow/${row.clusterID}/${row.keyspace}/${row.name}` - : null; - return ( - - -
{href ? {row.name} : row.name}
- {row.workflowType && ( -
- {row.workflowType} - {row.workflowSubType && row.workflowSubType !== 'None' && ( - {' (' + row.workflowSubType + ')'} - )} -
- )} -
{row.clusterName}
-
- - {row.source ? ( - <> - - {row.source} - -
{(row.sourceShards || []).join(', ')}
- - ) : ( - N/A - )} -
- - {row.target ? ( - <> - - {row.target} - -
{(row.targetShards || []).join(', ')}
- - ) : ( - N/A - )} -
+ const renderRows = (rows: typeof sortedData) => + rows.map((row, idx) => { + const href = + row.clusterID && row.keyspace && row.name + ? `/workflow/${row.clusterID}/${row.keyspace}/${row.name}` + : null; + return ( + + +
+ {href ? {row.name} : row.name} +
+ {row.workflowType && ( +
+ {row.workflowType} + {row.workflowSubType && row.workflowSubType !== "None" && ( + + {" (" + row.workflowSubType + ")"} + + )} +
+ )} +
{row.clusterName}
+
+ + {row.source ? ( + <> + + {row.source} + +
+ {(row.sourceShards || []).join(", ")} +
+ + ) : ( + N/A + )} +
+ + {row.target ? ( + <> + + {row.target} + +
+ {(row.targetShards || []).join(", ")} +
+ + ) : ( + N/A + )} +
- -
- {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} - {['Error', 'Copying', 'Running', 'Stopped'].map((streamState) => { - if (streamState in row.streams) { - var numThrottled = 0; - var throttledApp: string | undefined = ''; - const streamCount = row.streams[streamState].length; - var streamDescription: string; - switch (streamState) { - case 'Error': - streamDescription = 'failed'; - break; - case 'Running': - case 'Copying': - const streams = row.streams[streamState]; - if (streams !== undefined && streams !== null) { - for (const stream of streams) { - if ( - stream?.throttler_status?.time_throttled !== null && - stream?.throttler_status?.time_throttled !== undefined && - // If the stream has been throttled recently, treat it as throttled. - Number(stream?.throttler_status?.time_throttled?.seconds) > (Date.now() / 1000 - ThrottleThresholdSeconds) - ) { - numThrottled++; - // In case of multiple streams, show the first throttled app and time. - // The detail page will show each stream separately. - if (numThrottled === 1) { - throttledApp = stream?.throttler_status?.component_throttled?.toString(); - } - } - } - } - streamDescription = streamState.toLocaleLowerCase(); - if (numThrottled > 0) { - streamState = 'Throttled'; - } - break; - default: - streamDescription = streamState.toLocaleLowerCase(); - } - const tooltip = [ - streamCount, - streamDescription, - streamCount === 1 ? 'stream' : 'streams', - numThrottled > 0 - ? '(' + - numThrottled + - ' throttled in ' + - throttledApp + ')' - : '', - ].join(' '); - return ( - - - {streamCount} - - - ); - } - return ( - - - - - ); - })} -
-
+ +
+ {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} + {["Error", "Copying", "Running", "Stopped"].map((streamState) => { + if (streamState in row.streams) { + var numThrottled = 0; + var throttledApp: string | undefined = ""; + const streamCount = row.streams[streamState].length; + var streamDescription: string; + switch (streamState) { + case "Error": + streamDescription = "failed"; + break; + case "Running": + case "Copying": + const streams = row.streams[streamState]; + if (streams !== undefined && streams !== null) { + for (const stream of streams) { + if ( + stream?.throttler_status?.time_throttled !== null && + stream?.throttler_status?.time_throttled !== + undefined && + // If the stream has been throttled recently, treat it as throttled. + Number( + stream?.throttler_status?.time_throttled?.seconds + ) > + Date.now() / 1000 - ThrottleThresholdSeconds + ) { + numThrottled++; + // In case of multiple streams, show the first throttled app and time. + // The detail page will show each stream separately. + if (numThrottled === 1) { + throttledApp = + stream?.throttler_status?.component_throttled?.toString(); + } + } + } + } + streamDescription = streamState.toLocaleLowerCase(); + if (numThrottled > 0) { + streamState = "Throttled"; + } + break; + default: + streamDescription = streamState.toLocaleLowerCase(); + } + const tooltip = [ + streamCount, + streamDescription, + streamCount === 1 ? "stream" : "streams", + numThrottled > 0 + ? "(" + + numThrottled + + " throttled in " + + throttledApp + + ")" + : "", + ].join(" "); + return ( + + + {streamCount} + + + ); + } + return ( + + - + + ); + })} +
+
- -
{formatDateTime(row.timeUpdated)}
-
{formatRelativeTime(row.timeUpdated)}
-
- - ); - }); + +
+ {formatDateTime(row.timeUpdated)} +
+
+ {formatRelativeTime(row.timeUpdated)} +
+
+ + ); + }); - return ( -
- - Workflows - - - updateFilter(e.target.value)} - onClear={() => updateFilter('')} - placeholder="Filter workflows" - value={filter || ''} - /> + return ( +
+ + Workflows + + + updateFilter(e.target.value)} + onClear={() => updateFilter("")} + placeholder="Filter workflows" + value={filter || ""} + /> - + - - -
- ); + +
+
+ ); }; From 10232332c819589e0e622b86c3ef68b29fbd1cdd Mon Sep 17 00:00:00 2001 From: Rohit Nayak Date: Wed, 3 Jul 2024 23:13:15 +0200 Subject: [PATCH 14/15] Remove unused imports Signed-off-by: Rohit Nayak --- web/vtadmin/src/components/routes/Workflows.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/web/vtadmin/src/components/routes/Workflows.tsx b/web/vtadmin/src/components/routes/Workflows.tsx index 702867b3ff4..f3d17cff589 100644 --- a/web/vtadmin/src/components/routes/Workflows.tsx +++ b/web/vtadmin/src/components/routes/Workflows.tsx @@ -35,7 +35,6 @@ import { Tooltip } from "../tooltip/Tooltip"; import { KeyspaceLink } from "../links/KeyspaceLink"; import { QueryLoadingPlaceholder } from "../placeholders/QueryLoadingPlaceholder"; import { UseQueryResult } from "react-query"; -import { vtctldata, vttime } from "../../proto/vtadmin"; export const ThrottleThresholdSeconds = 60; From 860f117df00f9506326ffbca25cd8b2a6e196e85 Mon Sep 17 00:00:00 2001 From: Rohit Nayak Date: Wed, 3 Jul 2024 23:25:28 +0200 Subject: [PATCH 15/15] Prettifier again with a locally installed prettifier using the .prettifiercc config Signed-off-by: Rohit Nayak --- .../src/components/routes/Workflows.tsx | 384 +++++++++--------- .../components/routes/workflow/Workflow.tsx | 137 +++---- .../routes/workflow/WorkflowStreams.tsx | 250 ++++++------ 3 files changed, 363 insertions(+), 408 deletions(-) diff --git a/web/vtadmin/src/components/routes/Workflows.tsx b/web/vtadmin/src/components/routes/Workflows.tsx index f3d17cff589..32ddfcfb825 100644 --- a/web/vtadmin/src/components/routes/Workflows.tsx +++ b/web/vtadmin/src/components/routes/Workflows.tsx @@ -13,219 +13,199 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { groupBy, orderBy } from "lodash-es"; -import * as React from "react"; -import { Link } from "react-router-dom"; +import { groupBy, orderBy } from 'lodash-es'; +import * as React from 'react'; +import { Link } from 'react-router-dom'; -import style from "./Workflows.module.scss"; -import { useWorkflows } from "../../hooks/api"; -import { useDocumentTitle } from "../../hooks/useDocumentTitle"; -import { DataCell } from "../dataTable/DataCell"; -import { DataTable } from "../dataTable/DataTable"; -import { useSyncedURLParam } from "../../hooks/useSyncedURLParam"; -import { filterNouns } from "../../util/filterNouns"; -import { getStreams, getTimeUpdated } from "../../util/workflows"; -import { formatDateTime, formatRelativeTime } from "../../util/time"; -import { StreamStatePip } from "../pips/StreamStatePip"; -import { ContentContainer } from "../layout/ContentContainer"; -import { WorkspaceHeader } from "../layout/WorkspaceHeader"; -import { WorkspaceTitle } from "../layout/WorkspaceTitle"; -import { DataFilter } from "../dataTable/DataFilter"; -import { Tooltip } from "../tooltip/Tooltip"; -import { KeyspaceLink } from "../links/KeyspaceLink"; -import { QueryLoadingPlaceholder } from "../placeholders/QueryLoadingPlaceholder"; -import { UseQueryResult } from "react-query"; +import style from './Workflows.module.scss'; +import { useWorkflows } from '../../hooks/api'; +import { useDocumentTitle } from '../../hooks/useDocumentTitle'; +import { DataCell } from '../dataTable/DataCell'; +import { DataTable } from '../dataTable/DataTable'; +import { useSyncedURLParam } from '../../hooks/useSyncedURLParam'; +import { filterNouns } from '../../util/filterNouns'; +import { getStreams, getTimeUpdated } from '../../util/workflows'; +import { formatDateTime, formatRelativeTime } from '../../util/time'; +import { StreamStatePip } from '../pips/StreamStatePip'; +import { ContentContainer } from '../layout/ContentContainer'; +import { WorkspaceHeader } from '../layout/WorkspaceHeader'; +import { WorkspaceTitle } from '../layout/WorkspaceTitle'; +import { DataFilter } from '../dataTable/DataFilter'; +import { Tooltip } from '../tooltip/Tooltip'; +import { KeyspaceLink } from '../links/KeyspaceLink'; +import { QueryLoadingPlaceholder } from '../placeholders/QueryLoadingPlaceholder'; +import { UseQueryResult } from 'react-query'; export const ThrottleThresholdSeconds = 60; export const Workflows = () => { - useDocumentTitle("Workflows"); - const workflowsQuery = useWorkflows(); + useDocumentTitle('Workflows'); + const workflowsQuery = useWorkflows(); - const { value: filter, updateValue: updateFilter } = - useSyncedURLParam("filter"); + const { value: filter, updateValue: updateFilter } = useSyncedURLParam('filter'); - const sortedData = React.useMemo(() => { - const mapped = (workflowsQuery.data || []).map((workflow) => ({ - clusterID: workflow.cluster?.id, - clusterName: workflow.cluster?.name, - keyspace: workflow.keyspace, - name: workflow.workflow?.name, - source: workflow.workflow?.source?.keyspace, - sourceShards: workflow.workflow?.source?.shards, - streams: groupBy(getStreams(workflow), "state"), - target: workflow.workflow?.target?.keyspace, - targetShards: workflow.workflow?.target?.shards, - timeUpdated: getTimeUpdated(workflow), - workflowType: workflow.workflow?.workflow_type, - workflowSubType: workflow.workflow?.workflow_sub_type, - })); - const filtered = filterNouns(filter, mapped); - return orderBy(filtered, ["name", "clusterName", "source", "target"]); - }, [workflowsQuery.data, filter]); + const sortedData = React.useMemo(() => { + const mapped = (workflowsQuery.data || []).map((workflow) => ({ + clusterID: workflow.cluster?.id, + clusterName: workflow.cluster?.name, + keyspace: workflow.keyspace, + name: workflow.workflow?.name, + source: workflow.workflow?.source?.keyspace, + sourceShards: workflow.workflow?.source?.shards, + streams: groupBy(getStreams(workflow), 'state'), + target: workflow.workflow?.target?.keyspace, + targetShards: workflow.workflow?.target?.shards, + timeUpdated: getTimeUpdated(workflow), + workflowType: workflow.workflow?.workflow_type, + workflowSubType: workflow.workflow?.workflow_sub_type, + })); + const filtered = filterNouns(filter, mapped); + return orderBy(filtered, ['name', 'clusterName', 'source', 'target']); + }, [workflowsQuery.data, filter]); - const renderRows = (rows: typeof sortedData) => - rows.map((row, idx) => { - const href = - row.clusterID && row.keyspace && row.name - ? `/workflow/${row.clusterID}/${row.keyspace}/${row.name}` - : null; - return ( - - -
- {href ? {row.name} : row.name} -
- {row.workflowType && ( -
- {row.workflowType} - {row.workflowSubType && row.workflowSubType !== "None" && ( - - {" (" + row.workflowSubType + ")"} - - )} -
- )} -
{row.clusterName}
-
- - {row.source ? ( - <> - - {row.source} - -
- {(row.sourceShards || []).join(", ")} -
- - ) : ( - N/A - )} -
- - {row.target ? ( - <> - - {row.target} - -
- {(row.targetShards || []).join(", ")} -
- - ) : ( - N/A - )} -
+ const renderRows = (rows: typeof sortedData) => + rows.map((row, idx) => { + const href = + row.clusterID && row.keyspace && row.name + ? `/workflow/${row.clusterID}/${row.keyspace}/${row.name}` + : null; + return ( + + +
{href ? {row.name} : row.name}
+ {row.workflowType && ( +
+ {row.workflowType} + {row.workflowSubType && row.workflowSubType !== 'None' && ( + {' (' + row.workflowSubType + ')'} + )} +
+ )} +
{row.clusterName}
+
+ + {row.source ? ( + <> + + {row.source} + +
{(row.sourceShards || []).join(', ')}
+ + ) : ( + N/A + )} +
+ + {row.target ? ( + <> + + {row.target} + +
{(row.targetShards || []).join(', ')}
+ + ) : ( + N/A + )} +
- -
- {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} - {["Error", "Copying", "Running", "Stopped"].map((streamState) => { - if (streamState in row.streams) { - var numThrottled = 0; - var throttledApp: string | undefined = ""; - const streamCount = row.streams[streamState].length; - var streamDescription: string; - switch (streamState) { - case "Error": - streamDescription = "failed"; - break; - case "Running": - case "Copying": - const streams = row.streams[streamState]; - if (streams !== undefined && streams !== null) { - for (const stream of streams) { - if ( - stream?.throttler_status?.time_throttled !== null && - stream?.throttler_status?.time_throttled !== - undefined && - // If the stream has been throttled recently, treat it as throttled. - Number( - stream?.throttler_status?.time_throttled?.seconds - ) > - Date.now() / 1000 - ThrottleThresholdSeconds - ) { - numThrottled++; - // In case of multiple streams, show the first throttled app and time. - // The detail page will show each stream separately. - if (numThrottled === 1) { - throttledApp = - stream?.throttler_status?.component_throttled?.toString(); - } - } - } - } - streamDescription = streamState.toLocaleLowerCase(); - if (numThrottled > 0) { - streamState = "Throttled"; - } - break; - default: - streamDescription = streamState.toLocaleLowerCase(); - } - const tooltip = [ - streamCount, - streamDescription, - streamCount === 1 ? "stream" : "streams", - numThrottled > 0 - ? "(" + - numThrottled + - " throttled in " + - throttledApp + - ")" - : "", - ].join(" "); - return ( - - - {streamCount} - - - ); - } - return ( - - - - - ); - })} -
-
+ +
+ {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} + {['Error', 'Copying', 'Running', 'Stopped'].map((streamState) => { + if (streamState in row.streams) { + var numThrottled = 0; + var throttledApp: string | undefined = ''; + const streamCount = row.streams[streamState].length; + var streamDescription: string; + switch (streamState) { + case 'Error': + streamDescription = 'failed'; + break; + case 'Running': + case 'Copying': + const streams = row.streams[streamState]; + if (streams !== undefined && streams !== null) { + for (const stream of streams) { + if ( + stream?.throttler_status?.time_throttled !== null && + stream?.throttler_status?.time_throttled !== undefined && + // If the stream has been throttled recently, treat it as throttled. + Number(stream?.throttler_status?.time_throttled?.seconds) > + Date.now() / 1000 - ThrottleThresholdSeconds + ) { + numThrottled++; + // In case of multiple streams, show the first throttled app and time. + // The detail page will show each stream separately. + if (numThrottled === 1) { + throttledApp = + stream?.throttler_status?.component_throttled?.toString(); + } + } + } + } + streamDescription = streamState.toLocaleLowerCase(); + if (numThrottled > 0) { + streamState = 'Throttled'; + } + break; + default: + streamDescription = streamState.toLocaleLowerCase(); + } + const tooltip = [ + streamCount, + streamDescription, + streamCount === 1 ? 'stream' : 'streams', + numThrottled > 0 + ? '(' + numThrottled + ' throttled in ' + throttledApp + ')' + : '', + ].join(' '); + return ( + + + {streamCount} + + + ); + } + return ( + + - + + ); + })} +
+
- -
- {formatDateTime(row.timeUpdated)} -
-
- {formatRelativeTime(row.timeUpdated)} -
-
- - ); - }); + +
{formatDateTime(row.timeUpdated)}
+
{formatRelativeTime(row.timeUpdated)}
+
+ + ); + }); - return ( -
- - Workflows - - - updateFilter(e.target.value)} - onClear={() => updateFilter("")} - placeholder="Filter workflows" - value={filter || ""} - /> + return ( +
+ + Workflows + + + updateFilter(e.target.value)} + onClear={() => updateFilter('')} + placeholder="Filter workflows" + value={filter || ''} + /> - + - - -
- ); + +
+
+ ); }; diff --git a/web/vtadmin/src/components/routes/workflow/Workflow.tsx b/web/vtadmin/src/components/routes/workflow/Workflow.tsx index 3d32c5f9e8b..a81901786d8 100644 --- a/web/vtadmin/src/components/routes/workflow/Workflow.tsx +++ b/web/vtadmin/src/components/routes/workflow/Workflow.tsx @@ -13,93 +13,82 @@ * 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, useParams, useRouteMatch } from 'react-router-dom'; -import style from "./Workflow.module.scss"; +import style from './Workflow.module.scss'; -import { useWorkflow } from "../../../hooks/api"; -import { NavCrumbs } from "../../layout/NavCrumbs"; -import { WorkspaceHeader } from "../../layout/WorkspaceHeader"; -import { WorkspaceTitle } from "../../layout/WorkspaceTitle"; -import { useDocumentTitle } from "../../../hooks/useDocumentTitle"; -import { KeyspaceLink } from "../../links/KeyspaceLink"; -import { WorkflowStreams } from "./WorkflowStreams"; -import { ContentContainer } from "../../layout/ContentContainer"; -import { TabContainer } from "../../tabs/TabContainer"; -import { Tab } from "../../tabs/Tab"; -import { getStreams } from "../../../util/workflows"; -import { Code } from "../../Code"; +import { useWorkflow } from '../../../hooks/api'; +import { NavCrumbs } from '../../layout/NavCrumbs'; +import { WorkspaceHeader } from '../../layout/WorkspaceHeader'; +import { WorkspaceTitle } from '../../layout/WorkspaceTitle'; +import { useDocumentTitle } from '../../../hooks/useDocumentTitle'; +import { KeyspaceLink } from '../../links/KeyspaceLink'; +import { WorkflowStreams } from './WorkflowStreams'; +import { ContentContainer } from '../../layout/ContentContainer'; +import { TabContainer } from '../../tabs/TabContainer'; +import { Tab } from '../../tabs/Tab'; +import { getStreams } from '../../../util/workflows'; +import { Code } from '../../Code'; interface RouteParams { - clusterID: string; - keyspace: string; - name: string; + clusterID: string; + keyspace: string; + name: string; } export const Workflow = () => { - const { clusterID, keyspace, name } = useParams(); - const { path, url } = useRouteMatch(); + const { clusterID, keyspace, name } = useParams(); + const { path, url } = useRouteMatch(); - useDocumentTitle(`${name} (${keyspace})`); + useDocumentTitle(`${name} (${keyspace})`); - const { data } = useWorkflow({ clusterID, keyspace, name }); - const streams = getStreams(data); + const { data } = useWorkflow({ clusterID, keyspace, name }); + const streams = getStreams(data); - return ( -
- - - Workflows - + return ( +
+ + + Workflows + - {name} -
-
- - Cluster: {clusterID} - - - Target keyspace:{" "} - - {keyspace} - - -
- -
-
+ {name} +
+
+ + Cluster: {clusterID} + + + Target keyspace:{' '} + + {keyspace} + + +
+ +
+ - - - - - + + + + + - - - - + + + + - - - + + + - - - -
- ); + + + +
+ ); }; diff --git a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx index 6a948e084f4..2d86e1141a6 100644 --- a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx +++ b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx @@ -14,151 +14,137 @@ * limitations under the License. */ -import { groupBy, orderBy } from "lodash-es"; -import React, { useMemo } from "react"; -import { Link } from "react-router-dom"; +import { groupBy, orderBy } from 'lodash-es'; +import React, { useMemo } from 'react'; +import { Link } from 'react-router-dom'; -import { useWorkflow } from "../../../hooks/api"; -import { formatAlias } from "../../../util/tablets"; -import { formatDateTime, formatRelativeTime } from "../../../util/time"; -import { - formatStreamKey, - getStreams, - getStreamSource, - getStreamTarget, -} from "../../../util/workflows"; -import { DataCell } from "../../dataTable/DataCell"; -import { DataTable } from "../../dataTable/DataTable"; -import { TabletLink } from "../../links/TabletLink"; -import { StreamStatePip } from "../../pips/StreamStatePip"; -import { WorkflowStreamsLagChart } from "../../charts/WorkflowStreamsLagChart"; -import { ShardLink } from "../../links/ShardLink"; -import { env } from "../../../util/env"; -import { ThrottleThresholdSeconds } from "../Workflows"; +import { useWorkflow } from '../../../hooks/api'; +import { formatAlias } from '../../../util/tablets'; +import { formatDateTime, formatRelativeTime } from '../../../util/time'; +import { formatStreamKey, getStreams, getStreamSource, getStreamTarget } from '../../../util/workflows'; +import { DataCell } from '../../dataTable/DataCell'; +import { DataTable } from '../../dataTable/DataTable'; +import { TabletLink } from '../../links/TabletLink'; +import { StreamStatePip } from '../../pips/StreamStatePip'; +import { WorkflowStreamsLagChart } from '../../charts/WorkflowStreamsLagChart'; +import { ShardLink } from '../../links/ShardLink'; +import { env } from '../../../util/env'; +import { ThrottleThresholdSeconds } from '../Workflows'; interface Props { - clusterID: string; - keyspace: string; - name: string; + clusterID: string; + keyspace: string; + name: string; } -const COLUMNS = ["Stream", "Source", "Target", "Tablet"]; +const COLUMNS = ['Stream', 'Source', 'Target', 'Tablet']; export const WorkflowStreams = ({ clusterID, keyspace, name }: Props) => { - const { data } = useWorkflow({ clusterID, keyspace, name }); + const { data } = useWorkflow({ clusterID, keyspace, name }); - const streams = useMemo(() => { - const rows = getStreams(data).map((stream) => ({ - key: formatStreamKey(stream), - ...stream, - })); + const streams = useMemo(() => { + const rows = getStreams(data).map((stream) => ({ + key: formatStreamKey(stream), + ...stream, + })); - return orderBy(rows, "streamKey"); - }, [data]); + return orderBy(rows, 'streamKey'); + }, [data]); - const streamsByState = groupBy(streams, "state"); + const streamsByState = groupBy(streams, 'state'); - const renderRows = (rows: typeof streams) => { - return rows.map((row) => { - const href = - row.tablet && row.id - ? `/workflow/${clusterID}/${keyspace}/${name}/stream/${row.tablet.cell}/${row.tablet.uid}/${row.id}` - : null; + const renderRows = (rows: typeof streams) => { + return rows.map((row) => { + const href = + row.tablet && row.id + ? `/workflow/${clusterID}/${keyspace}/${name}/stream/${row.tablet.cell}/${row.tablet.uid}/${row.id}` + : null; - const source = getStreamSource(row); - const target = getStreamTarget(row, keyspace); - var isThrottled = - Number(row?.throttler_status?.time_throttled?.seconds) > - Date.now() / 1000 - ThrottleThresholdSeconds; - const rowState = isThrottled ? "Throttled" : row.state; - return ( - - - {" "} - - {row.key} - -
- Updated {formatDateTime(row.time_updated?.seconds)} -
- {isThrottled ? ( -
- Throttled: - in {row.throttler_status?.component_throttled} -
- ) : null} -
- - {source ? ( - - {source} - - ) : ( - N/A - )} - - - {target ? ( - - {target} - - ) : ( - N/A - )} - - - - {formatAlias(row.tablet)} - - - - ); - }); - }; + const source = getStreamSource(row); + const target = getStreamTarget(row, keyspace); + var isThrottled = + Number(row?.throttler_status?.time_throttled?.seconds) > Date.now() / 1000 - ThrottleThresholdSeconds; + const rowState = isThrottled ? 'Throttled' : row.state; + return ( + + + {' '} + + {row.key} + +
+ Updated {formatDateTime(row.time_updated?.seconds)} +
+ {isThrottled ? ( +
+ Throttled: + in {row.throttler_status?.component_throttled} +
+ ) : null} +
+ + {source ? ( + + {source} + + ) : ( + N/A + )} + + + {target ? ( + + {target} + + ) : ( + N/A + )} + + + + {formatAlias(row.tablet)} + + + + ); + }); + }; - return ( -
- {env().VITE_ENABLE_EXPERIMENTAL_TABLET_DEBUG_VARS && ( - <> -

Stream VReplication Lag

- - - )} + return ( +
+ {env().VITE_ENABLE_EXPERIMENTAL_TABLET_DEBUG_VARS && ( + <> +

Stream VReplication Lag

+ + + )} -

- Streams -

- {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} - {["Error", "Copying", "Running", "Stopped"].map((streamState) => { - if (!Array.isArray(streamsByState[streamState])) { - return null; - } +

+ Streams +

+ {/* TODO(doeg): add a protobuf enum for this (https://github.com/vitessio/vitess/projects/12#card-60190340) */} + {['Error', 'Copying', 'Running', 'Stopped'].map((streamState) => { + if (!Array.isArray(streamsByState[streamState])) { + return null; + } - return ( -
- -
- ); - })} -
- ); + return ( +
+ +
+ ); + })} +
+ ); };