Skip to content

Commit

Permalink
ui: save filters on cache for Transactions page
Browse files Browse the repository at this point in the history
Previously, a sort selection was not maintained when
the page change (e.g. changing tabs/pages).
This commits saves the selected value to be used.

Partially adresses #71851

Release note: None
  • Loading branch information
maryliag committed Nov 23, 2021
1 parent c482da4 commit 1cd2a34
Show file tree
Hide file tree
Showing 12 changed files with 432 additions and 116 deletions.
77 changes: 77 additions & 0 deletions pkg/ui/workspaces/cluster-ui/src/queryFilter/filter.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2021 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

import { Filters, getFiltersFromQueryString } from "./filter";

describe("Test filter functions", (): void => {
describe("Test get filters from query string", (): void => {
it("no values on query string", (): void => {
const expectedFilters: Filters = {
app: "",
timeNumber: "0",
timeUnit: "seconds",
fullScan: false,
sqlType: "",
database: "",
regions: "",
nodes: "",
};
const resultFilters = getFiltersFromQueryString("");
expect(resultFilters).toEqual(expectedFilters);
});
});

it("different values from default values on query string", (): void => {
const expectedFilters: Filters = {
app: "$ internal",
timeNumber: "1",
timeUnit: "milliseconds",
fullScan: true,
sqlType: "DML",
database: "movr",
regions: "us-central",
nodes: "n1,n2",
};
const resultFilters = getFiltersFromQueryString(
"app=%24+internal&timeNumber=1&timeUnit=milliseconds&fullScan=true&sqlType=DML&database=movr&regions=us-central&nodes=n1,n2",
);
expect(resultFilters).toEqual(expectedFilters);
});

it("testing boolean with full scan = true", (): void => {
const expectedFilters: Filters = {
app: "",
timeNumber: "0",
timeUnit: "seconds",
fullScan: true,
sqlType: "",
database: "",
regions: "",
nodes: "",
};
const resultFilters = getFiltersFromQueryString("fullScan=true");
expect(resultFilters).toEqual(expectedFilters);
});

it("testing boolean with full scan = false", (): void => {
const expectedFilters: Filters = {
app: "",
timeNumber: "0",
timeUnit: "seconds",
fullScan: false,
sqlType: "",
database: "",
regions: "",
nodes: "",
};
const resultFilters = getFiltersFromQueryString("fullScan=false");
expect(resultFilters).toEqual(expectedFilters);
});
});
105 changes: 103 additions & 2 deletions pkg/ui/workspaces/cluster-ui/src/queryFilter/filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import Select from "react-select";
import { Button } from "../button";
import { CaretDown } from "@cockroachlabs/icons";
import { Input } from "antd";
import { History } from "history";
import { isEqual } from "lodash";
import {
dropdownButton,
dropdownContentWrapper,
Expand All @@ -25,6 +27,7 @@ import {
checkbox,
} from "./filterClasses";
import { MultiSelectCheckbox } from "../multiSelectCheckbox/multiSelectCheckbox";
import { syncHistory } from "../util";

interface QueryFilter {
onSubmitFilters: (filters: Filters) => void;
Expand Down Expand Up @@ -100,14 +103,112 @@ export const getFiltersFromQueryString = (
const queryStringFilter = searchParams.get(filter);
const filterValue =
queryStringFilter == null
? defaultValue
: defaultValue.constructor(searchParams.get(filter));
? defaultValue // If this filter doesn't exist on query string, use default value.
: typeof defaultValue == "boolean"
? searchParams.get(filter) === "true" // If it's a Boolean, convert from String to Boolean;
: defaultValue.constructor(searchParams.get(filter)); // Otherwise, use the constructor for that class.
// Boolean is converted without using its own constructor because the value from the query
// params is a string and Boolean('false') = true, which would be incorrect.
return { [filter]: filterValue, ...filters };
},
{},
);
};

/**
* Get Filters from Query String and if its value is different from the current
* filters value, it calls the onFilterChange function.
* @param history History
* @param filters the current active filters
* @param onFilterChange function to be called if the values from the search
* params are different from the current ones. This function can update
* the value stored on localStorage for example
* @returns Filters the active filters
*/
export const handleFiltersFromQueryString = (
history: History,
filters: Filters,
onFilterChange: (value: Filters) => void,
): Filters => {
const filtersQueryString = getFiltersFromQueryString(history.location.search);
const searchParams = new URLSearchParams(history.location.search);
let hasFilter = false;

for (const key of Object.keys(defaultFilters)) {
if (searchParams.get(key)) {
hasFilter = true;
break;
}
}

if (onFilterChange && hasFilter && !isEqual(filtersQueryString, filters)) {
// If we have filters on query string and they're different
// from the current filter state on props (localStorage),
// we want to update the value on localStorage.
onFilterChange(filtersQueryString);
} else if (!isEqual(filters, defaultFilters)) {
// If the filters on props (localStorage) are different
// from the default values, we want to update the History,
// so the url can be easily shared with the filters selected.
syncHistory(
{
app: filters.app,
timeNumber: filters.timeNumber,
timeUnit: filters.timeUnit,
fullScan: filters.fullScan.toString(),
sqlType: filters.sqlType,
database: filters.database,
regions: filters.regions,
nodes: filters.nodes,
},
history,
);
}
// If we have a new filter selection on query params, they
// take precedent on what is stored on localStorage.
return hasFilter ? filtersQueryString : filters;
};

