Skip to content

Commit

Permalink
cluster-ui: add grants view to v2 db details
Browse files Browse the repository at this point in the history
This commit adds the db level grants table to the
grants tab in the v2 db details page.

A new hook, useDatabaseGrants is created to wrap the
request with useSWR. For now we simply get all grants
on the db on the page, showing 20 results per page
on the table.

Epic: CRDB-37558
Part of: #131211

Release note (ui change): The grants table in the db
details page (e.g. when clicking a db in the databases list)
will now show the db level grants. Previously it showed grants
per table in the db.
  • Loading branch information
xinhaoz committed Sep 30, 2024
1 parent 946ebda commit be412eb
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 6 deletions.
77 changes: 77 additions & 0 deletions pkg/ui/workspaces/cluster-ui/src/api/databases/grantsApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2024 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

import useSWRImmutable from "swr/immutable";

import { fetchDataJSON } from "../fetchData";
import {
APIV2ResponseWithPaginationState,
SimplePaginationState,
} from "../types";

export type DatabaseGrant = {
grantee: string;
privilege: string;
};

export enum GrantsSortOptions {
GRANTEE = "grantee",
PRIVILEGE = "privilege",
}

type DatabaseGrantsRequest = {
dbId: number;
sortBy?: GrantsSortOptions;
sortOrder?: "asc" | "desc";
pagination?: SimplePaginationState;
};

export type DatabaseGrantsResponse = APIV2ResponseWithPaginationState<
DatabaseGrant[]
>;

const createDbGrantsPath = (req: DatabaseGrantsRequest): string => {
const { dbId, pagination, sortBy, sortOrder } = req;
const urlParams = new URLSearchParams();
if (pagination?.pageNum) {
urlParams.append("pageNum", pagination.pageNum.toString());
}
if (pagination?.pageSize) {
urlParams.append("pageSize", pagination.pageSize.toString());
}
if (sortBy) {
urlParams.append("sortBy", sortBy);
}
if (sortOrder) {
urlParams.append("sortOrder", sortOrder);
}
return `api/v2/grants/databases/${dbId}/?` + urlParams.toString();
};

const fetchDbGrants = (
req: DatabaseGrantsRequest,
): Promise<DatabaseGrantsResponse> => {
const path = createDbGrantsPath(req);
return fetchDataJSON(path);
};

export const useDatabaseGrantsImmutable = (req: DatabaseGrantsRequest) => {
const { data, isLoading, error } = useSWRImmutable(
createDbGrantsPath(req),
() => fetchDbGrants(req),
);

return {
databaseGrants: data?.results,
pagination: data?.pagination_info,
isLoading,
error: error,
};
};
118 changes: 118 additions & 0 deletions pkg/ui/workspaces/cluster-ui/src/databaseDetailsV2/dbGrantsView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2024 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

import React, { useMemo, useState } from "react";

import {
GrantsSortOptions,
useDatabaseGrantsImmutable,
} from "src/api/databases/grantsApi";
import { useRouteParams } from "src/hooks/useRouteParams";
import { PageSection } from "src/layouts";
import PageCount from "src/sharedFromCloud/pageCount";
import {
Table,
TableChangeFn,
TableColumnProps,
} from "src/sharedFromCloud/table";

// This type is used by data source for the table.
type GrantsByUser = {
key: string;
grantee: string;
privileges: string[];
};

const COLUMNS: (TableColumnProps<GrantsByUser> & {
sortKey: GrantsSortOptions;
})[] = [
{
title: "Grantee",
sorter: (a, b) => a.grantee.localeCompare(b.grantee),
sortKey: GrantsSortOptions.GRANTEE,
render: grant => grant.grantee,
},
{
title: "Privileges",
sortKey: GrantsSortOptions.PRIVILEGE,
render: grant => grant.privileges.join(", "),
},
];

const pageSize = 20;

