Skip to content

Commit

Permalink
Merge pull request #103593 from THardy98/backport22.2-92589
Browse files Browse the repository at this point in the history
release-22.2: ui: Add search and filtering to the databases pages
  • Loading branch information
Thomas Hardy authored May 18, 2023
2 parents 9dd87a4 + bca5cda commit f8e53b7
Show file tree
Hide file tree
Showing 13 changed files with 706 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {

import * as H from "history";
import moment from "moment";
import { defaultFilters } from "src/queryFilter";
const history = H.createHashHistory();

const withLoadingIndicator: DatabaseDetailsPageProps = {
Expand All @@ -49,6 +50,9 @@ const withLoadingIndicator: DatabaseDetailsPageProps = {
refreshDatabaseDetails: () => {},
refreshTableDetails: () => {},
refreshTableStats: () => {},
search: null,
filters: defaultFilters,
nodeRegions: {},
location: history.location,
history,
match: {
Expand Down Expand Up @@ -79,6 +83,9 @@ const withoutData: DatabaseDetailsPageProps = {
refreshDatabaseDetails: () => {},
refreshTableDetails: () => {},
refreshTableStats: () => {},
search: null,
filters: defaultFilters,
nodeRegions: {},
location: history.location,
history,
match: {
Expand Down Expand Up @@ -144,6 +151,9 @@ const withData: DatabaseDetailsPageProps = {
refreshDatabaseDetails: () => {},
refreshTableDetails: () => {},
refreshTableStats: () => {},
search: null,
filters: defaultFilters,
nodeRegions: {},
location: history.location,
history,
match: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
} from "src/sortedtable";
import * as format from "src/util/format";
import { DATE_FORMAT } from "src/util/format";
import { mvccGarbage, syncHistory } from "../util";
import { mvccGarbage, syncHistory, unique } from "../util";

import styles from "./databaseDetailsPage.module.scss";
import sortableTableStyles from "src/sortedtable/sortedtable.module.scss";
Expand All @@ -41,6 +41,15 @@ import { Caution } from "@cockroachlabs/icons";
import { Anchor } from "../anchor";
import LoadingError from "../sqlActivity/errorComponent";
import { Loading } from "../loading";
import { Search } from "../search";
import {
Filter,
Filters,
defaultFilters,
calculateActiveFilters,
} from "src/queryFilter";
import { UIConfigState } from "src/store";
import { TableStatistics } from "src/tableStatistics";

const cx = classNames.bind(styles);
const sortableTableCx = classNames.bind(sortableTableStyles);
Expand All @@ -61,6 +70,10 @@ const sortableTableCx = classNames.bind(sortableTableStyles);
// name: string;
// sortSettingTables: SortSetting;
// sortSettingGrants: SortSetting;
// search: string;
// filters: Filters;
// nodeRegions: { [nodeId: string]: string };
// isTenant: boolean;
// viewMode: ViewMode;
// tables: { // DatabaseDetailsPageDataTable[]
// name: string;
Expand All @@ -80,6 +93,7 @@ const sortableTableCx = classNames.bind(sortableTableStyles);
// lastError: Error;
// replicationSizeInBytes: number;
// rangeCount: number;
// nodes: number[];
// nodesByRegionString: string;
// };
// }[];
Expand All @@ -92,6 +106,10 @@ export interface DatabaseDetailsPageData {
tables: DatabaseDetailsPageDataTable[];
sortSettingTables: SortSetting;
sortSettingGrants: SortSetting;
search: string;
filters: Filters;
nodeRegions: { [nodeId: string]: string };
isTenant?: UIConfigState["isTenant"];
viewMode: ViewMode;
showNodeRegionsColumn?: boolean;
}
Expand Down Expand Up @@ -124,13 +142,20 @@ export interface DatabaseDetailsPageDataTableStats {
lastError: Error;
replicationSizeInBytes: number;
rangeCount: number;
// 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.
// regionA(n1,n2), regionB(n3). Used for display in the table's
// "Regions/Nodes" column.
nodesByRegionString?: string;
}

export interface DatabaseDetailsPageActions {
refreshDatabaseDetails: (database: string) => void;
refreshTableDetails: (database: string, table: string) => void;
refreshTableStats: (database: string, table: string) => void;
onFilterChange?: (value: Filters) => void;
onSearchComplete?: (query: string) => void;
onSortingTablesChange?: (columnTitle: string, ascending: boolean) => void;
onSortingGrantsChange?: (columnTitle: string, ascending: boolean) => void;
onViewModeChange?: (viewMode: ViewMode) => void;
Expand All @@ -147,12 +172,35 @@ export enum ViewMode {

interface DatabaseDetailsPageState {
pagination: ISortedTablePagination;
filters?: Filters;
activeFilters?: number;
lastStatsError: Error;
lastDetailsError: Error;
}

class DatabaseSortedTable extends SortedTable<DatabaseDetailsPageDataTable> {}

// filterBySearchQuery returns true if the search query matches the database name.
function filterBySearchQuery(
table: DatabaseDetailsPageDataTable,
search: string,
): boolean {
const matchString = table.name.toLowerCase();

if (search.startsWith('"') && search.endsWith('"')) {
search = search.substring(1, search.length - 1);

return matchString.includes(search);
}

const res = search
.toLowerCase()
.split(" ")
.every(val => matchString.includes(val));

return res;
}

export class DatabaseDetailsPage extends React.Component<
DatabaseDetailsPageProps,
DatabaseDetailsPageState
Expand Down Expand Up @@ -286,6 +334,120 @@ export class DatabaseDetailsPage extends React.Component<
}
};

onClearSearchField = (): void => {
if (this.props.onSearchComplete) {
this.props.onSearchComplete("");
}

syncHistory(
{
q: undefined,
},
this.props.history,
);
};

onClearFilters = (): void => {
if (this.props.onFilterChange) {
this.props.onFilterChange(defaultFilters);
}

this.setState({
filters: defaultFilters,
activeFilters: 0,
});

this.resetPagination();
syncHistory(
{
regions: undefined,
nodes: undefined,
},
this.props.history,
);
};

onSubmitSearchField = (search: string): void => {
if (this.props.onSearchComplete) {
this.props.onSearchComplete(search);
}

this.resetPagination();
syncHistory(
{
q: search,
},
this.props.history,
);
};

onSubmitFilters = (filters: Filters): void => {
if (this.props.onFilterChange) {
this.props.onFilterChange(filters);
}

this.setState({
filters: filters,
activeFilters: calculateActiveFilters(filters),
});

this.resetPagination();
syncHistory(
{
regions: filters.regions,
nodes: filters.nodes,
},
this.props.history,
);
};

resetPagination = (): void => {
this.setState(prevState => {
return {
pagination: {
current: 1,
pageSize: prevState.pagination.pageSize,
},
};
});
};

// Returns a list of database tables to the display based on input from the
// search box and the applied filters.
filteredDatabaseTables = (): DatabaseDetailsPageDataTable[] => {
const { search, tables, filters, nodeRegions } = this.props;

const regionsSelected =
filters.regions.length > 0 ? filters.regions.split(",") : [];
const nodesSelected =
filters.nodes.length > 0 ? filters.nodes.split(",") : [];

return tables
.filter(table => (search ? filterBySearchQuery(table, search) : true))
.filter(table => {
if (regionsSelected.length == 0 && nodesSelected.length == 0)
return true;

let foundRegion = regionsSelected.length == 0;
let foundNode = nodesSelected.length == 0;

table.stats.nodes?.forEach(node => {
if (
foundRegion ||
regionsSelected.includes(nodeRegions[node.toString()])
) {
foundRegion = true;
}
if (foundNode || nodesSelected.includes("n" + node.toString())) {
foundNode = true;
}
if (foundNode && foundRegion) return true;
});

return foundRegion && foundNode;
});
};

private changeViewMode(viewMode: ViewMode) {
syncHistory(
{
Expand Down Expand Up @@ -594,11 +756,42 @@ export class DatabaseDetailsPage extends React.Component<
}

render(): React.ReactElement {
const { search, filters, isTenant, nodeRegions } = this.props;

const tablesToDisplay = this.filteredDatabaseTables();
const activeFilters = calculateActiveFilters(filters);

const nodes = Object.keys(nodeRegions)
.map(n => Number(n))
.sort();

const regions = unique(Object.values(nodeRegions));

const sortSetting =
this.props.viewMode == ViewMode.Tables
? this.props.sortSettingTables
: this.props.sortSettingGrants;

// Only show the filter component when the viewMode is Tables.
const filterComponent =
this.props.viewMode == ViewMode.Tables ? (
<PageConfigItem>
<Filter
hideAppNames={true}
regions={regions}
hideTimeLabel={true}
nodes={nodes.map(n => "n" + n.toString())}
activeFilters={activeFilters}
filters={defaultFilters}
onSubmitFilters={this.onSubmitFilters}
showNodes={!isTenant && nodes.length > 1}
showRegions={regions.length > 1}
/>
</PageConfigItem>
) : (
<></>
);

return (
<div className="root table-area">
<section className={baseHeadingClasses.wrapper}>
Expand Down Expand Up @@ -631,28 +824,33 @@ export class DatabaseDetailsPage extends React.Component<
View: {this.props.viewMode}
</Dropdown>
</PageConfigItem>
<PageConfigItem>
<Search
onSubmit={this.onSubmitSearchField}
onClear={this.onClearSearchField}
defaultValue={search}
placeholder={"Search Tables"}
/>
</PageConfigItem>
{filterComponent}
</PageConfig>

<section className={sortableTableCx("cl-table-container")}>
<div className={statisticsClasses.statistic}>
<h4 className={statisticsClasses.countTitle}>
<ResultsPerPageLabel
pagination={{
...this.state.pagination,
total: this.props.tables.length,
}}
pageName={this.props.tables.length == 1 ? "table" : "tables"}
/>
</h4>
</div>
<TableStatistics
pagination={this.state.pagination}
totalCount={tablesToDisplay.length}
arrayItemName="tables"
activeFilters={activeFilters}
onClearFilters={this.onClearFilters}
/>
<Loading
loading={this.props.loading}
page={"databases"}
error={this.props.lastError}
render={() => (
<DatabaseSortedTable
className={cx("database-table")}
data={this.props.tables}
data={tablesToDisplay}
columns={this.columns()}
sortSetting={sortSetting}
onChangeSortSetting={this.changeSortSetting}
Expand Down
Loading

0 comments on commit f8e53b7

Please sign in to comment.