diff --git a/web/vtadmin/src/components/NavRail.tsx b/web/vtadmin/src/components/NavRail.tsx
index 9863ec5d547..0dc401da6e1 100644
--- a/web/vtadmin/src/components/NavRail.tsx
+++ b/web/vtadmin/src/components/NavRail.tsx
@@ -18,17 +18,20 @@ 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, useWorkflows } from '../hooks/api';
+import { useClusters, useGates, useKeyspaces, useSchemas, useTablets, useWorkflows } from '../hooks/api';
import { Icon, Icons } from './Icon';
+import { getTableDefinitions } from '../util/tableDefinitions';
export const NavRail = () => {
const { data: clusters = [] } = useClusters();
const { data: keyspaces = [] } = useKeyspaces();
const { data: gates = [] } = useGates();
- const { data: schemas = [] } = useTableDefinitions();
+ const { data: schemas = [] } = useSchemas();
const { data: tablets = [] } = useTablets();
const { data: workflows = [] } = useWorkflows();
+ const tds = React.useMemo(() => getTableDefinitions(schemas), [schemas]);
+
return (
@@ -57,7 +60,7 @@ export const NavRail = () => {
-
+
diff --git a/web/vtadmin/src/components/routes/Schemas.tsx b/web/vtadmin/src/components/routes/Schemas.tsx
index 82691ac71e7..583944f2709 100644
--- a/web/vtadmin/src/components/routes/Schemas.tsx
+++ b/web/vtadmin/src/components/routes/Schemas.tsx
@@ -17,9 +17,10 @@ import { orderBy } from 'lodash-es';
import * as React from 'react';
import { Link } from 'react-router-dom';
-import { useTableDefinitions } from '../../hooks/api';
+import { useSchemas } from '../../hooks/api';
import { useDocumentTitle } from '../../hooks/useDocumentTitle';
import { filterNouns } from '../../util/filterNouns';
+import { getTableDefinitions } from '../../util/tableDefinitions';
import { Button } from '../Button';
import { DataCell } from '../dataTable/DataCell';
import { DataTable } from '../dataTable/DataTable';
@@ -30,11 +31,13 @@ import style from './Schemas.module.scss';
export const Schemas = () => {
useDocumentTitle('Schemas');
- const { data = [] } = useTableDefinitions();
+ const { data = [] } = useSchemas();
const [filter, setFilter] = React.useState('');
const filteredData = React.useMemo(() => {
- const mapped = data.map((d) => ({
+ const tableDefinitions = getTableDefinitions(data);
+
+ const mapped = tableDefinitions.map((d) => ({
cluster: d.cluster?.name,
clusterID: d.cluster?.id,
keyspace: d.keyspace,
diff --git a/web/vtadmin/src/hooks/api.ts b/web/vtadmin/src/hooks/api.ts
index b49b3317eae..1cc4f2d4fc6 100644
--- a/web/vtadmin/src/hooks/api.ts
+++ b/web/vtadmin/src/hooks/api.ts
@@ -86,44 +86,6 @@ export const useWorkflows = (...args: Parameters) =
return { data: workflows, ...query };
};
-export interface TableDefinition {
- cluster?: pb.Schema['cluster'];
- keyspace?: pb.Schema['keyspace'];
- // The [0] index is a typescript quirk to infer the type of
- // an entry in an array, and therefore the type of ALL entries
- // in the array (not just the first one).
- tableDefinition?: pb.Schema['table_definitions'][0];
-}
-
-/**
- * useTableDefinitions is a helper hook for when a flattened list
- * of table definitions (across all keyspaces and clusters) is required,
- * instead of the default vtadmin-api/Vitess grouping of schemas by keyspace.
- *
- * Under the hood, this calls the useSchemas hook and therefore uses
- * the same query cache.
- */
-export const useTableDefinitions = (...args: Parameters) => {
- const { data, ...query } = useSchemas(...args);
-
- if (!Array.isArray(data)) {
- return { data, ...query };
- }
-
- const tds = data.reduce((acc: TableDefinition[], schema: pb.Schema) => {
- (schema.table_definitions || []).forEach((td) => {
- acc.push({
- cluster: schema.cluster,
- keyspace: schema.keyspace,
- tableDefinition: td,
- });
- });
- return acc;
- }, []);
-
- return { ...query, data: tds };
-};
-
/**
* useSchema is a query hook that fetches a single schema for the given parameters.
*/
diff --git a/web/vtadmin/src/util/tableDefinitions.test.ts b/web/vtadmin/src/util/tableDefinitions.test.ts
new file mode 100644
index 00000000000..ebf12b86785
--- /dev/null
+++ b/web/vtadmin/src/util/tableDefinitions.test.ts
@@ -0,0 +1,132 @@
+/**
+ * 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 { vtadmin as pb } from '../proto/vtadmin';
+import { getTableDefinitions, TableDefinition } from './tableDefinitions';
+
+describe('getTableDefinitions', () => {
+ const tests: {
+ name: string;
+ input: pb.Schema[] | null | undefined;
+ expected: TableDefinition[];
+ }[] = [
+ {
+ name: 'handles empty arrays',
+ input: [],
+ expected: [],
+ },
+ {
+ name: 'handles undefined input',
+ input: undefined,
+ expected: [],
+ },
+ {
+ name: 'handles null input',
+ input: null,
+ expected: [],
+ },
+ {
+ name: 'extracts table definitions and sizes',
+ input: [
+ pb.Schema.create({
+ cluster: { id: 'c1', name: 'cluster1' },
+ keyspace: 'fauna',
+ table_definitions: [{ name: 'cats' }, { name: 'dogs' }],
+ table_sizes: {
+ cats: { row_count: 1234, data_length: 4321 },
+ dogs: { row_count: 5678, data_length: 8765 },
+ },
+ }),
+ pb.Schema.create({
+ cluster: { id: 'c2', name: 'cluster2' },
+ keyspace: 'flora',
+ table_definitions: [{ name: 'trees' }, { name: 'flowers' }],
+ table_sizes: {
+ flowers: { row_count: 1234, data_length: 4321 },
+ trees: { row_count: 5678, data_length: 8765 },
+ },
+ }),
+ ],
+ expected: [
+ {
+ cluster: { id: 'c1', name: 'cluster1' },
+ keyspace: 'fauna',
+ tableDefinition: { name: 'cats' },
+ tableSize: { row_count: 1234, data_length: 4321 },
+ },
+ {
+ cluster: { id: 'c1', name: 'cluster1' },
+ keyspace: 'fauna',
+ tableDefinition: { name: 'dogs' },
+ tableSize: { row_count: 5678, data_length: 8765 },
+ },
+ {
+ cluster: { id: 'c2', name: 'cluster2' },
+ keyspace: 'flora',
+ tableDefinition: { name: 'trees' },
+ tableSize: { row_count: 5678, data_length: 8765 },
+ },
+ {
+ cluster: { id: 'c2', name: 'cluster2' },
+ keyspace: 'flora',
+ tableDefinition: { name: 'flowers' },
+ tableSize: { row_count: 1234, data_length: 4321 },
+ },
+ ],
+ },
+ {
+ name: 'handles when a table has a definition but no defined size',
+ input: [
+ pb.Schema.create({
+ cluster: { id: 'c1', name: 'cluster1' },
+ keyspace: 'fauna',
+ table_definitions: [{ name: 'cats' }],
+ }),
+ ],
+ expected: [
+ {
+ cluster: { id: 'c1', name: 'cluster1' },
+ keyspace: 'fauna',
+ tableDefinition: { name: 'cats' },
+ },
+ ],
+ },
+ {
+ name: 'handles when a table defines sizes but not a definition',
+ input: [
+ pb.Schema.create({
+ cluster: { id: 'c1', name: 'cluster1' },
+ keyspace: 'fauna',
+ table_sizes: {
+ cats: { row_count: 1234, data_length: 4321 },
+ },
+ }),
+ ],
+ expected: [
+ {
+ cluster: { id: 'c1', name: 'cluster1' },
+ keyspace: 'fauna',
+ tableDefinition: { name: 'cats' },
+ tableSize: { row_count: 1234, data_length: 4321 },
+ },
+ ],
+ },
+ ];
+
+ test.each(tests.map(Object.values))('%s', (name: string, input: pb.Schema[], expected: TableDefinition[]) => {
+ const result = getTableDefinitions(input);
+ expect(result).toEqual(expected);
+ });
+});
diff --git a/web/vtadmin/src/util/tableDefinitions.ts b/web/vtadmin/src/util/tableDefinitions.ts
new file mode 100644
index 00000000000..ba367e25f54
--- /dev/null
+++ b/web/vtadmin/src/util/tableDefinitions.ts
@@ -0,0 +1,61 @@
+/**
+ * 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 { vtadmin as pb } from '../proto/vtadmin';
+
+export interface TableDefinition {
+ cluster?: pb.Schema['cluster'];
+ keyspace?: pb.Schema['keyspace'];
+ // The [0] index is a typescript quirk to infer the type of
+ // an entry in an array, and therefore the type of ALL entries
+ // in the array (not just the first one).
+ tableDefinition?: pb.Schema['table_definitions'][0];
+ tableSize?: pb.Schema['table_sizes'][0];
+}
+
+/**
+ * getTableDefinitions is a helper function for transforming an array of Schemas
+ * into a flat array of table definitions.
+ */
+export const getTableDefinitions = (schemas: pb.Schema[] | null | undefined): TableDefinition[] => {
+ return (schemas || []).reduce((acc: TableDefinition[], schema: pb.Schema) => {
+ // Index table definitions in this Schema by name, since we necessarily loop twice
+ const sts: { [tableName: string]: TableDefinition } = {};
+
+ (schema.table_definitions || []).forEach((td) => {
+ if (!td.name) return;
+ sts[td.name] = {
+ cluster: schema.cluster,
+ keyspace: schema.keyspace,
+ tableDefinition: td,
+ };
+ });
+
+ Object.entries(schema.table_sizes || {}).forEach(([tableName, tableSize]) => {
+ // Include tables that have size/rows defined but do not have a table definition.
+ if (!(tableName in sts)) {
+ sts[tableName] = {
+ cluster: schema.cluster,
+ keyspace: schema.keyspace,
+ tableDefinition: { name: tableName },
+ };
+ }
+
+ sts[tableName].tableSize = tableSize;
+ });
+
+ return acc.concat(Object.values(sts));
+ }, []);
+};