Skip to content

Commit

Permalink
ui: add index stats to table details page
Browse files Browse the repository at this point in the history
Resolves cockroachdb#67647, cockroachdb#72842

Previously, there was no way to view and clear index usage stats from
the frontend db console. This commit adds Index Stats tables for each
table on the Table Detail pages, allowing users to view index names,
total reads, and last used statistics. This commit also adds the
functionality of clearing all index stats as a button on the Index Stats
tables.

Release note (ui change): Add index stats table and button to clear
index usage stats on the Table Details page for each table.
  • Loading branch information
lindseyjin committed Nov 19, 2021
1 parent 265d1ba commit 6e973eb
Show file tree
Hide file tree
Showing 12 changed files with 544 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,58 @@
fill: $colors--primary-text;
}
}

.index-stats {
&__summary-card {
width: fit-content;
padding: 0;
}

&__header {
align-items: baseline;
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 24px 24px 0;
}

&__clear-info {
display: flex;
flex-direction: row;
}

&__last-cleared {
color: "#475872";
margin-right: 6px;
}

&__clear-btn {
border-bottom: none;
text-decoration: none;
}

&-table {
&__col {
&-indexes {
width: 30em;
}
&-last-used {
width: 30em;
}
}
}
}


.icon {
&--s {
height: 16px;
width: 16px;
margin-right: 10px;
}

&--primary {
fill: $colors--primary-text;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
randomTablePrivilege,
} from "src/storybook/fixtures";
import { DatabaseTablePage, DatabaseTablePageProps } from "./databaseTablePage";
import moment from "moment";

const withLoadingIndicator: DatabaseTablePageProps = {
databaseName: randomName(),
Expand All @@ -37,8 +38,23 @@ const withLoadingIndicator: DatabaseTablePageProps = {
sizeInBytes: 0,
rangeCount: 0,
},
indexStats: {
loading: true,
loaded: false,
stats: [
{
totalReads: 0,
lastUsedTime: moment("2021-11-10T16:29:00Z"),
lastUsedString: "Nov 10, 2021 at 4:29 PM",
indexName: "primary",
},
],
lastReset: "Nov 12, 2021 at 8:14 PM (UTC)",
},
refreshTableDetails: () => {},
refreshTableStats: () => {},
refreshIndexStats: () => {},
resetIndexUsageStats: () => {},
};

const name = randomName();
Expand Down Expand Up @@ -80,8 +96,35 @@ const withData: DatabaseTablePageProps = {
nodesByRegionString:
"gcp-europe-west1(n8), gcp-us-east1(n1), gcp-us-west1(n6)",
},
indexStats: {
loading: false,
loaded: false,
stats: [
{
totalReads: 0,
lastUsedTime: moment("2021-01-11T11:29:00Z"),
lastUsedString: "Jan 11, 2021 at 11:29 AM",
indexName: "primary",
},
{
totalReads: 3,
lastUsedTime: moment("2021-11-10T16:29:00Z"),
lastUsedString: "Nov 10, 2021 at 4:29 PM",
indexName: "primary",
},
{
totalReads: 2,
lastUsedTime: moment("2021-09-04T13:55:00Z"),
lastUsedString: "Sep 04, 2021 at 12:55 PM",
indexName: "secondary",
},
],
lastReset: "Oct 22, 2021 at 9:21 AM (UTC)",
},
refreshTableDetails: () => {},
refreshTableStats: () => {},
refreshIndexStats: () => {},
resetIndexUsageStats: () => {},
};

storiesOf("Database Table Page", module)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Col, Row, Tabs } from "antd";
import classNames from "classnames/bind";
import _ from "lodash";
import { Tooltip } from "antd";
import { Heading } from "@cockroachlabs/ui-components";

import { Breadcrumbs } from "src/breadcrumbs";
import { CaretRight } from "src/icon/caretRight";
Expand All @@ -25,6 +26,8 @@ import * as format from "src/util/format";
import styles from "./databaseTablePage.module.scss";
import { commonStyles } from "src/common";
import { baseHeadingClasses } from "src/transactionsPage/transactionsPageClasses";
import { Moment } from "moment";
import { Search as IndexIcon } from "@cockroachlabs/icons";
const cx = classNames.bind(styles);

