vmui: add functionality to preserve selected columns (#7037)

### Describe Your Changes

1) Changed table settings from a popup to a modal window to simplify
future functionality additions.
2) Added functionality to save selected columns when data is modified or
the page is reloaded. See #7016.

<details>
  <summary>Example screenshots</summary>
  
<img alt="demo-1" width="600"
src="https://github.com/user-attachments/assets/a5d9a910-363c-4931-8b12-18ea8b3d97d8"/>
  
</details>


### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

---------

Co-authored-by: Roman Khavronenko <roman@victoriametrics.com>
This commit is contained in:
Yury Molodov 2024-09-27 11:52:01 +02:00 committed by GitHub
parent 2d26d3e3de
commit c896bf340d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 173 additions and 119 deletions

View file

@ -149,7 +149,7 @@
max-width: 15px;
top: 0;
left: $padding-small;
height: 40px;
height: 36px;
position: absolute;
color: $color-text-secondary;
}

View file

@ -1,23 +1,23 @@
import React, { FC, useEffect, useRef, useMemo } from "preact/compat";
import Button from "../../Main/Button/Button";
import { SearchIcon, SettingsIcon } from "../../Main/Icons";
import Popper from "../../Main/Popper/Popper";
import "./style.scss";
import Checkbox from "../../Main/Checkbox/Checkbox";
import Tooltip from "../../Main/Tooltip/Tooltip";
import Switch from "../../Main/Switch/Switch";
import { arrayEquals } from "../../../utils/array";
import classNames from "classnames";
import useDeviceDetect from "../../../hooks/useDeviceDetect";
import useBoolean from "../../../hooks/useBoolean";
import TextField from "../../Main/TextField/TextField";
import { KeyboardEvent, useState } from "react";
import Modal from "../../Main/Modal/Modal";
import { getFromStorage, removeFromStorage, saveToStorage } from "../../../utils/storage";
const title = "Table settings";
interface TableSettingsProps {
columns: string[];
defaultColumns?: string[];
selectedColumns?: string[];
tableCompact: boolean;
toggleTableCompact: () => void;
onChangeColumns: (arr: string[]) => void
@ -25,13 +25,11 @@ interface TableSettingsProps {
const TableSettings: FC<TableSettingsProps> = ({
columns,
defaultColumns = [],
selectedColumns = [],
tableCompact,
onChangeColumns,
toggleTableCompact
}) => {
const { isMobile } = useDeviceDetect();
const buttonRef = useRef<HTMLDivElement>(null);
const {
@ -41,31 +39,34 @@ const TableSettings: FC<TableSettingsProps> = ({
} = useBoolean(false);
const {
value: showSearch,
toggle: toggleShowSearch,
} = useBoolean(false);
value: saveColumns,
toggle: toggleSaveColumns,
} = useBoolean(Boolean(getFromStorage("TABLE_COLUMNS")));
const [searchColumn, setSearchColumn] = useState("");
const [indexFocusItem, setIndexFocusItem] = useState(-1);
const customColumns = useMemo(() => {
return selectedColumns.filter(col => !columns.includes(col));
}, [columns, selectedColumns]);
const filteredColumns = useMemo(() => {
if (!searchColumn) return columns;
return columns.filter(col => col.includes(searchColumn));
}, [columns, searchColumn]);
const allColumns = customColumns.concat(columns);
if (!searchColumn) return allColumns;
return allColumns.filter(col => col.includes(searchColumn));
}, [columns, customColumns, searchColumn]);
const isAllChecked = useMemo(() => {
return filteredColumns.every(col => defaultColumns.includes(col));
}, [defaultColumns, filteredColumns]);
const disabledButton = useMemo(() => !columns.length, [columns]);
return filteredColumns.every(col => selectedColumns.includes(col));
}, [selectedColumns, filteredColumns]);
const handleChange = (key: string) => {
onChangeColumns(defaultColumns.includes(key) ? defaultColumns.filter(col => col !== key) : [...defaultColumns, key]);
onChangeColumns(selectedColumns.includes(key) ? selectedColumns.filter(col => col !== key) : [...selectedColumns, key]);
};
const toggleAllColumns = () => {
if (isAllChecked) {
onChangeColumns(defaultColumns.filter(col => !filteredColumns.includes(col)));
onChangeColumns(selectedColumns.filter(col => !filteredColumns.includes(col)));
} else {
onChangeColumns(filteredColumns);
}
@ -94,10 +95,24 @@ const TableSettings: FC<TableSettingsProps> = ({
};
useEffect(() => {
if (arrayEquals(columns, defaultColumns)) return;
if (arrayEquals(columns, selectedColumns) || saveColumns) return;
onChangeColumns(columns);
}, [columns]);
useEffect(() => {
if (!saveColumns) {
removeFromStorage(["TABLE_COLUMNS"]);
} else if (selectedColumns.length) {
saveToStorage("TABLE_COLUMNS", selectedColumns.join(","));
}
}, [saveColumns, selectedColumns]);
useEffect(() => {
const saveColumns = getFromStorage("TABLE_COLUMNS") as string;
if (!saveColumns) return;
onChangeColumns(saveColumns.split(","));
}, []);
return (
<div className="vm-table-settings">
<Tooltip title={title}>
@ -106,48 +121,24 @@ const TableSettings: FC<TableSettingsProps> = ({
variant="text"
startIcon={<SettingsIcon/>}
onClick={toggleOpenSettings}
disabled={disabledButton}
ariaLabel={title}
/>
</div>
</Tooltip>
<Popper
open={openSettings}
onClose={handleClose}
placement="bottom-right"
buttonRef={buttonRef}
{openSettings && (
<Modal
title={title}
className="vm-table-settings-modal"
onClose={handleClose}
>
<div
className={classNames({
"vm-table-settings-popper": true,
"vm-table-settings-popper_mobile": isMobile
})}
>
<div className="vm-table-settings-popper-list vm-table-settings-popper-list_first">
<Switch
label={"Compact view"}
value={tableCompact}
onChange={toggleTableCompact}
/>
<div className="vm-table-settings-modal-section">
<div className="vm-table-settings-modal-section__title">
Customize columns
</div>
<div className="vm-table-settings-popper-list">
<div>
<div className="vm-table-settings-popper-list-header">
<h3 className="vm-table-settings-popper-list-header__title">Display columns</h3>
<Tooltip title="search column">
<Button
color="primary"
variant="text"
onClick={toggleShowSearch}
startIcon={<SearchIcon/>}
ariaLabel="reset columns"
/>
</Tooltip>
</div>
{showSearch && (
<div className="vm-table-settings-modal-columns">
<div className="vm-table-settings-modal-columns__search">
<TextField
placeholder={"search column"}
placeholder={"Search columns"}
startIcon={<SearchIcon/>}
value={searchColumn}
onChange={setSearchColumn}
@ -155,13 +146,10 @@ const TableSettings: FC<TableSettingsProps> = ({
onKeyDown={handleKeyDown}
type="search"
/>
)}
{!filteredColumns.length && (
<p className="vm-table-settings-popper-list__no-found">No columns found</p>
)}
<div className="vm-table-settings-popper-list-header">
</div>
<div className="vm-table-settings-modal-columns-list">
{!!filteredColumns.length && (
<div className="vm-table-settings-popper-list__item vm-table-settings-popper-list__item_check_all">
<div className="vm-table-settings-modal-columns-list__item vm-table-settings-modal-columns-list__item_all">
<Checkbox
checked={isAllChecked}
onChange={toggleAllColumns}
@ -170,18 +158,24 @@ const TableSettings: FC<TableSettingsProps> = ({
/>
</div>
)}
{!filteredColumns.length && (
<div className="vm-table-settings-modal-columns-no-found">
<p className="vm-table-settings-modal-columns-no-found__info">
No columns found.
</p>
</div>
<div className="vm-table-settings-popper-list-columns">
)}
{filteredColumns.map((col, i) => (
<div
className={classNames({
"vm-table-settings-popper-list__item": true,
"vm-table-settings-popper-list__item_focus": i === indexFocusItem,
"vm-table-settings-modal-columns-list__item": true,
"vm-table-settings-modal-columns-list__item_focus": i === indexFocusItem,
"vm-table-settings-modal-columns-list__item_custom": customColumns.includes(col),
})}
key={col}
>
<Checkbox
checked={defaultColumns.includes(col)}
checked={selectedColumns.includes(col)}
onChange={createHandlerChange(col)}
label={col}
disabled={tableCompact}
@ -189,10 +183,34 @@ const TableSettings: FC<TableSettingsProps> = ({
</div>
))}
</div>
<div className="vm-table-settings-modal-preserve">
<Checkbox
checked={saveColumns}
onChange={toggleSaveColumns}
label={"Preserve column settings"}
disabled={tableCompact}
color={"primary"}
/>
<p className="vm-table-settings-modal-preserve__info">
This label indicates that when the checkbox is activated,
the current column configurations will not be reset.
</p>
</div>
</div>
</div>
</Popper>
<div className="vm-table-settings-modal-section">
<div className="vm-table-settings-modal-section__title">
Table view
</div>
<div className="vm-table-settings-modal-columns-list__item">
<Switch
label={"Compact view"}
value={tableCompact}
onChange={toggleTableCompact}
/>
</div>
</div>
</Modal>)}
</div>
);
};

View file

@ -1,66 +1,98 @@
@use "src/styles/variables" as *;
.vm-table-settings-popper {
display: grid;
min-width: 250px;
.vm-table-settings {
&-modal {
.vm-modal-content-body {
padding: 0;
}
&_mobile &-list {
gap: $padding-global;
&-section {
padding-block: $padding-global;
border-top: $border-divider;
&:first-child {
padding-top: 0;
border-top: none;
}
}
&-list {
display: grid;
gap: 12px;
padding: $padding-global;
border-bottom: $border-divider;
max-width: 250px;
&_first {
padding-top: 0;
}
&-header {
display: grid;
align-items: center;
justify-content: space-between;
grid-template-columns: 1fr auto;
gap: $padding-small;
min-height: 25px;
&__title {
padding-inline: $padding-global;
font-size: $font-size;
font-weight: bold;
margin-bottom: $padding-global;
}
}
&-columns {
max-height: 350px;
overflow: auto;
&__search {
padding-inline: $padding-global;
}
&-list {
display: flex;
flex-direction: column;
max-height: 250px;
min-height: 250px;
overflow: auto;
margin-bottom: $padding-global;
&__item {
padding: calc($padding-global/2) $padding-global;
width: 100%;
font-size: $font-size;
border-radius: $border-radius-small;
&>div {
padding: $padding-small $padding-global;
}
&_all {
font-weight: bold;
}
&:hover,
&_focus {
background-color: $color-hover-black;
}
&_check_all {
padding: calc($padding-global/2) $padding-global;
margin: 0 (-$padding-global);
&_custom {
.vm-checkbox__label:after {
width: 100%;
content: "(custom column, will be removed if unchecked)";
padding: 0 $padding-small;
text-align: right;
font-style: italic;
color: $color-text-secondary;
}
}
}
}
&__no-found {
&-no-found {
display: flex;
flex-direction: column;
min-width: 100%;
min-height: 250px;
align-items: center;
justify-content: center;
gap: $padding-global;
&__info {
text-align: center;
font-style: italic;
color: $color-text-secondary;
margin-bottom: $padding-small;
}
}
}
&-preserve {
padding: $padding-global;
&__info {
padding-top: $padding-small;
font-size: $font-size-small;
color: $color-text-secondary;
line-height: 130%;
}
}
}
}

View file

@ -26,7 +26,7 @@ const TableTab: FC<Props> = ({ liveData, controlsRef }) => {
const controls = (
<TableSettings
columns={columns}
defaultColumns={displayColumns}
selectedColumns={displayColumns}
onChangeColumns={setDisplayColumns}
tableCompact={tableCompact}
toggleTableCompact={toggleTableCompact}

View file

@ -96,7 +96,7 @@ const ExploreLogsBody: FC<ExploreLogBodyProps> = ({ data }) => {
<div className="vm-explore-logs-body-header__settings">
<TableSettings
columns={columns}
defaultColumns={displayColumns}
selectedColumns={displayColumns}
onChangeColumns={setDisplayColumns}
tableCompact={tableCompact}
toggleTableCompact={toggleTableCompact}

View file

@ -147,7 +147,7 @@ const QueryAnalyzerView: FC<Props> = ({ data, period }) => {
{displayType === "table" && (
<TableSettings
columns={columns}
defaultColumns={displayColumns}
selectedColumns={displayColumns}
onChangeColumns={setDisplayColumns}
tableCompact={tableCompact}
toggleTableCompact={toggleTableCompact}

View file

@ -3,6 +3,7 @@ export type StorageKeys = "AUTOCOMPLETE"
| "QUERY_TRACING"
| "SERIES_LIMITS"
| "TABLE_COMPACT"
| "TABLE_COLUMNS"
| "TIMEZONE"
| "DISABLED_DEFAULT_TIMEZONE"
| "THEME"

View file

@ -15,6 +15,8 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
## tip
* FEATURE: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): keep selected columns in table view on page reloads. Before, selected columns were reset on each update. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7016).
## [v0.30.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v0.30.1-victorialogs)
Released at 2024-09-27

View file

@ -26,6 +26,7 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
* FEATURE: [vmgateway](https://docs.victoriametrics.com/vmgateway/): support parsing `vm_access` claims in string format. This is useful for cases when identity provider does not support mapping claims to JSON format.
* FEATURE: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): add new metrics for data ingestion: `vm_rows_received_by_storage_total`, `vm_rows_ignored_total{reason="nan_value"}`, `vm_rows_ignored_total{reason="invalid_raw_metric_name"}`, `vm_rows_ignored_total{reason="hourly_limit_exceeded"}`, `vm_rows_ignored_total{reason="daily_limit_exceeded"}`. See this [PR](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/6663) for details.
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): change request method for `/query_range` and `/query` calls from `GET` to `POST`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6288).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): keep selected columns in table view on page reloads. Before, selected columns were reset on each update. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7016).
* FEATURE: [dashboards](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/dashboards) for VM single-node, cluster, vmalert, vmagent, VictoriaLogs: add `Go scheduling latency` panel to show the 99th quantile of Go goroutines scheduling. This panel should help identifying insufficient CPU resources for the service. It is especially useful if CPU gets throttled, which now should be visible on this panel.
* FEATURE: [alerts](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/alerts-health.yml): add alerting rule to track the Go scheduling latency for goroutines. It should notify users if VM component doesn't have enough CPU to run or gets throttled.
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent/) and [Single-node VictoriaMetrics](https://docs.victoriametrics.com/): hide jobs that contain only healthy targets when `show_only_unhealthy` filter is enabled. Before, jobs without unhealthy targets were still displayed on the page. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3536).