Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[vtadmin-web] Add hooks + skeleton view for workflows #7762

Merged
merged 4 commits into from
Mar 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions web/vtadmin/src/api/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,12 @@ export const fetchTablets = async () =>
return pb.Tablet.create(e);
},
});

export const fetchWorkflows = async () => {
const { result } = await vtfetch(`/api/workflows`);

const err = pb.GetWorkflowsResponse.verify(result);
if (err) throw Error(err);

return pb.GetWorkflowsResponse.create(result);
};
5 changes: 5 additions & 0 deletions web/vtadmin/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { Gates } from './routes/Gates';
import { Keyspaces } from './routes/Keyspaces';
import { Schemas } from './routes/Schemas';
import { Schema } from './routes/Schema';
import { Workflows } from './routes/Workflows';

export const App = () => {
return (
Expand Down Expand Up @@ -61,6 +62,10 @@ export const App = () => {
<Tablets />
</Route>

<Route path="/workflows">
<Workflows />
</Route>

<Route path="/debug">
<Debug />
</Route>
Expand Down
5 changes: 3 additions & 2 deletions web/vtadmin/src/components/NavRail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { Link, NavLink } from 'react-router-dom';

import style from './NavRail.module.scss';
import logo from '../img/vitess-icon-color.svg';
import { useClusters, useGates, useKeyspaces, useTableDefinitions, useTablets } from '../hooks/api';
import { useClusters, useGates, useKeyspaces, useTableDefinitions, useTablets, useWorkflows } from '../hooks/api';
import { Icon, Icons } from './Icon';

export const NavRail = () => {
Expand All @@ -27,6 +27,7 @@ export const NavRail = () => {
const { data: gates = [] } = useGates();
const { data: schemas = [] } = useTableDefinitions();
const { data: tablets = [] } = useTablets();
const { data: workflows = [] } = useWorkflows();

return (
<div className={style.container}>
Expand All @@ -40,7 +41,7 @@ export const NavRail = () => {
<NavRailLink icon={Icons.chart} text="Dashboard" to="/dashboard" count={0} />
</li>
<li>
<NavRailLink icon={Icons.wrench} text="Workflows" to="/workflows" count={0} />
<NavRailLink icon={Icons.wrench} text="Workflows" to="/workflows" count={workflows.length} />
</li>
</ul>

Expand Down
54 changes: 54 additions & 0 deletions web/vtadmin/src/components/routes/Workflows.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* 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 } from 'lodash-es';
import * as React from 'react';

import { useWorkflows } from '../../hooks/api';
import { useDocumentTitle } from '../../hooks/useDocumentTitle';
import { DataTable } from '../dataTable/DataTable';

export const Workflows = () => {
useDocumentTitle('Workflows');
const { data } = useWorkflows();

const sortedData = React.useMemo(
() => orderBy(data, ['workflow.name', 'cluster.name', 'workflow.source.keyspace', 'workflow.target.keyspace']),
[data]
);

const renderRows = (rows: typeof sortedData) =>
rows.map(({ cluster, workflow }, idx) => {
return (
<tr key={idx}>
<td>{workflow?.name}</td>
<td>{cluster?.name}</td>
<td>{workflow?.source?.keyspace || <span className="text-color-secondary">n/a</span>}</td>
<td>{workflow?.target?.keyspace || <span className="text-color-secondary">n/a</span>}</td>
</tr>
);
});

return (
<div className="max-width-content">
<h1>Workflows</h1>
<DataTable
columns={['Workflow', 'Cluster', 'Source', 'Target']}
data={sortedData}
renderRows={renderRows}
/>
</div>
);
};
81 changes: 81 additions & 0 deletions web/vtadmin/src/hooks/api.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* 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 React from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
import { renderHook } from '@testing-library/react-hooks';

import * as api from './api';
import * as httpAPI from '../api/http';
import { vtadmin as pb } from '../proto/vtadmin';

jest.mock('../api/http');

describe('useWorkflows', () => {
const tests: {
name: string;
response: pb.GetWorkflowsResponse | undefined;
expected: pb.Workflow[] | undefined;
}[] = [
{
name: 'returns a flat list of workflows',
response: pb.GetWorkflowsResponse.create({
workflows_by_cluster: {
east: {
workflows: [
{
workflow: { name: 'one-goes-east' },
Copy link
Contributor

Choose a reason for hiding this comment

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

🤌 (cmon github, how is this not in your emoji set yet???)

},
],
},
west: {
workflows: [
{
workflow: { name: 'one-goes-west' },
},
],
},
},
}),
expected: [
pb.Workflow.create({ workflow: { name: 'one-goes-east' } }),
pb.Workflow.create({ workflow: { name: 'one-goes-west' } }),
],
},
];

const queryClient = new QueryClient();
const wrapper: React.FunctionComponent = ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);

test.each(tests.map(Object.values))(
'%s',
async (name: string, response: pb.GetWorkflowsResponse | undefined, expected: pb.Workflow[] | undefined) => {
(httpAPI.fetchWorkflows as any).mockResolvedValueOnce(response);

const { result, waitFor } = renderHook(() => api.useWorkflows(), { wrapper });

// Check that our query helper handles when the query is still in flight
expect(result.current.data).toBeUndefined();

// "Wait" for the underlying fetch request to resolve (scare-quotes because,
// in practice, we're not "waiting" for anything since the response is mocked.)
await waitFor(() => result.current.isSuccess);

expect(result.current.data).toEqual(expected);
}
);
});
45 changes: 45 additions & 0 deletions web/vtadmin/src/hooks/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/**
* 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 { useQuery, useQueryClient, UseQueryOptions } from 'react-query';
import {
fetchClusters,
Expand All @@ -7,6 +22,7 @@ import {
FetchSchemaParams,
fetchSchemas,
fetchTablets,
fetchWorkflows,
} from '../api/http';
import { vtadmin as pb } from '../proto/vtadmin';

Expand Down Expand Up @@ -40,6 +56,35 @@ export const useSchemas = (options?: UseQueryOptions<pb.Schema[], Error> | undef
export const useTablets = (options?: UseQueryOptions<pb.Tablet[], Error> | undefined) =>
useQuery(['tablets'], fetchTablets, options);

/**
* useWorkflowsResponse is a query hook that fetches all workflows (by cluster) across every cluster.
*/
export const useWorkflowsResponse = (options?: UseQueryOptions<pb.GetWorkflowsResponse, Error> | undefined) =>
useQuery(['workflows'], fetchWorkflows, options);

/**
* useWorkflows is a helper hook for when a flattened list of workflows
* (across all clusters) is required. Under the hood, this call uses the
* useWorkflowsResponse hook and therefore uses the same query cache.
*/
export const useWorkflows = (...args: Parameters<typeof useWorkflowsResponse>) => {
const { data, ...query } = useWorkflowsResponse(...args);

if (!data?.workflows_by_cluster) {
return { data: undefined, ...query };
}

const workflows = Object.entries(data.workflows_by_cluster).reduce(
(acc: pb.Workflow[], [clusterID, { workflows }]) => {
(workflows || []).forEach((w) => acc.push(pb.Workflow.create(w)));
return acc;
},
[]
);

return { data: workflows, ...query };
};

export interface TableDefinition {
cluster?: pb.Schema['cluster'];
keyspace?: pb.Schema['keyspace'];
Expand Down