const { TabPane } = Tabs;
Expand Down Expand Up @@ -58,12 +61,24 @@ const { TabPane } = Tabs;
// rangeCount: number;
// nodesByRegionString: string;
// };
// indexUsageStats: { // DatabaseTablePageIndexStats
// loading: boolean;
// loaded: boolean;
// stats: {
// indexName: string;
// totalReads: number;
// lastUsedTime: Moment;
// lastUsedString: string;
// }[];
// lastReset: string;
// };
// }
export interface DatabaseTablePageData {
databaseName: string;
name: string;
details: DatabaseTablePageDataDetails;
stats: DatabaseTablePageDataStats;
indexStats: DatabaseTablePageIndexStats;
showNodeRegionsSection?: boolean;
}

Expand All @@ -76,6 +91,20 @@ export interface DatabaseTablePageDataDetails {
grants: Grant[];
}

export interface DatabaseTablePageIndexStats {
loading: boolean;
loaded: boolean;
stats: IndexStat[];
lastReset: string;
}

interface IndexStat {
indexName: string;
totalReads: number;
lastUsedTime: Moment;
lastUsedString: string;
}

interface Grant {
user: string;
privilege: string;
Expand All @@ -91,7 +120,9 @@ export interface DatabaseTablePageDataStats {

export interface DatabaseTablePageActions {
refreshTableDetails: (database: string, table: string) => void;
refreshTableStats: (databse: string, table: string) => void;
refreshTableStats: (database: string, table: string) => void;
refreshIndexStats?: (database: string, table: string) => void;
resetIndexUsageStats?: (database: string, table: string) => void;
refreshNodes?: () => void;
}

Expand All @@ -103,6 +134,7 @@ interface DatabaseTablePageState {
}

class DatabaseTableGrantsTable extends SortedTable<Grant> {}
class IndexUsageStatsTable extends SortedTable<IndexStat> {}

export class DatabaseTablePage extends React.Component<
DatabaseTablePageProps,
Expand Down Expand Up @@ -143,13 +175,67 @@ export class DatabaseTablePage extends React.Component<
this.props.name,
);
}

if (!this.props.indexStats.loaded && !this.props.indexStats.loading) {
return this.props.refreshIndexStats(
this.props.databaseName,
this.props.name,
);
}
}

private changeSortSetting(sortSetting: SortSetting) {
this.setState({ sortSetting });
}

