From 86dcb5ceb0ea3ffde653be9da245314596dcc47b Mon Sep 17 00:00:00 2001 From: maryliag Date: Thu, 27 Oct 2022 12:41:28 -0400 Subject: [PATCH] ui: handle errors on db endpoints Previously, when hitting an error on endpoints used on the database page, we would just keep retrying constantly, without showing a proper error state. On SQL Activity page, for example, we show the error message and let the user retry if they want. This commit uses the same logic on the Database page. Since the pages make several requests and just part of them can fail, some of the pages we will still load, but give a warning about unavailable data and show the error message about reload option. This commit also increases timeout of database endpoints. Fixes #90596 Release note: None --- .../databaseDetailsPage.tsx | 183 ++++++++-- .../databaseTablePage/databaseTablePage.tsx | 319 +++++++++++------- .../src/databasesPage/databasesPage.tsx | 114 +++++-- .../src/sessions/sessionDetails.tsx | 4 +- .../cluster-ui/src/sessions/sessionsPage.tsx | 4 +- .../src/sqlActivity/errorComponent.tsx | 4 +- .../src/statementDetails/statementDetails.tsx | 6 +- .../statementsPage/activeStatementsView.tsx | 4 +- .../src/statementsPage/statementsPage.tsx | 4 +- .../transactionDetails/transactionDetails.tsx | 4 +- .../activeTransactionsView.tsx | 4 +- .../src/transactionsPage/transactionsPage.tsx | 4 +- .../db-console/src/redux/apiReducers.ts | 8 + .../databaseDetailsPage/redux.spec.ts | 10 + .../databases/databaseDetailsPage/redux.ts | 3 + .../databases/databaseTablePage/redux.spec.ts | 6 + .../databases/databaseTablePage/redux.ts | 3 + .../databases/databasesPage/redux.spec.ts | 11 + .../views/databases/databasesPage/redux.ts | 7 + 19 files changed, 507 insertions(+), 195 deletions(-) diff --git a/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.tsx index 1fe7f5aafb27..205a762a5936 100644 --- a/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/databaseDetailsPage/databaseDetailsPage.tsx @@ -39,6 +39,8 @@ import { import { Moment } from "moment"; import { Caution } from "@cockroachlabs/icons"; import { Anchor } from "../anchor"; +import LoadingError from "../sqlActivity/errorComponent"; +import { Loading } from "../loading"; const cx = classNames.bind(styles); const sortableTableCx = classNames.bind(sortableTableStyles); @@ -55,6 +57,7 @@ const sortableTableCx = classNames.bind(sortableTableStyles); // interface DatabaseDetailsPageData { // loading: boolean; // loaded: boolean; +// lastError: Error; // name: string; // sortSettingTables: SortSetting; // sortSettingGrants: SortSetting; @@ -64,6 +67,7 @@ const sortableTableCx = classNames.bind(sortableTableStyles); // details: { // DatabaseDetailsPageDataTableDetails // loading: boolean; // loaded: boolean; +// lastError: Error; // columnCount: number; // indexCount: number; // userCount: number; @@ -73,6 +77,7 @@ const sortableTableCx = classNames.bind(sortableTableStyles); // stats: { // DatabaseDetailsPageDataTableStats // loading: boolean; // loaded: boolean; +// lastError: Error; // replicationSizeInBytes: number; // rangeCount: number; // nodesByRegionString: string; @@ -82,6 +87,7 @@ const sortableTableCx = classNames.bind(sortableTableStyles); export interface DatabaseDetailsPageData { loading: boolean; loaded: boolean; + lastError: Error; name: string; tables: DatabaseDetailsPageDataTable[]; sortSettingTables: SortSetting; @@ -99,6 +105,7 @@ export interface DatabaseDetailsPageDataTable { export interface DatabaseDetailsPageDataTableDetails { loading: boolean; loaded: boolean; + lastError: Error; columnCount: number; indexCount: number; userCount: number; @@ -114,6 +121,7 @@ export interface DatabaseDetailsPageDataTableDetails { export interface DatabaseDetailsPageDataTableStats { loading: boolean; loaded: boolean; + lastError: Error; replicationSizeInBytes: number; rangeCount: number; nodesByRegionString?: string; @@ -139,6 +147,8 @@ export enum ViewMode { interface DatabaseDetailsPageState { pagination: ISortedTablePagination; + lastStatsError: Error; + lastDetailsError: Error; } class DatabaseSortedTable extends SortedTable {} @@ -154,6 +164,8 @@ export class DatabaseDetailsPage extends React.Component< current: 1, pageSize: 20, }, + lastDetailsError: null, + lastStatsError: null, }; const { history } = this.props; @@ -204,16 +216,49 @@ export class DatabaseDetailsPage extends React.Component< } private refresh(): void { - if (!this.props.loaded && !this.props.loading) { + if ( + !this.props.loaded && + !this.props.loading && + this.props.lastError === undefined + ) { return this.props.refreshDatabaseDetails(this.props.name); } + let lastDetailsError: Error; + let lastStatsError: Error; this.props.tables.forEach(table => { - if (!table.details.loaded && !table.details.loading) { + if (table.details.lastError !== undefined) { + lastDetailsError = table.details.lastError; + } + if ( + lastDetailsError && + this.state.lastDetailsError?.name != lastDetailsError?.name + ) { + this.setState({ lastDetailsError: lastDetailsError }); + } + + if ( + !table.details.loaded && + !table.details.loading && + table.details.lastError === undefined + ) { return this.props.refreshTableDetails(this.props.name, table.name); } - if (!table.stats.loaded && !table.stats.loading) { + if (table.stats.lastError !== undefined) { + lastStatsError = table.stats.lastError; + } + if ( + lastStatsError && + this.state.lastStatsError?.name != lastStatsError?.name + ) { + this.setState({ lastStatsError: lastStatsError }); + } + if ( + !table.stats.loaded && + !table.stats.loading && + table.stats.lastError === undefined + ) { return this.props.refreshTableStats(this.props.name, table.name); } }); @@ -282,6 +327,16 @@ export class DatabaseDetailsPage extends React.Component< ); }; + checkInfoAvailable = ( + error: Error, + cell: React.ReactNode, + ): React.ReactNode => { + if (error) { + return "(unavailable)"; + } + return cell; + }; + private columnsForTablesViewMode(): ColumnDescriptor[] { return [ { @@ -312,7 +367,11 @@ export class DatabaseDetailsPage extends React.Component< Replication Size ), - cell: table => format.Bytes(table.stats.replicationSizeInBytes), + cell: table => + this.checkInfoAvailable( + table.stats.lastError, + format.Bytes(table.stats.replicationSizeInBytes), + ), sort: table => table.stats.replicationSizeInBytes, className: cx("database-table__col-size"), name: "replicationSize", @@ -326,7 +385,11 @@ export class DatabaseDetailsPage extends React.Component< Ranges ), - cell: table => table.stats.rangeCount, + cell: table => + this.checkInfoAvailable( + table.stats.lastError, + table.stats.rangeCount, + ), sort: table => table.stats.rangeCount, className: cx("database-table__col-range-count"), name: "rangeCount", @@ -340,7 +403,11 @@ export class DatabaseDetailsPage extends React.Component< Columns ), - cell: table => table.details.columnCount, + cell: table => + this.checkInfoAvailable( + table.stats.lastError, + table.details.columnCount, + ), sort: table => table.details.columnCount, className: cx("database-table__col-column-count"), name: "columnCount", @@ -355,8 +422,9 @@ export class DatabaseDetailsPage extends React.Component< ), cell: table => { + let cell; if (table.details.hasIndexRecommendations) { - return ( + cell = (
); + } else { + cell = table.details.indexCount; } - return table.details.indexCount; + return this.checkInfoAvailable(table.stats.lastError, cell); }, sort: table => table.details.indexCount, className: cx("database-table__col-index-count"), @@ -383,7 +453,11 @@ export class DatabaseDetailsPage extends React.Component< Regions ), - cell: table => table.stats.nodesByRegionString || "None", + cell: table => + this.checkInfoAvailable( + table.stats.lastError, + table.stats.nodesByRegionString || "None", + ), sort: table => table.stats.nodesByRegionString, className: cx("database-table__col--regions"), name: "regions", @@ -408,7 +482,11 @@ export class DatabaseDetailsPage extends React.Component< % of Live Data ), - cell: table => this.formatMVCCInfo(table.details), + cell: table => + this.checkInfoAvailable( + table.stats.lastError, + this.formatMVCCInfo(table.details), + ), sort: table => table.details.livePercentage, className: cx("database-table__col-column-count"), name: "livePercentage", @@ -460,7 +538,11 @@ export class DatabaseDetailsPage extends React.Component< Users ), - cell: table => table.details.userCount, + cell: table => + this.checkInfoAvailable( + table.details.lastError, + table.details.userCount, + ), sort: table => table.details.userCount, className: cx("database-table__col-user-count"), name: "userCount", @@ -471,7 +553,11 @@ export class DatabaseDetailsPage extends React.Component< Roles ), - cell: table => table.details.roles.join(", "), + cell: table => + this.checkInfoAvailable( + table.details.lastError, + table.details.roles.join(", "), + ), sort: table => table.details.roles.join(", "), className: cx("database-table__col-roles"), name: "roles", @@ -482,7 +568,11 @@ export class DatabaseDetailsPage extends React.Component< Grants ), - cell: table => table.details.grants.join(", "), + cell: table => + this.checkInfoAvailable( + table.details.lastError, + table.details.grants.join(", "), + ), sort: table => table.details.grants.join(", "), className: cx("database-table__col-grants"), name: "grants", @@ -555,24 +645,61 @@ export class DatabaseDetailsPage extends React.Component< />
- - - - This database has no tables. - + page={"databases"} + error={this.props.lastError} + render={() => ( + + + This database has no tables. + + } + /> + )} + renderError={() => + LoadingError({ + statsType: "databases", + timeout: this.props.lastError?.name + ?.toLowerCase() + .includes("timeout"), + }) } /> + {!this.props.loading && ( + <>} + renderError={() => + LoadingError({ + statsType: "part of the information", + timeout: + this.state.lastDetailsError?.name + ?.toLowerCase() + .includes("timeout") || + this.state.lastStatsError?.name + ?.toLowerCase() + .includes("timeout"), + }) + } + /> + )} - - - - - - - - - - - - - - {this.props.details.statsLastUpdated && ( - - )} - {this.props.automaticStatsCollectionEnabled != null && ( - - {" "} - Automatic statistics can help improve query - performance. Learn how to{" "} - ( + <> + + + + + + + + + + + + + + {this.props.details.statsLastUpdated && ( + - manage statistics collection - - . - - } - /> - )} - - - - - - {this.props.showNodeRegionsSection && ( - - )} - - - - - - - -
- Index Stats -
- + )} + {this.props.automaticStatsCollectionEnabled != + null && ( + + {" "} + Automatic statistics can help improve query + performance. Learn how to{" "} + + manage statistics collection + + . + + } + /> + )} + + + + + + {this.props.showNodeRegionsSection && ( + + )} + + + + + + + - -
- - - + loading={this.props.indexStats.loading} + /> + + + + )} + renderError={() => + LoadingError({ + statsType: "databases", + timeout: + this.props.details.lastError?.name + ?.toLowerCase() + .includes("timeout") || + this.props.stats.lastError?.name + ?.toLowerCase() + .includes("timeout"), + }) + } + /> - ( + + )} + renderError={() => + LoadingError({ + statsType: "databases", + timeout: this.props.details.lastError?.name + ?.toLowerCase() + .includes("timeout"), + }) + } /> diff --git a/pkg/ui/workspaces/cluster-ui/src/databasesPage/databasesPage.tsx b/pkg/ui/workspaces/cluster-ui/src/databasesPage/databasesPage.tsx index 61bf70f4f9f0..8e4f7591e14a 100644 --- a/pkg/ui/workspaces/cluster-ui/src/databasesPage/databasesPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/databasesPage/databasesPage.tsx @@ -36,6 +36,8 @@ import { import { syncHistory, tableStatsClusterSetting } from "src/util"; import booleanSettingStyles from "../settings/booleanSetting.module.scss"; import { CircleFilled } from "../icon"; +import LoadingError from "../sqlActivity/errorComponent"; +import { Loading } from "../loading"; const cx = classNames.bind(styles); const sortableTableCx = classNames.bind(sortableTableStyles); @@ -53,6 +55,7 @@ const booleanSettingCx = classnames.bind(booleanSettingStyles); // interface DatabasesPageData { // loading: boolean; // loaded: boolean; +// lastError: Error; // sortSetting: SortSetting; // databases: { // DatabasesPageDataDatabase[] // loading: boolean; @@ -71,6 +74,7 @@ const booleanSettingCx = classnames.bind(booleanSettingStyles); export interface DatabasesPageData { loading: boolean; loaded: boolean; + lastError: Error; databases: DatabasesPageDataDatabase[]; sortSetting: SortSetting; automaticStatsCollectionEnabled?: boolean; @@ -80,6 +84,7 @@ export interface DatabasesPageData { export interface DatabasesPageDataDatabase { loading: boolean; loaded: boolean; + lastError: Error; name: string; sizeInBytes: number; tableCount: number; @@ -122,6 +127,7 @@ export type DatabasesPageProps = DatabasesPageData & interface DatabasesPageState { pagination: ISortedTablePagination; + lastDetailsError: Error; } class DatabasesSortedTable extends SortedTable {} @@ -138,6 +144,7 @@ export class DatabasesPage extends React.Component< current: 1, pageSize: 20, }, + lastDetailsError: null, }; const { history } = this.props; @@ -173,12 +180,30 @@ export class DatabasesPage extends React.Component< this.props.refreshSettings(); } - if (!this.props.loaded && !this.props.loading) { + if ( + !this.props.loaded && + !this.props.loading && + this.props.lastError === undefined + ) { return this.props.refreshDatabases(); } + let lastDetailsError: Error; this.props.databases.forEach(database => { - if (!database.loaded && !database.loading) { + if (database.lastError !== undefined) { + lastDetailsError = database.lastError; + } + if ( + lastDetailsError && + this.state.lastDetailsError?.name != lastDetailsError?.name + ) { + this.setState({ lastDetailsError: lastDetailsError }); + } + if ( + !database.loaded && + !database.loading && + database.lastError === undefined + ) { return this.props.refreshDatabaseDetails(database.name); } @@ -230,6 +255,16 @@ export class DatabasesPage extends React.Component< ); }; + checkInfoAvailable = ( + database: DatabasesPageDataDatabase, + cell: React.ReactNode, + ): React.ReactNode => { + if (database.lastError) { + return "(unavailable)"; + } + return cell; + }; + private columns: ColumnDescriptor[] = [ { title: ( @@ -259,7 +294,8 @@ export class DatabasesPage extends React.Component< Size ), - cell: database => format.Bytes(database.sizeInBytes), + cell: database => + this.checkInfoAvailable(database, format.Bytes(database.sizeInBytes)), sort: database => database.sizeInBytes, className: cx("databases-table__col-size"), name: "size", @@ -273,7 +309,7 @@ export class DatabasesPage extends React.Component< Tables ), - cell: database => database.tableCount, + cell: database => this.checkInfoAvailable(database, database.tableCount), sort: database => database.tableCount, className: cx("databases-table__col-table-count"), name: "tableCount", @@ -287,7 +323,7 @@ export class DatabasesPage extends React.Component< Range Count ), - cell: database => database.rangeCount, + cell: database => this.checkInfoAvailable(database, database.rangeCount), sort: database => database.rangeCount, className: cx("databases-table__col-range-count"), name: "rangeCount", @@ -301,7 +337,11 @@ export class DatabasesPage extends React.Component< Regions/Nodes ), - cell: database => database.nodesByRegionString || "None", + cell: database => + this.checkInfoAvailable( + database, + database.nodesByRegionString || "None", + ), sort: database => database.nodesByRegionString, className: cx("databases-table__col-node-regions"), name: "nodeRegions", @@ -370,23 +410,57 @@ export class DatabasesPage extends React.Component<
- - - This cluster has no databases. - + page={"databases"} + error={this.props.lastError} + render={() => ( + + + This cluster has no databases. + + } + /> + )} + renderError={() => + LoadingError({ + statsType: "databases", + timeout: this.props.lastError?.name + ?.toLowerCase() + .includes("timeout"), + }) } /> + {!this.props.loading && ( + <>} + renderError={() => + LoadingError({ + statsType: "part of the information", + timeout: this.state.lastDetailsError?.name + ?.toLowerCase() + .includes("timeout"), + }) + } + /> + )} { error={this.props.sessionError} render={this.renderContent} renderError={() => - SQLActivityError({ + LoadingError({ statsType: "sessions", }) } diff --git a/pkg/ui/workspaces/cluster-ui/src/sessions/sessionsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/sessions/sessionsPage.tsx index 7ecf49c32022..7f700869d943 100644 --- a/pkg/ui/workspaces/cluster-ui/src/sessions/sessionsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/sessions/sessionsPage.tsx @@ -23,7 +23,7 @@ import classNames from "classnames/bind"; import { sessionsTable } from "src/util/docs"; import emptyTableResultsIcon from "../assets/emptyState/empty-table-results.svg"; -import SQLActivityError from "../sqlActivity/errorComponent"; +import LoadingError from "../sqlActivity/errorComponent"; import { Pagination } from "src/pagination"; import { SortSetting, @@ -464,7 +464,7 @@ export class SessionsPage extends React.Component< error={this.props.sessionsError} render={this.renderSessions} renderError={() => - SQLActivityError({ + LoadingError({ statsType: "sessions", }) } diff --git a/pkg/ui/workspaces/cluster-ui/src/sqlActivity/errorComponent.tsx b/pkg/ui/workspaces/cluster-ui/src/sqlActivity/errorComponent.tsx index d7edf00cd72d..dc053af8c690 100644 --- a/pkg/ui/workspaces/cluster-ui/src/sqlActivity/errorComponent.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/sqlActivity/errorComponent.tsx @@ -19,7 +19,7 @@ interface SQLActivityErrorProps { timeout?: boolean; } -const SQLActivityError: React.FC = props => { +const LoadingError: React.FC = props => { const error = props.timeout ? "a timeout" : "an unexpected error"; return (
@@ -37,4 +37,4 @@ const SQLActivityError: React.FC = props => { ); }; -export default SQLActivityError; +export default LoadingError; diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx index 2c82c0db89b1..07fc7033c1c3 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx @@ -57,7 +57,7 @@ import { timeScaleToString, toRoundedDateRange, } from "../timeScaleDropdown"; -import SQLActivityError from "../sqlActivity/errorComponent"; +import LoadingError from "../sqlActivity/errorComponent"; import { ActivateDiagnosticsModalRef, ActivateStatementDiagnosticsModal, @@ -390,7 +390,7 @@ export class StatementDetails extends React.Component< error={error} render={this.renderTabs} renderError={() => - SQLActivityError({ + LoadingError({ statsType: "statements", }) } @@ -478,7 +478,7 @@ export class StatementDetails extends React.Component< {hasTimeout && ( = ({ page="active statements" error={sessionsError} renderError={() => - SQLActivityError({ + LoadingError({ statsType: "statements", }) } diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx index be92e398c4ab..45019c9c84dd 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.tsx @@ -66,7 +66,7 @@ import { UIConfigState } from "../store"; import { StatementsRequest } from "src/api/statementsApi"; import Long from "long"; import ClearStats from "../sqlActivity/clearStats"; -import SQLActivityError from "../sqlActivity/errorComponent"; +import LoadingError from "../sqlActivity/errorComponent"; import { getValidOption, TimeScale, @@ -744,7 +744,7 @@ export class StatementsPage extends React.Component< error={this.props.statementsError} render={() => this.renderStatements(regions)} renderError={() => - SQLActivityError({ + LoadingError({ statsType: "statements", timeout: this.props.statementsError?.name ?.toLowerCase() diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx index e11e67a5798f..6178e78b0ae6 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx @@ -44,7 +44,7 @@ import { unset, } from "src/util"; import { UIConfigState } from "../store"; -import SQLActivityError from "../sqlActivity/errorComponent"; +import LoadingError from "../sqlActivity/errorComponent"; import summaryCardStyles from "../summaryCard/summaryCard.module.scss"; import transactionDetailsStyles from "./transactionDetails.modules.scss"; @@ -484,7 +484,7 @@ export class TransactionDetails extends React.Component< ); }} renderError={() => - SQLActivityError({ + LoadingError({ statsType: "transactions", }) } diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/activeTransactionsView.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/activeTransactionsView.tsx index 667a7993f7df..2382dd7207ac 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/activeTransactionsView.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/activeTransactionsView.tsx @@ -23,7 +23,7 @@ import { ActiveStatementFilters, ActiveTransactionFilters, } from "src/activeExecutions"; -import SQLActivityError from "src/sqlActivity/errorComponent"; +import LoadingError from "src/sqlActivity/errorComponent"; import { calculateActiveFilters, Filter, @@ -192,7 +192,7 @@ export const ActiveTransactionsView: React.FC = ({ page="active transactions" error={sessionsError} renderError={() => - SQLActivityError({ + LoadingError({ statsType: "transactions", }) } diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx index 46915d17503d..3e2dec353ed0 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPage.tsx @@ -58,7 +58,7 @@ import { StatisticTableColumnKeys, } from "../statsTableUtil/statsTableUtil"; import ClearStats from "../sqlActivity/clearStats"; -import SQLActivityError from "../sqlActivity/errorComponent"; +import LoadingError from "../sqlActivity/errorComponent"; import { commonStyles } from "../common"; import { TimeScaleDropdown, @@ -578,7 +578,7 @@ export class TransactionsPage extends React.Component< ); }} renderError={() => - SQLActivityError({ + LoadingError({ statsType: "transactions", timeout: this.props?.error?.name ?.toLowerCase() diff --git a/pkg/ui/workspaces/db-console/src/redux/apiReducers.ts b/pkg/ui/workspaces/db-console/src/redux/apiReducers.ts index 9903d33eeaa2..e1060d9815de 100644 --- a/pkg/ui/workspaces/db-console/src/redux/apiReducers.ts +++ b/pkg/ui/workspaces/db-console/src/redux/apiReducers.ts @@ -94,6 +94,8 @@ export const refreshLocations = locationsReducerObj.refresh; const databasesReducerObj = new CachedDataReducer( api.getDatabaseList, "databases", + null, + moment.duration(10, "m"), ); export const refreshDatabases = databasesReducerObj.refresh; @@ -105,6 +107,8 @@ const databaseDetailsReducerObj = new KeyedCachedDataReducer( api.getDatabaseDetails, "databaseDetails", databaseRequestToID, + null, + moment.duration(10, "m"), ); const hotRangesRequestToID = (req: api.HotRangesRequestMessage) => @@ -137,6 +141,8 @@ const tableDetailsReducerObj = new KeyedCachedDataReducer( api.getTableDetails, "tableDetails", tableRequestToID, + null, + moment.duration(10, "m"), ); export const refreshTableDetails = tableDetailsReducerObj.refresh; @@ -144,6 +150,8 @@ const tableStatsReducerObj = new KeyedCachedDataReducer( api.getTableStats, "tableStats", tableRequestToID, + null, + moment.duration(10, "m"), ); export const refreshTableStats = tableStatsReducerObj.refresh; 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 4c8c5fb4ffb1..d5c7d769df7c 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 @@ -125,6 +125,7 @@ describe("Database Details Page", function () { driver.assertProperties({ loading: false, loaded: false, + lastError: undefined, name: "things", showNodeRegionsColumn: false, viewMode: ViewMode.Tables, @@ -144,6 +145,7 @@ describe("Database Details Page", function () { driver.assertProperties({ loading: false, loaded: true, + lastError: null, name: "things", showNodeRegionsColumn: false, viewMode: ViewMode.Tables, @@ -155,6 +157,7 @@ describe("Database Details Page", function () { details: { loading: false, loaded: false, + lastError: undefined, columnCount: 0, indexCount: 0, userCount: 0, @@ -169,6 +172,7 @@ describe("Database Details Page", function () { stats: { loading: false, loaded: false, + lastError: undefined, replicationSizeInBytes: 0, rangeCount: 0, nodesByRegionString: "", @@ -179,6 +183,7 @@ describe("Database Details Page", function () { details: { loading: false, loaded: false, + lastError: undefined, columnCount: 0, indexCount: 0, userCount: 0, @@ -193,6 +198,7 @@ describe("Database Details Page", function () { stats: { loading: false, loaded: false, + lastError: undefined, replicationSizeInBytes: 0, rangeCount: 0, nodesByRegionString: "", @@ -326,6 +332,7 @@ describe("Database Details Page", function () { driver.assertTableDetails("foo", { loading: false, loaded: true, + lastError: null, columnCount: 5, indexCount: 3, userCount: 2, @@ -343,6 +350,7 @@ describe("Database Details Page", function () { driver.assertTableDetails("bar", { loading: false, loaded: true, + lastError: null, columnCount: 4, indexCount: 1, userCount: 3, @@ -442,6 +450,7 @@ describe("Database Details Page", function () { driver.assertTableStats("foo", { loading: false, loaded: true, + lastError: null, replicationSizeInBytes: 44040192, rangeCount: 4200, nodesByRegionString: "", @@ -450,6 +459,7 @@ describe("Database Details Page", function () { driver.assertTableStats("bar", { loading: false, loaded: true, + lastError: null, replicationSizeInBytes: 8675309, rangeCount: 1023, 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 367f7922b678..2b16d9a7184d 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 @@ -115,6 +115,7 @@ export const mapStateToProps = createSelector( return { loading: !!databaseDetails[database]?.inFlight, loaded: !!databaseDetails[database]?.valid, + lastError: databaseDetails[database]?.lastError, name: database, showNodeRegionsColumn, viewMode, @@ -139,6 +140,7 @@ export const mapStateToProps = createSelector( details: { loading: !!details?.inFlight, loaded: !!details?.valid, + lastError: details?.lastError, columnCount: details?.data?.columns?.length || 0, indexCount: numIndexes, userCount: roles.length, @@ -158,6 +160,7 @@ export const mapStateToProps = createSelector( stats: { loading: !!stats?.inFlight, loaded: !!stats?.valid, + lastError: stats?.lastError, replicationSizeInBytes: FixLong( stats?.data?.approximate_disk_bytes || 0, ).toNumber(), diff --git a/pkg/ui/workspaces/db-console/src/views/databases/databaseTablePage/redux.spec.ts b/pkg/ui/workspaces/db-console/src/views/databases/databaseTablePage/redux.spec.ts index cf863c31b22a..c1f4ed2ab6a5 100644 --- a/pkg/ui/workspaces/db-console/src/views/databases/databaseTablePage/redux.spec.ts +++ b/pkg/ui/workspaces/db-console/src/views/databases/databaseTablePage/redux.spec.ts @@ -169,6 +169,7 @@ describe("Database Table Page", function () { details: { loading: false, loaded: false, + lastError: undefined, createStatement: "", replicaCount: 0, indexNames: [], @@ -182,6 +183,7 @@ describe("Database Table Page", function () { stats: { loading: false, loaded: false, + lastError: undefined, sizeInBytes: 0, rangeCount: 0, nodesByRegionString: "", @@ -189,6 +191,7 @@ describe("Database Table Page", function () { indexStats: { loading: false, loaded: false, + lastError: undefined, stats: [], lastReset: null, }, @@ -223,6 +226,7 @@ describe("Database Table Page", function () { driver.assertTableDetails({ loading: false, loaded: true, + lastError: null, createStatement: "CREATE TABLE foo", replicaCount: 5, indexNames: ["primary", "another_index"], @@ -251,6 +255,7 @@ describe("Database Table Page", function () { driver.assertTableStats({ loading: false, loaded: true, + lastError: null, sizeInBytes: 44040192, rangeCount: 4200, nodesByRegionString: "", @@ -306,6 +311,7 @@ describe("Database Table Page", function () { driver.assertIndexStats({ loading: false, loaded: true, + lastError: null, stats: [ { indexName: "jobs_status_created_idx", diff --git a/pkg/ui/workspaces/db-console/src/views/databases/databaseTablePage/redux.ts b/pkg/ui/workspaces/db-console/src/views/databases/databaseTablePage/redux.ts index abec8fcdd42e..e83a3606e6de 100644 --- a/pkg/ui/workspaces/db-console/src/views/databases/databaseTablePage/redux.ts +++ b/pkg/ui/workspaces/db-console/src/views/databases/databaseTablePage/redux.ts @@ -124,6 +124,7 @@ export const mapStateToProps = createSelector( details: { loading: !!details?.inFlight, loaded: !!details?.valid, + lastError: details?.lastError, createStatement: details?.data?.create_table_statement || "", replicaCount: details?.data?.zone_config?.num_replicas || 0, indexNames: _.uniq(_.map(details?.data?.indexes, index => index.name)), @@ -140,6 +141,7 @@ export const mapStateToProps = createSelector( stats: { loading: !!stats?.inFlight, loaded: !!stats?.valid, + lastError: stats?.lastError, sizeInBytes: FixLong( stats?.data?.approximate_disk_bytes || 0, ).toNumber(), @@ -149,6 +151,7 @@ export const mapStateToProps = createSelector( indexStats: { loading: !!indexStats?.inFlight, loaded: !!indexStats?.valid, + lastError: indexStats?.lastError, stats: indexStatsData, lastReset: lastReset, }, diff --git a/pkg/ui/workspaces/db-console/src/views/databases/databasesPage/redux.spec.ts b/pkg/ui/workspaces/db-console/src/views/databases/databasesPage/redux.spec.ts index fd742fad7765..8f39d7c54b14 100644 --- a/pkg/ui/workspaces/db-console/src/views/databases/databasesPage/redux.spec.ts +++ b/pkg/ui/workspaces/db-console/src/views/databases/databasesPage/redux.spec.ts @@ -104,6 +104,7 @@ describe("Databases Page", function () { driver.assertProperties({ loading: false, loaded: false, + lastError: undefined, databases: [], sortSetting: { ascending: true, columnTitle: "name" }, automaticStatsCollectionEnabled: true, @@ -127,10 +128,12 @@ describe("Databases Page", function () { driver.assertProperties({ loading: false, loaded: true, + lastError: null, databases: [ { loading: false, loaded: false, + lastError: undefined, name: "system", sizeInBytes: 0, tableCount: 0, @@ -142,6 +145,7 @@ describe("Databases Page", function () { { loading: false, loaded: false, + lastError: undefined, name: "test", sizeInBytes: 0, tableCount: 0, @@ -187,6 +191,7 @@ describe("Databases Page", function () { driver.assertDatabaseProperties("system", { loading: false, loaded: true, + lastError: null, name: "system", sizeInBytes: 7168, tableCount: 2, @@ -199,6 +204,7 @@ describe("Databases Page", function () { driver.assertDatabaseProperties("test", { loading: false, loaded: true, + lastError: null, name: "test", sizeInBytes: 1234, tableCount: 1, @@ -231,6 +237,7 @@ describe("Databases Page", function () { driver.assertDatabaseProperties("system", { loading: false, loaded: true, + lastError: null, name: "system", sizeInBytes: 7168, tableCount: 2, @@ -267,6 +274,7 @@ describe("Databases Page", function () { driver.assertDatabaseProperties("system", { loading: false, loaded: true, + lastError: null, name: "system", sizeInBytes: 8192, tableCount: 2, @@ -294,6 +302,7 @@ describe("Databases Page", function () { driver.assertDatabaseProperties("system", { loading: false, loaded: true, + lastError: null, name: "system", sizeInBytes: 0, tableCount: 2, @@ -333,6 +342,7 @@ describe("Databases Page", function () { driver.assertDatabaseProperties("system", { loading: false, loaded: true, + lastError: null, name: "system", sizeInBytes: 7168, tableCount: 2, @@ -347,6 +357,7 @@ describe("Databases Page", function () { driver.assertDatabaseProperties("system", { loading: false, loaded: true, + lastError: null, name: "system", sizeInBytes: 8192, tableCount: 2, diff --git a/pkg/ui/workspaces/db-console/src/views/databases/databasesPage/redux.ts b/pkg/ui/workspaces/db-console/src/views/databases/databasesPage/redux.ts index 216330507a7c..447d12b67fac 100644 --- a/pkg/ui/workspaces/db-console/src/views/databases/databasesPage/redux.ts +++ b/pkg/ui/workspaces/db-console/src/views/databases/databasesPage/redux.ts @@ -46,6 +46,11 @@ const selectLoaded = createSelector( databases => databases.valid, ); +const selectLastError = createSelector( + (state: AdminUIState) => state.cachedData.databases, + databases => databases.lastError, +); + const sortSettingLocalSetting = new LocalSetting( "sortSetting/DatabasesPage", (state: AdminUIState) => state.localSettings, @@ -106,6 +111,7 @@ const selectDatabases = createSelector( return { loading: !!details?.inFlight, loaded: !!details?.valid, + lastError: details?.lastError, name: database, sizeInBytes: sizeInBytes, tableCount: details?.data?.table_names?.length || 0, @@ -125,6 +131,7 @@ const selectDatabases = createSelector( export const mapStateToProps = (state: AdminUIState): DatabasesPageData => ({ loading: selectLoading(state), loaded: selectLoaded(state), + lastError: selectLastError(state), databases: selectDatabases(state), sortSetting: sortSettingLocalSetting.selector(state), automaticStatsCollectionEnabled: selectAutomaticStatsCollectionEnabled(state),