From 001ca87b050bdc064aaef69b5aeabf0548a75e9b Mon Sep 17 00:00:00 2001 From: Thomas Hardy Date: Wed, 23 Aug 2023 16:54:16 -0400 Subject: [PATCH] cluster-ui: handle partial response errors on the database details page Part of: #102386 This change applies the same error handling ideas from #109245 to the database details page, enabling non-admin users to use the database details page and providing better transparency to data fetching issues. Errors encountered while fetching table details can be viewed via the tooltip provided by the `Caution` icon at the table's name. `unavailable` cells also provide a tooltip that displays the error impacting that exact cell. Release note (ui change): Non-admin users are able to use the database details page. --- .../workspaces/cluster-ui/src/api/sqlApi.ts | 3 +- .../cluster-ui/src/api/tableDetailsApi.ts | 39 +-- .../databaseDetailsConnected.ts | 9 +- .../databaseDetailsPage.stories.tsx | 9 +- .../databaseDetailsPage.tsx | 242 +++++++++--------- .../{helperComponents.tsx => tableCells.tsx} | 71 ++++- .../cluster-ui/src/databases/combiners.ts | 27 +- .../databaseDetailsPage/redux.spec.ts | 135 +++++----- .../databases/databaseDetailsPage/redux.ts | 8 +- 9 files changed, 301 insertions(+), 242 deletions(-) rename pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/{helperComponents.tsx => tableCells.tsx} (62%) diff --git a/pkg/ui/workspaces/cluster-ui/src/api/sqlApi.ts b/pkg/ui/workspaces/cluster-ui/src/api/sqlApi.ts index fa9933605bf9..31c11e99c77c 100644 --- a/pkg/ui/workspaces/cluster-ui/src/api/sqlApi.ts +++ b/pkg/ui/workspaces/cluster-ui/src/api/sqlApi.ts @@ -147,7 +147,7 @@ export function isUpgradeError(message: string): boolean { } /** - * errorMessage cleans the error message returned by the sqlApi, + * sqlApiErrorMessage cleans the error message returned by the sqlApi, * removing information not useful for the user. * e.g. the error message * "$executing stmt 1: run-query-via-api: only users with either MODIFYCLUSTERSETTING @@ -170,7 +170,6 @@ export function sqlApiErrorMessage(message: string): string { const idx = message.indexOf(":") + 1; return idx < message.length ? message.substring(idx) : message; } - return message; } diff --git a/pkg/ui/workspaces/cluster-ui/src/api/tableDetailsApi.ts b/pkg/ui/workspaces/cluster-ui/src/api/tableDetailsApi.ts index f4f20d7f1ec2..460519368800 100644 --- a/pkg/ui/workspaces/cluster-ui/src/api/tableDetailsApi.ts +++ b/pkg/ui/workspaces/cluster-ui/src/api/tableDetailsApi.ts @@ -9,6 +9,7 @@ // licenses/APL.txt. import { + combineQueryErrors, executeInternalSql, formatApiResult, LARGE_RESULT_SIZE, @@ -111,7 +112,7 @@ const getTableId: TableDetailsQuery = { }; // Table create statement. -type TableCreateStatementRow = { create_statement: string }; +export type TableCreateStatementRow = { create_statement: string }; const getTableCreateStatement: TableDetailsQuery = { createStmt: (dbName, tableName) => { @@ -129,22 +130,22 @@ const getTableCreateStatement: TableDetailsQuery = { txn_result: SqlTxnResult, resp: TableDetailsResponse, ) => { + if (txn_result.error) { + resp.createStmtResp.error = txn_result.error; + } if (!txnResultIsEmpty(txn_result)) { resp.createStmtResp.create_statement = txn_result.rows[0].create_statement; - } else { + } else if (!txn_result.error) { txn_result.error = new Error( "getTableCreateStatement: unexpected empty results", ); } - if (txn_result.error) { - resp.createStmtResp.error = txn_result.error; - } }, }; // Table grants. -type TableGrantsResponse = { +export type TableGrantsResponse = { grants: TableGrantsRow[]; }; @@ -181,7 +182,7 @@ const getTableGrants: TableDetailsQuery = { }; // Table schema details. -type TableSchemaDetailsRow = { +export type TableSchemaDetailsRow = { columns: string[]; indexes: string[]; }; @@ -332,7 +333,7 @@ const getTableZoneConfig: TableDetailsQuery = { }; // Table heuristics details. -type TableHeuristicDetailsRow = { +export type TableHeuristicDetailsRow = { stats_last_created_at: moment.Moment; }; @@ -371,7 +372,7 @@ type TableDetailsStats = { }; // Table span stats. -type TableSpanStatsRow = { +export type TableSpanStatsRow = { approximate_disk_bytes: number; live_bytes: number; total_bytes: number; @@ -418,7 +419,7 @@ const getTableSpanStats: TableDetailsQuery = { }, }; -type TableReplicaData = SqlApiQueryResponse<{ +export type TableReplicaData = SqlApiQueryResponse<{ nodeIDs: number[]; nodeCount: number; replicaCount: number; @@ -471,7 +472,7 @@ const getTableReplicas: TableDetailsQuery = { }; // Table index usage stats. -type TableIndexUsageStats = { +export type TableIndexUsageStats = { has_index_recommendations: boolean; }; @@ -569,6 +570,7 @@ export function createTableDetailsReq( max_result_size: LARGE_RESULT_SIZE, timeout: LONG_TIMEOUT, database: dbName, + separate_txns: true, }; } @@ -605,19 +607,24 @@ async function fetchTableDetails( csIndexUnusedDuration, ); const resp = await executeInternalSql(req); + const errs: Error[] = []; resp.execution.txn_results.forEach(txn_result => { - if (txn_result.rows) { - const query: TableDetailsQuery = - tableDetailQueries[txn_result.statement - 1]; - query.addToTableDetail(txn_result, detailsResponse); + if (txn_result.error) { + errs.push(txn_result.error); } + const query: TableDetailsQuery = + tableDetailQueries[txn_result.statement - 1]; + query.addToTableDetail(txn_result, detailsResponse); }); if (resp.error) { detailsResponse.error = resp.error; } + + detailsResponse.error = combineQueryErrors(errs, detailsResponse.error); return formatApiResult( detailsResponse, detailsResponse.error, - "retrieving table details information", + `retrieving table details information for table '${tableName}'`, + false, ); } diff --git a/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsConnected.ts b/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsConnected.ts index d557dabbd96d..8cdeb925e326 100644 --- a/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsConnected.ts +++ b/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsConnected.ts @@ -37,7 +37,7 @@ import { selectDatabaseDetailsTablesSortSetting, selectDatabaseDetailsViewModeSetting, } from "../store/databaseDetails/databaseDetails.selectors"; -import { combineLoadingErrors, deriveTableDetailsMemoized } from "../databases"; +import { deriveTableDetailsMemoized } from "../databases"; import { selectDropUnusedIndexDuration, selectIndexRecommendationsEnabled, @@ -56,11 +56,8 @@ const mapStateToProps = ( return { loading: !!databaseDetails[database]?.inFlight, loaded: !!databaseDetails[database]?.valid, - lastError: combineLoadingErrors( - databaseDetails[database]?.lastError, - databaseDetails[database]?.data?.maxSizeReached, - null, - ), + requestError: databaseDetails[database]?.lastError, + queryError: databaseDetails[database]?.data?.results?.error, name: database, showNodeRegionsColumn: Object.keys(nodeRegions).length > 1 && !isTenant, viewMode: selectDatabaseDetailsViewModeSetting(state), diff --git a/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.stories.tsx b/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.stories.tsx index 10b683a6ceab..a2516260dbbf 100644 --- a/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.stories.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.stories.tsx @@ -34,7 +34,8 @@ const history = H.createHashHistory(); const withLoadingIndicator: DatabaseDetailsPageProps = { loading: true, loaded: false, - lastError: undefined, + requestError: undefined, + queryError: undefined, showIndexRecommendations: false, csIndexUnusedDuration: indexUnusedDuration, name: randomName(), @@ -68,7 +69,8 @@ const withLoadingIndicator: DatabaseDetailsPageProps = { const withoutData: DatabaseDetailsPageProps = { loading: false, loaded: true, - lastError: null, + requestError: null, + queryError: undefined, showIndexRecommendations: false, csIndexUnusedDuration: indexUnusedDuration, name: randomName(), @@ -108,7 +110,8 @@ function createTable(): DatabaseDetailsPageDataTable { return { loading: false, loaded: true, - lastError: null, + requestError: null, + queryError: undefined, name: randomName(), details: { columnCount: _.random(5, 42), diff --git a/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.tsx index a063c3668493..aad2ce5ce0d2 100644 --- a/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.tsx @@ -24,14 +24,12 @@ import { SortedTable, SortSetting, } from "src/sortedtable"; -import * as format from "src/util/format"; import { DATE_FORMAT, EncodeDatabaseTableUri } from "src/util/format"; import { mvccGarbage, syncHistory, unique } from "../util"; import styles from "./databaseDetailsPage.module.scss"; import sortableTableStyles from "src/sortedtable/sortedtable.module.scss"; import { baseHeadingClasses } from "src/transactionsPage/transactionsPageClasses"; -import { Moment } from "moment-timezone"; import { Anchor } from "../anchor"; import LoadingError from "../sqlActivity/errorComponent"; import { Loading } from "../loading"; @@ -47,10 +45,22 @@ import { TableStatistics } from "src/tableStatistics"; import { Timestamp, Timezone } from "../timestamp"; import { DbDetailsBreadcrumbs, - IndexRecWithIconCell, + DiskSizeCell, + IndexesCell, MVCCInfoCell, TableNameCell, -} from "./helperComponents"; +} from "./tableCells"; +import { + isMaxSizeError, + SqlApiQueryResponse, + SqlExecutionErrorMessage, + TableHeuristicDetailsRow, + TableIndexUsageStats, + TableSchemaDetailsRow, + TableSpanStatsRow, +} from "../api"; +import { checkInfoAvailable } from "../databases"; +import { InlineAlert } from "@cockroachlabs/ui-components"; const cx = classNames.bind(styles); const sortableTableCx = classNames.bind(sortableTableStyles); @@ -97,7 +107,10 @@ const sortableTableCx = classNames.bind(sortableTableStyles); export interface DatabaseDetailsPageData { loading: boolean; loaded: boolean; - lastError: Error; + // Request error when getting table names. + requestError?: Error; + // Query error when getting table names. + queryError?: SqlExecutionErrorMessage; name: string; tables: DatabaseDetailsPageDataTable[]; sortSettingTables: SortSetting; @@ -116,23 +129,24 @@ export interface DatabaseDetailsPageDataTable { name: string; loading: boolean; loaded: boolean; - lastError: Error; + // Request error when getting table details. + requestError?: Error; + // Query error when getting table details. + queryError?: SqlExecutionErrorMessage; details: DatabaseDetailsPageDataTableDetails; } -export interface DatabaseDetailsPageDataTableDetails { - columnCount: number; - indexCount: number; - userCount: number; +interface GrantsData { roles: string[]; - grants: string[]; - statsLastUpdated?: Moment; - hasIndexRecommendations: boolean; - totalBytes: number; - liveBytes: number; - livePercentage: number; - replicationSizeInBytes: number; - rangeCount: number; + privileges: string[]; +} + +export interface DatabaseDetailsPageDataTableDetails { + schemaDetails?: SqlApiQueryResponse; + grants: SqlApiQueryResponse; + statsLastUpdated?: SqlApiQueryResponse; + indexStatRecs?: SqlApiQueryResponse; + spanStats?: SqlApiQueryResponse; // Array of node IDs used to unambiguously filter by node and region. nodes?: number[]; // String of nodes grouped by region in alphabetical order, e.g. @@ -171,7 +185,6 @@ interface DatabaseDetailsPageState { pagination: ISortedTablePagination; filters?: Filters; activeFilters?: number; - lastDetailsError: Error; } const tablePageSize = 20; @@ -209,7 +222,6 @@ export class DatabaseDetailsPage extends React.Component< current: 1, pageSize: 20, }, - lastDetailsError: null, }; const { history } = this.props; @@ -252,7 +264,7 @@ export class DatabaseDetailsPage extends React.Component< } componentDidMount(): void { - if (!this.props.loaded && !this.props.loading && !this.props.lastError) { + if (!this.props.loaded && !this.props.loading && !this.props.requestError) { this.props.refreshDatabaseDetails( this.props.name, this.props.csIndexUnusedDuration, @@ -302,7 +314,7 @@ export class DatabaseDetailsPage extends React.Component< i++ ) { const table = filteredTables[i]; - if (!table.loaded && !table.loading && table.lastError == undefined) { + if (!table.loaded && !table.loading && table.requestError == undefined) { return true; } } @@ -311,7 +323,6 @@ export class DatabaseDetailsPage extends React.Component< } private refresh(): void { - let lastDetailsError: Error; // Load everything by default let filteredTables = this.props.tables; @@ -337,22 +348,7 @@ export class DatabaseDetailsPage extends React.Component< } filteredTables.forEach(table => { - if (table.lastError !== undefined) { - lastDetailsError = table.lastError; - } - if ( - lastDetailsError && - this.state.lastDetailsError?.name != lastDetailsError?.name - ) { - this.setState({ lastDetailsError: lastDetailsError }); - } - - if ( - !table.loaded && - !table.loading && - (table.lastError === undefined || - table.lastError?.name === "GetDatabaseInfoError") - ) { + if (!table.loaded && !table.loading && table.requestError === undefined) { this.props.refreshTableDetails( this.props.name, table.name, @@ -528,16 +524,6 @@ export class DatabaseDetailsPage extends React.Component< } } - checkInfoAvailable = ( - error: Error, - cell: React.ReactNode, - ): React.ReactNode => { - if (error) { - return "(unavailable)"; - } - return cell; - }; - private columnsForTablesViewMode(): ColumnDescriptor[] { return ( [ @@ -561,12 +547,8 @@ export class DatabaseDetailsPage extends React.Component< Replication Size ), - cell: table => - this.checkInfoAvailable( - table.lastError, - format.Bytes(table.details.replicationSizeInBytes), - ), - sort: table => table.details.replicationSizeInBytes, + cell: table => , + sort: table => table.details.spanStats?.approximate_disk_bytes, className: cx("database-table__col-size"), name: "replicationSize", }, @@ -580,8 +562,12 @@ export class DatabaseDetailsPage extends React.Component< ), cell: table => - this.checkInfoAvailable(table.lastError, table.details.rangeCount), - sort: table => table.details.rangeCount, + checkInfoAvailable( + table.requestError, + table.details.spanStats?.error, + table.details.spanStats?.range_count, + ), + sort: table => table.details.spanStats?.range_count, className: cx("database-table__col-range-count"), name: "rangeCount", }, @@ -595,8 +581,12 @@ export class DatabaseDetailsPage extends React.Component< ), cell: table => - this.checkInfoAvailable(table.lastError, table.details.columnCount), - sort: table => table.details.columnCount, + checkInfoAvailable( + table.requestError, + table.details.schemaDetails?.error, + table.details.schemaDetails?.columns?.length, + ), + sort: table => table.details.schemaDetails?.columns?.length, className: cx("database-table__col-column-count"), name: "columnCount", }, @@ -609,19 +599,13 @@ export class DatabaseDetailsPage extends React.Component< Indexes ), - cell: table => { - return table.details.hasIndexRecommendations && - this.props.showIndexRecommendations - ? this.checkInfoAvailable( - table.lastError, - , - ) - : this.checkInfoAvailable( - table.lastError, - table.details.indexCount, - ); - }, - sort: table => table.details.indexCount, + cell: table => ( + + ), + sort: table => table.details.schemaDetails?.indexes?.length, className: cx("database-table__col-index-count"), name: "indexCount", }, @@ -635,8 +619,9 @@ export class DatabaseDetailsPage extends React.Component< ), cell: table => - this.checkInfoAvailable( - table.lastError, + checkInfoAvailable( + table.requestError, + null, table.details.nodesByRegionString || "None", ), sort: table => table.details.nodesByRegionString, @@ -664,11 +649,14 @@ export class DatabaseDetailsPage extends React.Component< ), cell: table => - this.checkInfoAvailable( - table.lastError, - , + checkInfoAvailable( + table.requestError, + table.details.spanStats?.error, + table.details.spanStats ? ( + + ) : null, ), - sort: table => table.details.livePercentage, + sort: table => table.details.spanStats?.live_percentage, className: cx("database-table__col-column-count"), name: "livePercentage", }, @@ -683,7 +671,7 @@ export class DatabaseDetailsPage extends React.Component< ), cell: table => ( @@ -727,8 +715,12 @@ export class DatabaseDetailsPage extends React.Component< ), cell: table => - this.checkInfoAvailable(table.lastError, table.details.userCount), - sort: table => table.details.userCount, + checkInfoAvailable( + table.requestError, + table.details.grants?.error, + table.details.grants?.roles.length, + ), + sort: table => table.details.grants?.roles.length, className: cx("database-table__col-user-count"), name: "userCount", }, @@ -739,11 +731,12 @@ export class DatabaseDetailsPage extends React.Component< ), cell: table => - this.checkInfoAvailable( - table.lastError, - table.details.roles.join(", "), + checkInfoAvailable( + table.requestError, + table.details.grants?.error, + table.details.grants?.roles.join(", "), ), - sort: table => table.details.roles.join(", "), + sort: table => table.details.grants?.roles.join(", "), className: cx("database-table__col-roles"), name: "roles", }, @@ -754,11 +747,12 @@ export class DatabaseDetailsPage extends React.Component< ), cell: table => - this.checkInfoAvailable( - table.lastError, - table.details.grants.join(", "), + checkInfoAvailable( + table.requestError, + table.details.grants?.error, + table.details.grants?.privileges.join(", "), ), - sort: table => table.details.grants.join(", "), + sort: table => table.details.grants?.privileges?.join(", "), className: cx("database-table__col-grants"), name: "grants", }, @@ -864,54 +858,46 @@ export class DatabaseDetailsPage extends React.Component< ( - - - This database has no tables. - - } - /> - )} + error={this.props.requestError} renderError={() => LoadingError({ statsType: "databases", - error: this.props.lastError, + error: this.props.requestError, }) } - /> - {!this.props.loading && ( - + {isMaxSizeError(this.props.queryError?.message) && ( + + Not all tables are displayed because the maximum number of + tables was reached in the console.  + + } + /> + )} + <>} - renderError={() => - LoadingError({ - statsType: "part of the information", - error: this.state.lastDetailsError, - }) + disableSortSizeLimit={disableTableSortSize} + renderNoResult={ +
+ + This database has no tables. +
} /> - )} +
- { + return ( + <> + {checkInfoAvailable( + table.requestError, + table.details?.spanStats?.error, + table.details?.spanStats?.approximate_disk_bytes + ? format.Bytes(table.details?.spanStats?.approximate_disk_bytes) + : null, + )} + + ); +}; + export const TableNameCell = ({ table, dbDetails, @@ -58,19 +77,55 @@ export const TableNameCell = ({ linkURL += `?tab=grants`; } } + let icon = ; + if (table.requestError || table.queryError) { + icon = ( + + + + ); + } return ( - + {icon} {table.name} ); }; -export const IndexRecWithIconCell = ({ +export const IndexesCell = ({ table, + showIndexRecommendations, }: { table: DatabaseDetailsPageDataTable; + showIndexRecommendations: boolean; }): JSX.Element => { + const elem = ( + <> + {checkInfoAvailable( + table.requestError, + table.details?.schemaDetails?.error, + table.details?.schemaDetails?.indexes?.length, + )} + + ); + // If index recommendations are not enabled or we don't have any index recommendations, + // just return the number of indexes. + if ( + !table.details.indexStatRecs?.has_index_recommendations || + !showIndexRecommendations + ) { + return elem; + } + // Display an icon indicating we have index recommendations next to the number of indexes. return (
- {table.details.indexCount} + {elem}
); }; @@ -92,12 +147,16 @@ export const MVCCInfoCell = ({ return ( <>

- {format.Percentage(details.livePercentage, 1, 1)} + {format.Percentage(details?.spanStats?.live_percentage, 1, 1)}

- {format.Bytes(details.liveBytes)}{" "} + + {format.Bytes(details?.spanStats?.live_bytes)} + {" "} live data /{" "} - {format.Bytes(details.totalBytes)} + + {format.Bytes(details?.spanStats?.total_bytes)} + {" total"}

diff --git a/pkg/ui/workspaces/cluster-ui/src/databases/combiners.ts b/pkg/ui/workspaces/cluster-ui/src/databases/combiners.ts index 743eeefdbd7f..3379ce9ba580 100644 --- a/pkg/ui/workspaces/cluster-ui/src/databases/combiners.ts +++ b/pkg/ui/workspaces/cluster-ui/src/databases/combiners.ts @@ -139,24 +139,19 @@ const deriveDatabaseTableDetails = ( name: table, loading: !!details?.inFlight, loaded: !!details?.valid, - lastError: details?.lastError, + requestError: details?.lastError, + queryError: details?.data?.results?.error, details: { - columnCount: results?.schemaDetails.columns?.length || 0, - indexCount: results?.schemaDetails.indexes.length || 0, - userCount: normalizedRoles.length, - roles: normalizedRoles, - grants: normalizedPrivileges, - statsLastUpdated: - results?.heuristicsDetails.stats_last_created_at || null, - hasIndexRecommendations: - results?.stats.indexStats.has_index_recommendations || false, - totalBytes: results?.stats?.spanStats.total_bytes || 0, - liveBytes: results?.stats?.spanStats.live_bytes || 0, - livePercentage: results?.stats?.spanStats.live_percentage || 0, - replicationSizeInBytes: - results?.stats?.spanStats.approximate_disk_bytes || 0, + schemaDetails: results?.schemaDetails, + grants: { + roles: normalizedRoles, + privileges: normalizedPrivileges, + error: results?.grantsResp?.error, + }, + statsLastUpdated: results?.heuristicsDetails, + indexStatRecs: results?.stats.indexStats, + spanStats: results?.stats.spanStats, nodes: nodes, - rangeCount: results?.stats?.spanStats.range_count || 0, nodesByRegionString: getNodesByRegionString(nodes, nodeRegions, isTenant), }, }; diff --git a/pkg/ui/workspaces/db-console/src/views/databases/databaseDetailsPage/redux.spec.ts b/pkg/ui/workspaces/db-console/src/views/databases/databaseDetailsPage/redux.spec.ts index f181f4c64e75..0631d305b6d7 100644 --- a/pkg/ui/workspaces/db-console/src/views/databases/databaseDetailsPage/redux.spec.ts +++ b/pkg/ui/workspaces/db-console/src/views/databases/databaseDetailsPage/redux.spec.ts @@ -92,19 +92,22 @@ class TestDriver { // Expect Moment type field to be equal. expect( // Moments are the same - moment(statsLastUpdated).isSame(expectedStatsLastUpdated) || + moment(statsLastUpdated.stats_last_created_at).isSame( + expectedStatsLastUpdated.stats_last_created_at, + ) || // Moments are null. - (statsLastUpdated === expectedStatsLastUpdated && - statsLastUpdated === null), + (statsLastUpdated.stats_last_created_at === + expectedStatsLastUpdated.stats_last_created_at && + statsLastUpdated.stats_last_created_at === null), ).toBe(true); } assertTableRoles(name: string, expected: string[]) { - expect(this.findTable(name).details.roles).toEqual(expected); + expect(this.findTable(name).details.grants.roles).toEqual(expected); } assertTableGrants(name: string, expected: string[]) { - expect(this.findTable(name).details.grants).toEqual(expected); + expect(this.findTable(name).details.grants.privileges).toEqual(expected); } async refreshDatabaseDetails() { @@ -145,7 +148,8 @@ describe("Database Details Page", function () { driver.assertProperties({ loading: false, loaded: false, - lastError: undefined, + requestError: undefined, + queryError: undefined, name: "things", search: null, filters: defaultFilters, @@ -186,7 +190,8 @@ describe("Database Details Page", function () { driver.assertProperties({ loading: false, loaded: true, - lastError: undefined, + requestError: null, + queryError: undefined, name: "things", search: null, filters: defaultFilters, @@ -203,21 +208,19 @@ describe("Database Details Page", function () { name: `"public"."foo"`, loading: false, loaded: false, - lastError: undefined, + requestError: undefined, + queryError: undefined, details: { - columnCount: 0, - indexCount: 0, - userCount: 0, - roles: [], - grants: [], - statsLastUpdated: null, - hasIndexRecommendations: false, - livePercentage: 0, - liveBytes: 0, - totalBytes: 0, + schemaDetails: undefined, + grants: { + error: undefined, + roles: [], + privileges: [], + }, + statsLastUpdated: undefined, + indexStatRecs: undefined, + spanStats: undefined, nodes: [], - replicationSizeInBytes: 0, - rangeCount: 0, nodesByRegionString: "", }, }, @@ -225,21 +228,19 @@ describe("Database Details Page", function () { name: `"public"."bar"`, loading: false, loaded: false, - lastError: undefined, + requestError: undefined, + queryError: undefined, details: { - columnCount: 0, - indexCount: 0, - userCount: 0, - roles: [], - grants: [], - statsLastUpdated: null, - hasIndexRecommendations: false, - livePercentage: 0, - totalBytes: 0, - liveBytes: 0, + schemaDetails: undefined, + grants: { + error: undefined, + roles: [], + privileges: [], + }, + statsLastUpdated: undefined, + indexStatRecs: undefined, + spanStats: undefined, nodes: [], - replicationSizeInBytes: 0, - rangeCount: 0, nodesByRegionString: "", }, }, @@ -382,20 +383,29 @@ describe("Database Details Page", function () { name: `"public"."foo"`, loading: false, loaded: true, - lastError: null, + requestError: null, + queryError: undefined, details: { - columnCount: 3, - indexCount: 2, - userCount: 2, - roles: ["admin", "public"], - grants: ["CREATE", "SELECT"], - statsLastUpdated: mockStatsLastCreatedTimestamp, - hasIndexRecommendations: true, - liveBytes: 200, - totalBytes: 400, - livePercentage: 0.5, - replicationSizeInBytes: 100, - rangeCount: 400, + schemaDetails: { + columns: ["a", "b", "c"], + indexes: ["d", "e"], + }, + grants: { + error: undefined, + roles: ["admin", "public"], + privileges: ["CREATE", "SELECT"], + }, + statsLastUpdated: { + stats_last_created_at: mockStatsLastCreatedTimestamp, + }, + indexStatRecs: { has_index_recommendations: true }, + spanStats: { + approximate_disk_bytes: 100, + live_bytes: 200, + total_bytes: 400, + range_count: 400, + live_percentage: 0.5, + }, nodes: [1, 2, 3], nodesByRegionString: "", }, @@ -405,20 +415,27 @@ describe("Database Details Page", function () { name: `"public"."bar"`, loading: false, loaded: true, - lastError: null, + requestError: null, + queryError: undefined, details: { - columnCount: 2, - indexCount: 4, - userCount: 3, - roles: ["root", "app", "data"], - grants: ["ALL", "SELECT", "INSERT"], - statsLastUpdated: null, - hasIndexRecommendations: false, - liveBytes: 100, - totalBytes: 100, - livePercentage: 1, - replicationSizeInBytes: 10, - rangeCount: 50, + schemaDetails: { + columns: ["a", "b"], + indexes: ["c", "d", "e", "f"], + }, + grants: { + error: undefined, + roles: ["root", "app", "data"], + privileges: ["ALL", "SELECT", "INSERT"], + }, + statsLastUpdated: { stats_last_created_at: null }, + indexStatRecs: { has_index_recommendations: false }, + spanStats: { + live_percentage: 1, + live_bytes: 100, + total_bytes: 100, + range_count: 50, + approximate_disk_bytes: 10, + }, nodes: [1, 2, 3, 4, 5], nodesByRegionString: "", }, diff --git a/pkg/ui/workspaces/db-console/src/views/databases/databaseDetailsPage/redux.ts b/pkg/ui/workspaces/db-console/src/views/databases/databaseDetailsPage/redux.ts index 89bba471a1de..47cd3488ac09 100644 --- a/pkg/ui/workspaces/db-console/src/views/databases/databaseDetailsPage/redux.ts +++ b/pkg/ui/workspaces/db-console/src/views/databases/databaseDetailsPage/redux.ts @@ -15,7 +15,6 @@ import { defaultFilters, Filters, ViewMode, - combineLoadingErrors, deriveTableDetailsMemoized, } from "@cockroachlabs/cluster-ui"; @@ -81,11 +80,8 @@ export const mapStateToProps = ( return { loading: !!databaseDetails[database]?.inFlight, loaded: !!databaseDetails[database]?.valid, - lastError: combineLoadingErrors( - databaseDetails[database]?.lastError, - databaseDetails[database]?.data?.maxSizeReached, - null, - ), + requestError: databaseDetails[database]?.lastError, + queryError: databaseDetails[database]?.data?.results?.error, name: database, showNodeRegionsColumn: selectIsMoreThanOneNode(state), viewMode: viewModeLocalSetting.selector(state),