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),