private columns: ColumnDescriptor<Grant>[] = [
private indexStatsColumns: ColumnDescriptor<IndexStat>[] = [
{
name: "indexes",
title: (
<Tooltip placement="bottom" title="The index name.">
Indexes
</Tooltip>
),
className: cx("index-stats-table__col-indexes"),
cell: indexStat => (
<>
<IndexIcon className={cx("icon--s", "icon--primary")} />
{indexStat.indexName}
</>
),
sort: indexStat => indexStat.indexName,
},
{
name: "total reads",
title: (
<Tooltip
placement="bottom"
title="The total number of reads for this index."
>
Total Reads
</Tooltip>
),
cell: indexStat => indexStat.totalReads,
sort: indexStat => indexStat.totalReads,
},
{
name: "last used",
title: (
<Tooltip
placement="bottom"
title="The last time this index was used (reset or read from)."
>
Last Used (UTC)
</Tooltip>
),
className: cx("index-stats-table__col-last-used"),
cell: indexStat => indexStat.lastUsedString,
sort: indexStat => indexStat.lastUsedTime,
},
// TODO(lindseyjin): add index recommendations column
];

private grantsColumns: ColumnDescriptor<Grant>[] = [
{
name: "user",
title: (
Expand Down Expand Up @@ -247,12 +333,61 @@ export class DatabaseTablePage extends React.Component<
</SummaryCard>
</Col>
</Row>
<SummaryCard
className={cx("summary-card", "index-stats__summary-card")}
>
<Row>
<div className={cx("index-stats__header")}>
<Heading type="h5">Index Stats</Heading>
<div className={cx("index-stats__clear-info")}>
<Tooltip
placement="bottom"
title="This is the last time the indexes were reset."
>
<div
className={cx(
"index-stats__last-cleared",
"underline",
)}
>
Last cleared: {this.props.indexStats.lastReset}
</div>
</Tooltip>
<Tooltip
placement="bottom"
title="Clicking this button will clear index usage stats for all tables."
>
<div>
<a
className={cx("action", "separator")}
onClick={() =>
this.props.resetIndexUsageStats(
this.props.databaseName,
this.props.name,
)
}
>
Clear index stats
</a>
</div>
</Tooltip>
</div>
</div>
<IndexUsageStatsTable
className="index-stats-table"
data={this.props.indexStats.stats}
columns={this.indexStatsColumns}
sortSetting={this.state.sortSetting}
onChangeSortSetting={this.changeSortSetting.bind(this)}
loading={this.props.indexStats.loading}
/>
</Row>
</SummaryCard>
</TabPane>

<TabPane tab="Grants" key="grants">
<DatabaseTableGrantsTable
data={this.props.details.grants}
columns={this.columns}
columns={this.grantsColumns}
sortSetting={this.state.sortSetting}
onChangeSortSetting={this.changeSortSetting.bind(this)}
loading={this.props.details.loading}
Expand Down
16 changes: 15 additions & 1 deletion pkg/ui/workspaces/db-console/src/redux/apiReducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ export function generateTableID(db: string, table: string) {
}

export const tableRequestToID = (
req: api.TableDetailsRequestMessage | api.TableStatsRequestMessage,
req:
| api.TableDetailsRequestMessage
| api.TableStatsRequestMessage
| api.IndexStatsRequestMessage,
): string => generateTableID(req.database, req.table);

const tableDetailsReducerObj = new KeyedCachedDataReducer(
Expand All @@ -125,6 +128,15 @@ const tableStatsReducerObj = new KeyedCachedDataReducer(
);
export const refreshTableStats = tableStatsReducerObj.refresh;

const indexStatsReducerObj = new KeyedCachedDataReducer(
api.getIndexStats,
"indexStats",
tableRequestToID,
);

export const invalidateIndexStats = indexStatsReducerObj.cachedDataReducer.invalidateData;
export const refreshIndexStats = indexStatsReducerObj.refresh;

const nonTableStatsReducerObj = new CachedDataReducer(
api.getNonTableStats,
"nonTableStats",
Expand Down Expand Up @@ -325,6 +337,7 @@ export interface APIReducersState {
>;
tableDetails: KeyedCachedDataReducerState<api.TableDetailsResponseMessage>;
tableStats: KeyedCachedDataReducerState<api.TableStatsResponseMessage>;
indexStats: KeyedCachedDataReducerState<api.IndexStatsResponseMessage>;
nonTableStats: CachedDataReducerState<api.NonTableStatsResponseMessage>;
logs: CachedDataReducerState<api.LogEntriesResponseMessage>;
liveness: CachedDataReducerState<api.LivenessResponseMessage>;
Expand Down Expand Up @@ -362,6 +375,7 @@ export const apiReducersReducer = combineReducers<APIReducersState>({
databaseDetailsReducerObj.reducer,
[tableDetailsReducerObj.actionNamespace]: tableDetailsReducerObj.reducer,
[tableStatsReducerObj.actionNamespace]: tableStatsReducerObj.reducer,
[indexStatsReducerObj.actionNamespace]: indexStatsReducerObj.reducer,
[nonTableStatsReducerObj.actionNamespace]: nonTableStatsReducerObj.reducer,
[logsReducerObj.actionNamespace]: logsReducerObj.reducer,
[livenessReducerObj.actionNamespace]: livenessReducerObj.reducer,
Expand Down
12 changes: 12 additions & 0 deletions pkg/ui/workspaces/db-console/src/redux/indexUsageStats/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2021 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.

export * from "./indexUsageStatsActions";
export * from "./indexUsageStatsSagas";
Loading

0 comments on commit 6e973eb

Please sign in to comment.