Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release-22.1: ui: handle errors on db endpoints #91013

Merged
merged 1 commit into from
Oct 31, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
maryliag committed Oct 31, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit d29f8e0144dc7d9d205a93fdfd66b81a73dcf0a4
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@ const history = H.createHashHistory();
const withLoadingIndicator: DatabaseDetailsPageProps = {
loading: true,
loaded: false,
lastError: undefined,
name: randomName(),
tables: [],
viewMode: ViewMode.Tables,
@@ -60,6 +61,7 @@ const withLoadingIndicator: DatabaseDetailsPageProps = {
const withoutData: DatabaseDetailsPageProps = {
loading: false,
loaded: true,
lastError: null,
name: randomName(),
tables: [],
viewMode: ViewMode.Tables,
@@ -89,6 +91,7 @@ const withoutData: DatabaseDetailsPageProps = {
const withData: DatabaseDetailsPageProps = {
loading: false,
loaded: true,
lastError: null,
name: randomName(),
tables: _.map(Array(42), _item => {
const roles = _.uniq(
@@ -103,6 +106,7 @@ const withData: DatabaseDetailsPageProps = {
details: {
loading: false,
loaded: true,
lastError: null,
columnCount: _.random(5, 42),
indexCount: _.random(1, 6),
userCount: roles.length,
@@ -114,6 +118,7 @@ const withData: DatabaseDetailsPageProps = {
stats: {
loading: false,
loaded: true,
lastError: null,
replicationSizeInBytes: _.random(1000.0) * 1024 ** _.random(1, 2),
rangeCount: _.random(50, 500),
nodesByRegionString:
Original file line number Diff line number Diff line change
@@ -12,7 +12,6 @@ import React from "react";
import { Link, RouteComponentProps } from "react-router-dom";
import { Tooltip } from "antd";
import classNames from "classnames/bind";
import _ from "lodash";

import { Breadcrumbs } from "src/breadcrumbs";
import { Dropdown, DropdownOption } from "src/dropdown";
@@ -38,6 +37,8 @@ import {
} from "src/transactionsPage/transactionsPageClasses";
import { Moment } from "moment";
import { DATE_FORMAT } from "src/util/format";
import LoadingError from "../sqlActivity/errorComponent";
import { Loading } from "../loading";

const cx = classNames.bind(styles);
const sortableTableCx = classNames.bind(sortableTableStyles);
@@ -54,6 +55,7 @@ const sortableTableCx = classNames.bind(sortableTableStyles);
// interface DatabaseDetailsPageData {
// loading: boolean;
// loaded: boolean;
// lastError: Error;
// name: string;
// sortSettingTables: SortSetting;
// sortSettingGrants: SortSetting;
@@ -63,6 +65,7 @@ const sortableTableCx = classNames.bind(sortableTableStyles);
// details: { // DatabaseDetailsPageDataTableDetails
// loading: boolean;
// loaded: boolean;
// lastError: Error;
// columnCount: number;
// indexCount: number;
// userCount: number;
@@ -72,6 +75,7 @@ const sortableTableCx = classNames.bind(sortableTableStyles);
// stats: { // DatabaseDetailsPageDataTableStats
// loading: boolean;
// loaded: boolean;
// lastError: Error;
// replicationSizeInBytes: number;
// rangeCount: number;
// nodesByRegionString: string;
@@ -81,6 +85,7 @@ const sortableTableCx = classNames.bind(sortableTableStyles);
export interface DatabaseDetailsPageData {
loading: boolean;
loaded: boolean;
lastError: Error;
name: string;
tables: DatabaseDetailsPageDataTable[];
sortSettingTables: SortSetting;
@@ -98,6 +103,7 @@ export interface DatabaseDetailsPageDataTable {
export interface DatabaseDetailsPageDataTableDetails {
loading: boolean;
loaded: boolean;
lastError: Error;
columnCount: number;
indexCount: number;
userCount: number;
@@ -109,6 +115,7 @@ export interface DatabaseDetailsPageDataTableDetails {
export interface DatabaseDetailsPageDataTableStats {
loading: boolean;
loaded: boolean;
lastError: Error;
replicationSizeInBytes: number;
rangeCount: number;
nodesByRegionString?: string;
@@ -134,6 +141,8 @@ export enum ViewMode {

interface DatabaseDetailsPageState {
pagination: ISortedTablePagination;
lastStatsError: Error;
lastDetailsError: Error;
}

class DatabaseSortedTable extends SortedTable<DatabaseDetailsPageDataTable> {}
@@ -149,6 +158,8 @@ export class DatabaseDetailsPage extends React.Component<
current: 1,
pageSize: 20,
},
lastDetailsError: null,
lastStatsError: null,
};

const { history } = this.props;
@@ -199,16 +210,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);
}

_.forEach(this.props.tables, table => {
if (!table.details.loaded && !table.details.loading) {
let lastDetailsError: Error;
let lastStatsError: Error;
this.props.tables.forEach(table => {
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);
}
});
@@ -259,6 +303,16 @@ export class DatabaseDetailsPage extends React.Component<
}
}

checkInfoAvailable = (
error: Error,
cell: React.ReactNode,
): React.ReactNode => {
if (error) {
return "(unavailable)";
}
return cell;
};

private columnsForTablesViewMode(): ColumnDescriptor<
DatabaseDetailsPageDataTable
>[] {
@@ -291,7 +345,11 @@ export class DatabaseDetailsPage extends React.Component<
Replication Size
</Tooltip>
),
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",
@@ -305,7 +363,11 @@ export class DatabaseDetailsPage extends React.Component<
Ranges
</Tooltip>
),
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",
@@ -319,7 +381,11 @@ export class DatabaseDetailsPage extends React.Component<
Columns
</Tooltip>
),
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",
@@ -333,7 +399,11 @@ export class DatabaseDetailsPage extends React.Component<
Indexes
</Tooltip>
),
cell: table => table.details.indexCount,
cell: table =>
this.checkInfoAvailable(
table.stats.lastError,
table.details.indexCount,
),
sort: table => table.details.indexCount,
className: cx("database-table__col-index-count"),
name: "indexCount",
@@ -347,7 +417,11 @@ export class DatabaseDetailsPage extends React.Component<
Regions
</Tooltip>
),
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",
@@ -403,7 +477,11 @@ export class DatabaseDetailsPage extends React.Component<
Users
</Tooltip>
),
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",
@@ -414,8 +492,12 @@ export class DatabaseDetailsPage extends React.Component<
Roles
</Tooltip>
),
cell: table => _.join(table.details.roles, ", "),
sort: table => _.join(table.details.roles, ", "),
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",
},
@@ -425,8 +507,12 @@ export class DatabaseDetailsPage extends React.Component<
Grants
</Tooltip>
),
cell: table => _.join(table.details.grants, ", "),
sort: table => _.join(table.details.grants, ", "),
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",
},
@@ -498,24 +584,61 @@ export class DatabaseDetailsPage extends React.Component<
/>
</h4>
</div>

<DatabaseSortedTable
className={cx("database-table")}
data={this.props.tables}
columns={this.columns()}
sortSetting={sortSetting}
onChangeSortSetting={this.changeSortSetting}
pagination={this.state.pagination}
<Loading
loading={this.props.loading}
renderNoResult={
<div
className={cx("database-table__no-result", "icon__container")}
>
<DatabaseIcon className={cx("icon--s")} />
This database has no tables.
</div>
page={"databases"}
error={this.props.lastError}
render={() => (
<DatabaseSortedTable
className={cx("database-table")}
data={this.props.tables}
columns={this.columns()}
sortSetting={sortSetting}
onChangeSortSetting={this.changeSortSetting}
pagination={this.state.pagination}
loading={this.props.loading}
renderNoResult={
<div
className={cx(
"database-table__no-result",
"icon__container",
)}
>
<DatabaseIcon className={cx("icon--s")} />
This database has no tables.
</div>
}
/>
)}
renderError={() =>
LoadingError({
statsType: "databases",
timeout: this.props.lastError?.name
?.toLowerCase()
.includes("timeout"),
})
}
/>
{!this.props.loading && (
<Loading
loading={this.props.loading}
page={"database_details"}
error={this.state.lastDetailsError || this.state.lastStatsError}
render={() => <></>}
renderError={() =>
LoadingError({
statsType: "part of the information",
timeout:
this.state.lastDetailsError?.name
?.toLowerCase()
.includes("timeout") ||
this.state.lastStatsError?.name
?.toLowerCase()
.includes("timeout"),
})
}
/>
)}
</section>

<Pagination
Loading