export const DbGrantsView: React.FC = () => {
const { dbID } = useRouteParams();
const [currentPage, setCurrentPage] = useState(1);

const {
databaseGrants,
pagination: paginationRes,
isLoading,
error,
} = useDatabaseGrantsImmutable({
dbId: parseInt(dbID, 10),
pagination: {
pageSize: 0, // Get all.
pageNum: 0,
},
});

const dataWithKey: GrantsByUser[] = useMemo(() => {
if (!databaseGrants) {
return [];
}
const grantsByUser = {} as Record<string, string[]>;
databaseGrants.forEach(grant => {
if (!grantsByUser[grant.grantee]) {
grantsByUser[grant.grantee] = [];
}
grantsByUser[grant.grantee].push(grant.privilege);
});

return Object.entries(grantsByUser).map(([grantee, privileges]) => ({
key: grantee,
grantee,
privileges,
}));
}, [databaseGrants]);

const onTableChange: TableChangeFn<GrantsByUser> = pagination => {
if (pagination.current) {
setCurrentPage(pagination.current);
}
};

return (
<PageSection heading={"Grants"}>
<PageCount
page={currentPage}
pageSize={pageSize}
total={paginationRes?.total_results ?? 0}
entity="grants"
/>
<Table
error={error}
loading={isLoading}
dataSource={dataWithKey ?? []}
columns={COLUMNS}
pagination={{
size: "small",
current: currentPage,
pageSize,
showSizeChanger: false,
position: ["bottomCenter"],
total: paginationRes?.total_results,
}}
onChange={onTableChange}
/>
</PageSection>
);
};
29 changes: 24 additions & 5 deletions pkg/ui/workspaces/cluster-ui/src/databaseDetailsV2/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,39 @@
// licenses/APL.txt.

import { Tabs } from "antd";
import React, { useState } from "react";
import React from "react";
import { useHistory, useLocation } from "react-router";

import { commonStyles } from "src/common";
import { PageLayout } from "src/layouts";
import { PageHeader } from "src/sharedFromCloud/pageHeader";

import { queryByName, tabAttr } from "../util";

import { DbGrantsView } from "./dbGrantsView";
import { TablesPageV2 } from "./tablesView";

enum TabKeys {
TABLES = "tables",
GRANTS = "grants",
}
export const DatabaseDetailsPageV2 = () => {
const [currentTab, setCurrentTab] = useState(TabKeys.TABLES);
const history = useHistory();
const location = useLocation();
const tab = queryByName(location, tabAttr) ?? TabKeys.TABLES;

const onTabChange = (key: string) => {
if (tab === key) {
return;
}
const searchParams = new URLSearchParams();
if (key) {
searchParams.set(tabAttr, key);
}
history.push({
search: searchParams.toString(),
});
};

// TODO (xinhaoz) #131119 - Populate db name here.
const tabItems = [
Expand All @@ -34,7 +53,7 @@ export const DatabaseDetailsPageV2 = () => {
{
key: TabKeys.GRANTS,
label: "Grants",
children: <div />,
children: <DbGrantsView />,
},
];

Expand All @@ -44,8 +63,8 @@ export const DatabaseDetailsPageV2 = () => {
<Tabs
defaultActiveKey={TabKeys.TABLES}
className={commonStyles("cockroach--tabs")}
onChange={setCurrentTab}
activeKey={currentTab}
onChange={onTabChange}
activeKey={tab}
destroyInactiveTabPane
items={tabItems}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
} from "src/sharedFromCloud/table";
import useTable, { TableParams } from "src/sharedFromCloud/useTable";
import { StoreID } from "src/types/clusterTypes";
import { Bytes, EncodeDatabaseTableUri } from "src/util";
import { Bytes, EncodeDatabaseTableUri, tabAttr } from "src/util";

import { TableColName } from "./constants";
import { TableRow } from "./types";
Expand Down Expand Up @@ -150,9 +150,12 @@ const initialParams: TableParams = {
},
};

const ignoreParams = [tabAttr];

export const TablesPageV2 = () => {
const { params, setFilters, setSort, setSearch, setPagination } = useTable({
initial: initialParams,
paramsToIgnore: ignoreParams,
});

// Get db id from the URL.
Expand Down

0 comments on commit be412eb

Please sign in to comment.