/**
* Update the query params to the current values of the Filter.
* When we change tabs inside the SQL Activity page for example,
* the constructor is called only on the first time.
* The component update event is called frequently and can be used to
* update the query params by using this function that only updates
* the query params if the values did change and we're on the correct tab.
* @param tab which the query params should update
* @param filters the current filters
* @param history
*/
export const updateFiltersQueryParamsOnTab = (
tab: string,
filters: Filters,
history: History,
): void => {
const filtersQueryString = getFiltersFromQueryString(history.location.search);
const searchParams = new URLSearchParams(history.location.search);
const currentTab = searchParams.get("tab") || "";
if (
currentTab === tab &&
!isEqual(filters, defaultFilters) &&
!isEqual(filters, filtersQueryString)
) {
syncHistory(
{
app: filters.app,
timeNumber: filters.timeNumber,
timeUnit: filters.timeUnit,
fullScan: filters.fullScan.toString(),
sqlType: filters.sqlType,
database: filters.database,
regions: filters.regions,
nodes: filters.nodes,
},
history,
);
}
};

/**
* The State of the filter that is consider inactive.
* It's different from defaultFilters because we don't want to take
Expand Down
88 changes: 86 additions & 2 deletions pkg/ui/workspaces/cluster-ui/src/sortedtable/sortedtable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import React from "react";
import _ from "lodash";
import * as Long from "long";
import { History } from "history";
import { Moment } from "moment";
import { createSelector } from "reselect";

Expand Down Expand Up @@ -319,7 +320,7 @@ export class SortedTable<T> extends React.Component<
return sortData ? sortData.slice(start, end) : data.slice(start, end);
};

render() {
render(): React.ReactElement {
const {
data,
loading,
Expand Down Expand Up @@ -397,7 +398,10 @@ export class SortedTable<T> extends React.Component<
* @param maxLength the max length to which it should display value
* and hide the remaining.
*/
export function longListWithTooltip(value: string, maxLength: number) {
export function longListWithTooltip(
value: string,
maxLength: number,
): React.ReactElement {
const summary =
value.length > maxLength ? value.slice(0, maxLength) + "..." : value;
return (
Expand All @@ -411,3 +415,83 @@ export function longListWithTooltip(value: string, maxLength: number) {
</Tooltip>
);
}

/**
* Get Sort Setting from Query String and if it's different from current
* sortSetting calls the onSortChange function.
* @param page the page where the table was added (used for analytics)
* @param queryString searchParams
* @param sortSetting the current sort Setting on the page
* @param onSortingChange function to be called if the values from the search
* params are different from the current ones. This function can update
* the value stored on localStorage for example.
*/
export const handleSortSettingFromQueryString = (
page: string,
queryString: string,
sortSetting: SortSetting,
onSortingChange: (
name: string,
columnTitle: string,
ascending: boolean,
) => void,
): void => {
const searchParams = new URLSearchParams(queryString);
const ascending = (searchParams.get("ascending") || undefined) === "true";
const columnTitle = searchParams.get("columnTitle") || undefined;
if (
onSortingChange &&
columnTitle &&
(sortSetting.columnTitle != columnTitle ||
sortSetting.ascending != ascending)
) {
onSortingChange(page, columnTitle, ascending);
}
};

/**
* Update the query params to the current values of the Sort Setting.
* When we change tabs inside the SQL Activity page for example,
* the constructor is called only on the first time.
* The component update event is called frequently and can be used to
* update the query params by using this function that only updates
* the query params if the values did change and we're on the correct tab.
* @param tab which the query params should update
* @param sortSetting the current sort settings
* @param defaultSortSetting the default sort settings
* @param history
*/
export const updateSortSettingQueryParamsOnTab = (
tab: string,
sortSetting: SortSetting,
defaultSortSetting: SortSetting,
history: History,
): void => {
const searchParams = new URLSearchParams(history.location.search);
const currentTab = searchParams.get("tab") || "";
const ascending =
(searchParams.get("ascending") ||
defaultSortSetting.ascending.toString()) === "true";
const columnTitle =
searchParams.get("columnTitle") || defaultSortSetting.columnTitle;
if (
currentTab === tab &&
(sortSetting.columnTitle != columnTitle ||
sortSetting.ascending != ascending)
) {
const params = {
ascending: sortSetting.ascending.toString(),
columnTitle: sortSetting.columnTitle,
};
const nextSearchParams = new URLSearchParams(history.location.search);
Object.entries(params).forEach(([key, value]) => {
if (!value) {
nextSearchParams.delete(key);
} else {
nextSearchParams.set(key, value);
}
});
history.location.search = nextSearchParams.toString();
history.replace(history.location);
}
};
Loading

0 comments on commit 1cd2a34

Please sign in to comment.