From ad867ac45c8a18de0a2c75277ff84adea2068e96 Mon Sep 17 00:00:00 2001 From: Noble Mittal Date: Wed, 18 Sep 2024 12:01:25 +0530 Subject: [PATCH] VTAdmin(web): Init transactions screen Signed-off-by: Noble Mittal --- web/vtadmin/src/api/http.ts | 14 +++ web/vtadmin/src/components/App.tsx | 5 + web/vtadmin/src/components/NavRail.tsx | 3 + .../src/components/routes/Transactions.tsx | 113 ++++++++++++++++++ web/vtadmin/src/hooks/api.ts | 12 ++ web/vtadmin/src/util/transactions.ts | 28 +++++ 6 files changed, 175 insertions(+) create mode 100644 web/vtadmin/src/components/routes/Transactions.tsx create mode 100644 web/vtadmin/src/util/transactions.ts diff --git a/web/vtadmin/src/api/http.ts b/web/vtadmin/src/api/http.ts index 030455c652d..c23ad131332 100644 --- a/web/vtadmin/src/api/http.ts +++ b/web/vtadmin/src/api/http.ts @@ -421,6 +421,20 @@ export const fetchVSchema = async ({ clusterID, keyspace }: FetchVSchemaParams) return pb.VSchema.create(result); }; +export interface FetchTransactionsParams { + clusterID: string; + keyspace: string; +} + +export const fetchTransactions = async ({ clusterID, keyspace }: FetchTransactionsParams) => { + const { result } = await vtfetch(`/api/transactions/${clusterID}/${keyspace}`); + + const err = vtctldata.GetUnresolvedTransactionsResponse.verify(result); + if (err) throw Error(err); + + return vtctldata.GetUnresolvedTransactionsResponse.create(result); +}; + export const fetchWorkflows = async () => { const { result } = await vtfetch(`/api/workflows`); diff --git a/web/vtadmin/src/components/App.tsx b/web/vtadmin/src/components/App.tsx index f7821f3a4ad..bbf6f014111 100644 --- a/web/vtadmin/src/components/App.tsx +++ b/web/vtadmin/src/components/App.tsx @@ -40,6 +40,7 @@ import { isReadOnlyMode } from '../util/env'; import { CreateKeyspace } from './routes/createKeyspace/CreateKeyspace'; import { Topology } from './routes/topology/Topology'; import { ClusterTopology } from './routes/topology/ClusterTopology'; +import { Transactions } from './routes/Transactions'; export const App = () => { return ( @@ -117,6 +118,10 @@ export const App = () => { + + + + diff --git a/web/vtadmin/src/components/NavRail.tsx b/web/vtadmin/src/components/NavRail.tsx index e2897395760..9f9e1bf1681 100644 --- a/web/vtadmin/src/components/NavRail.tsx +++ b/web/vtadmin/src/components/NavRail.tsx @@ -65,6 +65,9 @@ export const NavRail = () => {
    +
  • + +
  • diff --git a/web/vtadmin/src/components/routes/Transactions.tsx b/web/vtadmin/src/components/routes/Transactions.tsx new file mode 100644 index 00000000000..dc6742ccac8 --- /dev/null +++ b/web/vtadmin/src/components/routes/Transactions.tsx @@ -0,0 +1,113 @@ +/** + * Copyright 2024 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 { useState } from 'react'; +import { useKeyspaces, useTransactions } from '../../hooks/api'; +import { DataCell } from '../dataTable/DataCell'; +import { DataTable } from '../dataTable/DataTable'; +import { ContentContainer } from '../layout/ContentContainer'; +import { WorkspaceHeader } from '../layout/WorkspaceHeader'; +import { WorkspaceTitle } from '../layout/WorkspaceTitle'; +import { QueryLoadingPlaceholder } from '../placeholders/QueryLoadingPlaceholder'; +import { useDocumentTitle } from '../../hooks/useDocumentTitle'; +import { query } from '../../proto/vtadmin'; +import { Select } from '../inputs/Select'; +import { FetchTransactionsParams } from '../../api/http'; +import { formatTransactionState } from '../../util/transactions'; +import { TABLET_TYPES } from '../../util/tablets'; +import { ShardLink } from '../links/ShardLink'; + +const COLUMNS = ['ID', 'State', 'Participants']; + +export const Transactions = () => { + useDocumentTitle('Transactions'); + + const keyspacesQuery = useKeyspaces(); + + const { data: keyspaces = [] } = keyspacesQuery; + + const [params, setParams] = useState({ clusterID: '', keyspace: '' }); + + const selectedKeyspace = keyspaces.find( + (ks) => ks.keyspace?.name === params.keyspace && ks.cluster?.id === params.clusterID + ); + + const transactionsQuery = useTransactions(params, { + enabled: !!params.keyspace, + }); + + const transactions = (transactionsQuery.data && transactionsQuery.data.transactions) || []; + + const renderRows = (rows: query.ITransactionMetadata[]) => { + return rows.map((row) => { + return ( + + +
    {row.dtid}
    +
    + +
    {formatTransactionState(row)}
    +
    + + {row.participants?.map((participant) => { + const shard = `${participant.keyspace}/${participant.shard}`; + const tabletType = TABLET_TYPES[participant.tablet_type!]; + return ( +
    + + {shard} + + {tabletType} +
    + ); + })} +
    + + ); + }); + }; + + return ( +
    + + Transactions + + + +