Skip to content

Commit

Permalink
Merge pull request #8203 from tinyspeck/sarabee-workflow-tabs
Browse files Browse the repository at this point in the history
[vtadmin-web] Add tabs to Workflow view
  • Loading branch information
ajm188 authored May 28, 2021
2 parents 2b0c8fd + 686d61d commit a1e0047
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 103 deletions.
8 changes: 4 additions & 4 deletions web/vtadmin/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,14 @@ export const App = () => {
<Workflows />
</Route>

<Route exact path="/workflow/:clusterID/:keyspace/:name">
<Workflow />
</Route>

<Route path="/workflow/:clusterID/:keyspace/:workflowName/stream/:tabletCell/:tabletUID/:streamID">
<Stream />
</Route>

<Route path="/workflow/:clusterID/:keyspace/:name">
<Workflow />
</Route>

<Route path="/debug">
<Debug />
</Route>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,3 @@
content: none;
}
}

.streamTable {
margin: 0 0 48px 0;
}
122 changes: 27 additions & 95 deletions web/vtadmin/src/components/routes/workflow/Workflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,103 +13,37 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useMemo } from 'react';
import { groupBy, orderBy } from 'lodash-es';
import { Link, useParams } 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 { formatStreamKey, getStreams, getStreamSource, getStreamTarget } from '../../../util/workflows';
import { DataCell } from '../../dataTable/DataCell';
import { DataTable } from '../../dataTable/DataTable';
import { ContentContainer } from '../../layout/ContentContainer';
import { NavCrumbs } from '../../layout/NavCrumbs';
import { WorkspaceHeader } from '../../layout/WorkspaceHeader';
import { WorkspaceTitle } from '../../layout/WorkspaceTitle';
import { StreamStatePip } from '../../pips/StreamStatePip';
import { formatAlias } from '../../../util/tablets';
import { useDocumentTitle } from '../../../hooks/useDocumentTitle';
import { formatDateTime } from '../../../util/time';
import { KeyspaceLink } from '../../links/KeyspaceLink';
import { TabletLink } from '../../links/TabletLink';
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;
}

const COLUMNS = ['Stream', 'Source', 'Target', 'Tablet'];

export const Workflow = () => {
const { clusterID, keyspace, name } = useParams<RouteParams>();
const { path, url } = useRouteMatch();

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

const { data } = useWorkflow({ clusterID, keyspace, name }, { refetchInterval: 1000 });

const streams = useMemo(() => {
const rows = getStreams(data).map((stream) => ({
key: formatStreamKey(stream),
...stream,
}));

return orderBy(rows, 'streamKey');
}, [data]);

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 source = getStreamSource(row);
const target = getStreamTarget(row, keyspace);

return (
<tr key={row.key}>
<DataCell>
<StreamStatePip state={row.state} />{' '}
<Link className="font-weight-bold" to={href}>
{row.key}
</Link>
<div className="font-size-small text-color-secondary">
Updated {formatDateTime(row.time_updated?.seconds)}
</div>
</DataCell>
<DataCell>
{source ? (
<KeyspaceLink
clusterID={clusterID}
name={row.binlog_source?.keyspace}
shard={row.binlog_source?.shard}
>
{source}
</KeyspaceLink>
) : (
<span className="text-color-secondary">N/A</span>
)}
</DataCell>
<DataCell>
{target ? (
<KeyspaceLink clusterID={clusterID} name={keyspace} shard={row.shard}>
{source}
</KeyspaceLink>
) : (
<span className="text-color-secondary">N/A</span>
)}
</DataCell>
<DataCell>
<TabletLink alias={formatAlias(row.tablet)} clusterID={clusterID}>
{formatAlias(row.tablet)}
</TabletLink>
</DataCell>
</tr>
);
});
};
const streams = getStreams(data);

return (
<div>
Expand All @@ -131,26 +65,24 @@ export const Workflow = () => {
</span>
</div>
</WorkspaceHeader>

<ContentContainer>
{/* 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;
}
<TabContainer>
<Tab text="Streams" to={`${url}/streams`} count={streams.length} />
<Tab text="JSON" to={`${url}/json`} />
</TabContainer>

<Switch>
<Route path={`${path}/streams`}>
<WorkflowStreams clusterID={clusterID} keyspace={keyspace} name={name} />
</Route>

<Route path={`${path}/json`}>
<Code code={JSON.stringify(data, null, 2)} />
</Route>

return (
<div className={style.streamTable} key={streamState}>
<DataTable
columns={COLUMNS}
data={streamsByState[streamState]}
// TODO(doeg): make pagination optional in DataTable https://github.com/vitessio/vitess/projects/12#card-60810231
pageSize={1000}
renderRows={renderRows}
title={streamState}
/>
</div>
);
})}
<Redirect exact from={path} to={`${path}/streams`} />
</Switch>
</ContentContainer>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Copyright 2021 The Vitess Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

.streamTable {
margin: 24px 0;
}
131 changes: 131 additions & 0 deletions web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* Copyright 2021 The Vitess Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { orderBy, groupBy } from 'lodash-es';
import React, { useMemo } from 'react';
import { Link } from 'react-router-dom';

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

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 { KeyspaceLink } from '../../links/KeyspaceLink';
import { TabletLink } from '../../links/TabletLink';
import { StreamStatePip } from '../../pips/StreamStatePip';

interface Props {
clusterID: string;
keyspace: string;
name: string;
}

const COLUMNS = ['Stream', 'Source', 'Target', 'Tablet'];

export const WorkflowStreams = ({ clusterID, keyspace, name }: Props) => {
const { data } = useWorkflow({ clusterID, keyspace, name }, { refetchInterval: 1000 });

const streams = useMemo(() => {
const rows = getStreams(data).map((stream) => ({
key: formatStreamKey(stream),
...stream,
}));

return orderBy(rows, 'streamKey');
}, [data]);

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 source = getStreamSource(row);
const target = getStreamTarget(row, keyspace);

return (
<tr key={row.key}>
<DataCell>
<StreamStatePip state={row.state} />{' '}
<Link className="font-weight-bold" to={href}>
{row.key}
</Link>
<div className="font-size-small text-color-secondary">
Updated {formatDateTime(row.time_updated?.seconds)}
</div>
</DataCell>
<DataCell>
{source ? (
<KeyspaceLink
clusterID={clusterID}
name={row.binlog_source?.keyspace}
shard={row.binlog_source?.shard}
>
{source}
</KeyspaceLink>
) : (
<span className="text-color-secondary">N/A</span>
)}
</DataCell>
<DataCell>
{target ? (
<KeyspaceLink clusterID={clusterID} name={keyspace} shard={row.shard}>
{source}
</KeyspaceLink>
) : (
<span className="text-color-secondary">N/A</span>
)}
</DataCell>
<DataCell>
<TabletLink alias={formatAlias(row.tablet)} clusterID={clusterID}>
{formatAlias(row.tablet)}
</TabletLink>
</DataCell>
</tr>
);
});
};

return (
<div>
{/* 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 (
<div className={style.streamTable} key={streamState}>
<DataTable
columns={COLUMNS}
data={streamsByState[streamState]}
// TODO(doeg): make pagination optional in DataTable https://github.com/vitessio/vitess/projects/12#card-60810231
pageSize={1000}
renderRows={renderRows}
title={streamState}
/>
</div>
);
})}
</div>
);
};

0 comments on commit a1e0047

Please sign in to comment.