diff --git a/docs/generated/http/full.md b/docs/generated/http/full.md index a7c840fa75ae..4c62e1db9484 100644 --- a/docs/generated/http/full.md +++ b/docs/generated/http/full.md @@ -4746,6 +4746,8 @@ Response object returned by TableIndexStatsResponse. | index_type | [string](#cockroach.server.serverpb.TableIndexStatsResponse-string) | | index_type is the type of the index i.e. primary, secondary. | [reserved](#support-status) | | create_statement | [string](#cockroach.server.serverpb.TableIndexStatsResponse-string) | | create_statement is the SQL statement that would re-create the current index if executed. | [reserved](#support-status) | | created_at | [google.protobuf.Timestamp](#cockroach.server.serverpb.TableIndexStatsResponse-google.protobuf.Timestamp) | | CreatedAt is an approximate timestamp at which the index was created. Note that it may not always be populated. | [reserved](#support-status) | +| index_id | [string](#cockroach.server.serverpb.TableIndexStatsResponse-string) | | index_id is the ID of the index. | [reserved](#support-status) | +| table_id | [string](#cockroach.server.serverpb.TableIndexStatsResponse-string) | | table_id is the ID of the table which the index belongs to. | [reserved](#support-status) | diff --git a/pkg/server/serverpb/status.proto b/pkg/server/serverpb/status.proto index fd94ef447cf3..e02fde997c74 100644 --- a/pkg/server/serverpb/status.proto +++ b/pkg/server/serverpb/status.proto @@ -1802,7 +1802,10 @@ message TableIndexStatsResponse { // CreatedAt is an approximate timestamp at which the index was created. // Note that it may not always be populated. google.protobuf.Timestamp created_at = 5 [(gogoproto.stdtime) = true]; - + // index_id is the ID of the index. + string index_id = 6 [(gogoproto.customname) = "IndexID"]; + // table_id is the ID of the table which the index belongs to. + string table_id = 7 [(gogoproto.customname) = "TableID"]; } repeated ExtendedCollectedIndexUsageStatistics statistics = 1; diff --git a/pkg/ui/workspaces/cluster-ui/src/api/indexDetailsApi.ts b/pkg/ui/workspaces/cluster-ui/src/api/indexDetailsApi.ts index 295b7f5a2489..8ad7f84946b6 100644 --- a/pkg/ui/workspaces/cluster-ui/src/api/indexDetailsApi.ts +++ b/pkg/ui/workspaces/cluster-ui/src/api/indexDetailsApi.ts @@ -9,7 +9,18 @@ // licenses/APL.txt. import { cockroach } from "@cockroachlabs/crdb-protobuf-client"; -import { fetchData } from "src/api"; +import { + convertStatementRawFormatToAggregatedStatistics, + executeInternalSql, + fetchData, + SqlExecutionRequest, + sqlResultsAreEmpty, + StatementRawFormat, +} from "src/api"; +import moment from "moment"; +import { TimeScale, toDateRange } from "../timeScaleDropdown"; +import { AggregateStatistics } from "../statementsTable"; +import { INTERNAL_APP_NAME_PREFIX } from "../recentExecutions/recentStatementUtils"; export type TableIndexStatsRequest = cockroach.server.serverpb.TableIndexStatsRequest; @@ -50,3 +61,70 @@ export const resetIndexStats = ( "30M", ); }; + +export type StatementsUsingIndexRequest = { + table: string; + index: string; + database: string; + start?: moment.Moment; + end?: moment.Moment; +}; + +export function StatementsListRequestFromDetails( + table: string, + index: string, + database: string, + ts: TimeScale, +): StatementsUsingIndexRequest { + if (ts === null) return { table, index, database }; + const [start, end] = toDateRange(ts); + return { table, index, database, start, end }; +} + +export function getStatementsUsingIndex({ + table, + index, + database, + start, + end, +}: StatementsUsingIndexRequest): Promise { + const args: any = [`"${table}@${index}"`]; + let placeholder = 2; + let whereClause = ""; + if (start) { + args.push(start); + whereClause = `${whereClause} AND aggregated_ts >= $${placeholder}`; + placeholder++; + } + if (end) { + args.push(end); + whereClause = `${whereClause} AND aggregated_ts <= $${placeholder}`; + placeholder++; + } + + const selectStatements = { + sql: `SELECT * FROM system.statement_statistics + WHERE $1::jsonb <@ indexes_usage + AND app_name NOT LIKE '${INTERNAL_APP_NAME_PREFIX}%' + ${whereClause};`, + arguments: args, + }; + + const req: SqlExecutionRequest = { + execute: true, + statements: [selectStatements], + database: database, + }; + + return executeInternalSql(req).then(res => { + if (res.error || sqlResultsAreEmpty(res)) { + return []; + } + + const statements: AggregateStatistics[] = []; + res.execution.txn_results[0].rows.forEach(s => { + statements.push(convertStatementRawFormatToAggregatedStatistics(s)); + }); + return statements; + }); +} diff --git a/pkg/ui/workspaces/cluster-ui/src/api/statementsApi.ts b/pkg/ui/workspaces/cluster-ui/src/api/statementsApi.ts index c70f44417827..81c65904b925 100644 --- a/pkg/ui/workspaces/cluster-ui/src/api/statementsApi.ts +++ b/pkg/ui/workspaces/cluster-ui/src/api/statementsApi.ts @@ -10,8 +10,15 @@ import { cockroach } from "@cockroachlabs/crdb-protobuf-client"; import { fetchData } from "src/api"; -import { propsToQueryString } from "src/util"; - +import { + FixFingerprintHexValue, + HexStringToInt64String, + NumericStat, + propsToQueryString, + stringToTimestamp, +} from "src/util"; +import Long from "long"; +import { AggregateStatistics } from "../statementsTable"; const STATEMENTS_PATH = "/_status/statements"; const STATEMENT_DETAILS_PATH = "/_status/stmtdetails"; @@ -64,3 +71,112 @@ export const getStatementDetails = ( "30M", ); }; + +export type StatementMetadata = { + db: string; + distsql: boolean; + failed: boolean; + fullScan: boolean; + implicitTxn: boolean; + query: string; + querySummary: string; + stmtTyp: string; + vec: boolean; +}; + +type Statistics = { + bytesRead: NumericStat; + cnt: Long; + firstAttemptCnt: Long; + idleLat: NumericStat; + indexes: string[]; + lastExecAt: string; + maxRetries: Long; + nodes: Long[]; + numRows: NumericStat; + ovhLat: NumericStat; + parseLat: NumericStat; + planGists: string[]; + planLat: NumericStat; + rowsRead: NumericStat; + rowsWritten: NumericStat; + runLat: NumericStat; + svcLat: NumericStat; +}; + +type ExecStats = { + contentionTime: NumericStat; + cnt: Long; + maxDiskUsage: NumericStat; + maxMemUsage: NumericStat; + networkBytes: NumericStat; + networkMsgs: NumericStat; +}; + +type StatementStatistics = { + execution_statistics: ExecStats; + index_recommendations: string[]; + statistics: Statistics; +}; + +export type StatementRawFormat = { + aggregated_ts: number; + fingerprint_id: string; + transaction_fingerprint_id: string; + plan_hash: string; + app_name: string; + node_id: number; + agg_interval: number; + metadata: StatementMetadata; + statistics: StatementStatistics; + index_recommendations: string[]; + indexes_usage: string[]; +}; + +export function convertStatementRawFormatToAggregatedStatistics( + s: StatementRawFormat, +): AggregateStatistics { + return { + aggregationInterval: s.agg_interval, + applicationName: s.app_name, + database: s.metadata.db, + fullScan: s.metadata.fullScan, + implicitTxn: s.metadata.implicitTxn, + label: s.metadata.querySummary, + summary: s.metadata.querySummary, + aggregatedTs: s.aggregated_ts, + aggregatedFingerprintID: HexStringToInt64String(s.fingerprint_id), + aggregatedFingerprintHexID: FixFingerprintHexValue(s.fingerprint_id), + stats: { + exec_stats: { + contention_time: s.statistics.execution_statistics.contentionTime, + count: s.statistics.execution_statistics.cnt, + max_disk_usage: s.statistics.execution_statistics.maxDiskUsage, + max_mem_usage: s.statistics.execution_statistics.maxMemUsage, + network_bytes: s.statistics.execution_statistics.networkBytes, + network_messages: s.statistics.execution_statistics.networkMsgs, + }, + bytes_read: s.statistics.statistics.bytesRead, + count: s.statistics.statistics.cnt, + first_attempt_count: s.statistics.statistics.firstAttemptCnt, + idle_lat: s.statistics.statistics.idleLat, + index_recommendations: s.statistics.index_recommendations, + indexes: s.statistics.statistics.indexes, + last_exec_timestamp: stringToTimestamp( + s.statistics.statistics.lastExecAt, + ), + max_retries: s.statistics.statistics.maxRetries, + nodes: s.statistics.statistics.nodes, + num_rows: s.statistics.statistics.numRows, + overhead_lat: s.statistics.statistics.ovhLat, + parse_lat: s.statistics.statistics.parseLat, + plan_gists: s.statistics.statistics.planGists, + plan_lat: s.statistics.statistics.planLat, + rows_read: s.statistics.statistics.rowsRead, + rows_written: s.statistics.statistics.rowsWritten, + run_lat: s.statistics.statistics.runLat, + service_lat: s.statistics.statistics.svcLat, + sql_type: s.metadata.stmtTyp, + }, + }; +} diff --git a/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetails.selectors.ts b/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetails.selectors.ts index 960462b51380..4b6e7638107d 100644 --- a/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetails.selectors.ts +++ b/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetails.selectors.ts @@ -23,9 +23,13 @@ import { } from "../util"; import { cockroach } from "@cockroachlabs/crdb-protobuf-client"; import { IndexDetailsPageData } from "./indexDetailsPage"; -import { selectIsTenant } from "../store/uiConfig"; +import { + selectHasViewActivityRedactedRole, + selectIsTenant, +} from "../store/uiConfig"; import { BreadcrumbItem } from "../breadcrumbs"; import { RecommendationType as RecType } from "./indexDetailsPage"; +import { nodeRegionsByIDSelector } from "../store/nodes"; const { RecommendationType } = cockroach.sql.IndexRecommendation; export const selectIndexDetails = createSelector( @@ -39,7 +43,18 @@ export const selectIndexDetails = createSelector( getMatchParamByName(props.match, indexNameAttr), (state: AppState) => state.adminUI.indexStats.cachedData, (state: AppState) => selectIsTenant(state), - (database, schema, table, index, indexStats): IndexDetailsPageData => { + (state: AppState) => selectHasViewActivityRedactedRole(state), + (state: AppState) => nodeRegionsByIDSelector(state), + ( + database, + schema, + table, + index, + indexStats, + isTenant, + hasViewActivityRedactedRole, + nodeRegions, + ): IndexDetailsPageData => { const stats = indexStats[generateTableID(database, table)]; const details = stats?.data?.statistics.filter( stat => stat.index_name === index, // index names must be unique for a table @@ -70,10 +85,15 @@ export const selectIndexDetails = createSelector( table, index, ), + isTenant: isTenant, + hasViewActivityRedactedRole: hasViewActivityRedactedRole, + nodeRegions: nodeRegions, details: { loading: !!stats?.inFlight, loaded: !!stats?.valid, createStatement: details?.create_statement || "", + tableID: details?.statistics.key.table_id.toString(), + indexID: details?.statistics.key.index_id.toString(), totalReads: longToInt(details?.statistics?.stats?.total_read_count) || 0, lastRead: TimestampToMoment(details?.statistics?.stats?.last_read), diff --git a/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.module.scss b/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.module.scss index 0bd704fab476..1eb725a6423f 100644 --- a/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.module.scss +++ b/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.module.scss @@ -142,3 +142,13 @@ margin-top: 20px; } +.bottom-space { + margin-bottom: 40px; +} + +.table-scroll { + width: calc(100% - -23px); + overflow-x: scroll; + padding-left: 0; +} + diff --git a/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.stories.tsx b/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.stories.tsx index b3af43360003..04de5f68d276 100644 --- a/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.stories.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.stories.tsx @@ -20,12 +20,16 @@ const withData: IndexDetailsPageProps = { databaseName: randomName(), tableName: randomName(), indexName: randomName(), + isTenant: false, + nodeRegions: {}, details: { loading: false, loaded: true, createStatement: ` CREATE UNIQUE INDEX "primary" ON system.public.database_role_settings USING btree (database_id ASC, role_name ASC) `, + tableID: "1", + indexID: "1", totalReads: 0, lastRead: moment("2021-10-21T22:00:00Z"), lastReset: moment("2021-12-02T07:12:00Z"), diff --git a/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.tsx index e569d89a5f6a..fb858470e701 100644 --- a/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/indexDetailsPage/indexDetailsPage.tsx @@ -10,7 +10,11 @@ import React from "react"; import classNames from "classnames/bind"; -import { SortSetting } from "src/sortedtable"; +import { + ISortedTablePagination, + SortedTable, + SortSetting, +} from "src/sortedtable"; import styles from "./indexDetailsPage.module.scss"; import { baseHeadingClasses } from "src/transactionsPage/transactionsPageClasses"; @@ -26,9 +30,31 @@ import { SummaryCard } from "../summaryCard"; import moment, { Moment } from "moment"; import { Heading } from "@cockroachlabs/ui-components"; import { Anchor } from "../anchor"; -import { Count, DATE_FORMAT_24_UTC, performanceTuningRecipes } from "../util"; +import { + calculateTotalWorkload, + Count, + DATE_FORMAT_24_UTC, + performanceTuningRecipes, +} from "../util"; +import { + getStatementsUsingIndex, + StatementsListRequestFromDetails, + StatementsUsingIndexRequest, +} from "../api/indexDetailsApi"; +import { + AggregateStatistics, + makeStatementsColumns, + populateRegionNodeForStatements, +} from "../statementsTable"; +import { UIConfigState } from "../store"; +import statementsStyles from "../statementsPage/statementsPage.module.scss"; +import { Pagination } from "../pagination"; +import { TableStatistics } from "../tableStatistics"; +import { EmptyStatementsPlaceholder } from "../statementsPage/emptyStatementsPlaceholder"; +import { StatementViewType } from "../statementsPage/statementPageTypes"; const cx = classNames.bind(styles); +const stmtCx = classNames.bind(statementsStyles); // We break out separate interfaces for some of the nested objects in our data // so that we can make (typed) test assertions on narrower slices of the data. @@ -45,6 +71,8 @@ const cx = classNames.bind(styles); // details: { // IndexDetails; // loading: boolean; // loaded: boolean; +// tableID: string; +// indexID: string; // createStatement: string; // totalReads: number; // lastRead: Moment; @@ -58,11 +86,16 @@ export interface IndexDetailsPageData { indexName: string; details: IndexDetails; breadcrumbItems: BreadcrumbItem[]; + isTenant: UIConfigState["isTenant"]; + hasViewActivityRedactedRole?: UIConfigState["hasViewActivityRedactedRole"]; + nodeRegions: { [nodeId: string]: string }; } interface IndexDetails { loading: boolean; loaded: boolean; + tableID: string; + indexID: string; createStatement: string; totalReads: number; lastRead: Moment; @@ -87,7 +120,9 @@ export type IndexDetailsPageProps = IndexDetailsPageData & IndexDetailPageActions; interface IndexDetailsPageState { - sortSetting: SortSetting; + statements: AggregateStatistics[]; + stmtSortSetting: SortSetting; + stmtPagination: ISortedTablePagination; } export class IndexDetailsPage extends React.Component< @@ -98,9 +133,15 @@ export class IndexDetailsPage extends React.Component< super(props); this.state = { - sortSetting: { + stmtSortSetting: { ascending: true, + columnTitle: "statementTime", }, + stmtPagination: { + pageSize: 10, + current: 1, + }, + statements: [], }; } @@ -112,6 +153,17 @@ export class IndexDetailsPage extends React.Component< this.refresh(); } + onChangeSortSetting = (ss: SortSetting): void => { + this.setState({ + stmtSortSetting: ss, + }); + }; + + onChangePage = (current: number): void => { + const { stmtPagination } = this.state; + this.setState({ stmtPagination: { ...stmtPagination, current } }); + }; + private refresh() { if (this.props.refreshNodes != null) { this.props.refreshNodes(); @@ -122,6 +174,17 @@ export class IndexDetailsPage extends React.Component< this.props.tableName, ); } + + const req: StatementsUsingIndexRequest = StatementsListRequestFromDetails( + this.props.details.tableID, + this.props.details.indexID, + this.props.databaseName, + null, + ); + getStatementsUsingIndex(req).then(res => { + populateRegionNodeForStatements(res, this.props.nodeRegions); + this.setState({ statements: res }); + }); } private getTimestampString(timestamp: Moment): string { @@ -210,6 +273,10 @@ export class IndexDetailsPage extends React.Component< } render(): React.ReactElement { + const { statements, stmtSortSetting, stmtPagination } = this.state; + const { isTenant, hasViewActivityRedactedRole } = this.props; + const isEmptySearchResults = statements?.length > 0; + return (
@@ -312,7 +379,7 @@ export class IndexDetailsPage extends React.Component< - Index recommendations + Index Recommendations {this.renderIndexRecommendations( @@ -323,6 +390,47 @@ export class IndexDetailsPage extends React.Component< + + + + Index Usage + + !(isTenant && c.hideIfTenant))} + className={stmtCx("statements-table")} + tableWrapperClassName={cx("table-scroll")} + sortSetting={stmtSortSetting} + onChangeSortSetting={this.onChangeSortSetting} + pagination={stmtPagination} + renderNoResult={ + + } + /> + + + + diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/emptyStatementsPlaceholder.tsx b/pkg/ui/workspaces/cluster-ui/src/statementsPage/emptyStatementsPlaceholder.tsx index a521cb1e93bf..0f4f51e689ef 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/emptyStatementsPlaceholder.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/emptyStatementsPlaceholder.tsx @@ -11,11 +11,10 @@ import React from "react"; import { EmptyTable, EmptyTableProps } from "src/empty"; import { Anchor } from "src/anchor"; -import { statementsTable } from "src/util"; +import { statementsTable, tabAttr, viewAttr } from "src/util"; import magnifyingGlassImg from "../assets/emptyState/magnifying-glass.svg"; import emptyTableResultsImg from "../assets/emptyState/empty-table-results.svg"; import { StatementViewType } from "./statementPageTypes"; -import { tabAttr, viewAttr } from "src/util"; import { Link } from "react-router-dom"; import { commonStyles } from "src/common"; @@ -47,6 +46,13 @@ function getMessage(type: StatementViewType): EmptyTableProps { ), }; + case StatementViewType.USING_INDEX: + return { + title: + "No SQL statements using this index in the selected time interval", + icon: emptyTableResultsImg, + footer, + }; case StatementViewType.FINGERPRINTS: default: return { diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementPageTypes.ts b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementPageTypes.ts index 24587b5fbba8..77392eb13cd9 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementPageTypes.ts +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementPageTypes.ts @@ -11,4 +11,5 @@ export enum StatementViewType { ACTIVE = "active", FINGERPRINTS = "fingerprints", + USING_INDEX = "using_index", } diff --git a/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx b/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx index ba3e747c7cc5..b0f1529f5cfd 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statsTableUtil/statsTableUtil.tsx @@ -353,8 +353,8 @@ export const statisticsTableTitles: StatisticTableTitleType = {

- {` represents one or more SQL statements by replacing the literal values (e.g., numbers and strings) with - underscores (_). To view additional details of a SQL statement fingerprint, click the fingerprint to + {` represents one or more SQL statements by replacing the literal values (e.g., numbers and strings) with + underscores (_). To view additional details of a SQL statement fingerprint, click the fingerprint to open the Statement Details page.`}

@@ -372,8 +372,8 @@ export const statisticsTableTitles: StatisticTableTitleType = { content={ <>

- {`A transaction fingerprint represents one or more SQL transactions by replacing the literal values (e.g., numbers and strings) with - underscores (_). To view additional details of a SQL transaction fingerprint, click the fingerprint to + {`A transaction fingerprint represents one or more SQL transactions by replacing the literal values (e.g., numbers and strings) with + underscores (_). To view additional details of a SQL transaction fingerprint, click the fingerprint to open the Transaction Details page.`}

@@ -656,7 +656,7 @@ export const statisticsTableTitles: StatisticTableTitleType = { content={ <>

- {`Maximum memory used by a ${contentModifier} with this fingerprint${fingerprintModifier} at any time + {`Maximum memory used by a ${contentModifier} with this fingerprint${fingerprintModifier} at any time during its execution within the specified time interval. `}

diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx index 964793161c08..59660621d1c6 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx @@ -485,6 +485,7 @@ export class TransactionDetails extends React.Component< className={cx("statements-table")} sortSetting={sortSetting} onChangeSortSetting={this.onChangeSortSetting} + pagination={pagination} /> diff --git a/pkg/ui/workspaces/cluster-ui/src/util/convert.ts b/pkg/ui/workspaces/cluster-ui/src/util/convert.ts index 3abaa308ec0d..782a17368969 100644 --- a/pkg/ui/workspaces/cluster-ui/src/util/convert.ts +++ b/pkg/ui/workspaces/cluster-ui/src/util/convert.ts @@ -162,3 +162,8 @@ export function makeTimestamp(unixTs: number): Timestamp { seconds: fromNumber(unixTs), }); } + +export function stringToTimestamp(t: string): Timestamp { + const unix = new Date(t).getTime() / 1000; + return makeTimestamp(unix); +} diff --git a/pkg/ui/workspaces/db-console/src/views/databases/indexDetailsPage/redux.spec.ts b/pkg/ui/workspaces/db-console/src/views/databases/indexDetailsPage/redux.spec.ts index 875e860d66dc..82bfd9899f63 100644 --- a/pkg/ui/workspaces/db-console/src/views/databases/indexDetailsPage/redux.spec.ts +++ b/pkg/ui/workspaces/db-console/src/views/databases/indexDetailsPage/redux.spec.ts @@ -134,6 +134,8 @@ describe("Index Details Page", function () { databaseName: "DATABASE", tableName: "TABLE", indexName: "INDEX", + isTenant: false, + nodeRegions: {}, details: { loading: false, loaded: false, @@ -142,6 +144,8 @@ describe("Index Details Page", function () { lastRead: moment(), lastReset: moment(), indexRecommendations: [], + tableID: undefined, + indexID: undefined, }, breadcrumbItems: null, }, @@ -182,9 +186,13 @@ describe("Index Details Page", function () { databaseName: "DATABASE", tableName: "TABLE", indexName: "INDEX", + isTenant: false, + nodeRegions: {}, details: { loading: false, loaded: true, + tableID: "15", + indexID: "2", createStatement: "CREATE INDEX jobs_created_by_type_created_by_id_idx ON system.public.jobs USING btree (created_by_type ASC, created_by_id ASC) STORING (status)", totalReads: 2, diff --git a/pkg/ui/workspaces/db-console/src/views/databases/indexDetailsPage/redux.ts b/pkg/ui/workspaces/db-console/src/views/databases/indexDetailsPage/redux.ts index 728d75daa425..18fdac83c595 100644 --- a/pkg/ui/workspaces/db-console/src/views/databases/indexDetailsPage/redux.ts +++ b/pkg/ui/workspaces/db-console/src/views/databases/indexDetailsPage/redux.ts @@ -31,6 +31,8 @@ import { resetIndexUsageStatsAction } from "src/redux/indexUsageStats"; import { longToInt } from "src/util/fixLong"; import { cockroach } from "src/js/protos"; import TableIndexStatsRequest = cockroach.server.serverpb.TableIndexStatsRequest; +import { selectHasViewActivityRedactedRole } from "src/redux/user"; +import { nodeRegionsByIDSelector } from "src/redux/nodes"; const { RecommendationType } = cockroach.sql.IndexRecommendation; export const mapStateToProps = createSelector( @@ -41,7 +43,16 @@ export const mapStateToProps = createSelector( (_state: AdminUIState, props: RouteComponentProps): string => getMatchParamByName(props.match, indexNameAttr), state => state.cachedData.indexStats, - (database, table, index, indexStats): IndexDetailsPageData => { + state => selectHasViewActivityRedactedRole(state), + state => nodeRegionsByIDSelector(state), + ( + database, + table, + index, + indexStats, + hasViewActivityRedactedRole, + nodeRegions, + ): IndexDetailsPageData => { const stats = indexStats[generateTableID(database, table)]; const details = stats?.data?.statistics.filter( stat => stat.index_name === index, // index names must be unique for a table @@ -67,10 +78,15 @@ export const mapStateToProps = createSelector( databaseName: database, tableName: table, indexName: index, + isTenant: false, + hasViewActivityRedactedRole: hasViewActivityRedactedRole, + nodeRegions: nodeRegions, details: { loading: !!stats?.inFlight, loaded: !!stats?.valid, createStatement: details?.create_statement || "", + tableID: details?.statistics.key.table_id.toString(), + indexID: details?.statistics.key.index_id.toString(), totalReads: longToInt(details?.statistics?.stats?.total_read_count) || 0, lastRead: util.TimestampToMoment(details?.statistics?.stats?.last_read),