mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
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:
parent
2d26d3e3de
commit
c896bf340d
9 changed files with 173 additions and 119 deletions
|
@ -149,7 +149,7 @@
|
|||
max-width: 15px;
|
||||
top: 0;
|
||||
left: $padding-small;
|
||||
height: 40px;
|
||||
height: 36px;
|
||||
position: absolute;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
title={title}
|
||||
>
|
||||
<div
|
||||
className={classNames({
|
||||
"vm-table-settings-popper": true,
|
||||
"vm-table-settings-popper_mobile": isMobile
|
||||
})}
|
||||
{openSettings && (
|
||||
<Modal
|
||||
title={title}
|
||||
className="vm-table-settings-modal"
|
||||
onClose={handleClose}
|
||||
>
|
||||
<div className="vm-table-settings-popper-list vm-table-settings-popper-list_first">
|
||||
<Switch
|
||||
label={"Compact view"}
|
||||
value={tableCompact}
|
||||
onChange={toggleTableCompact}
|
||||
/>
|
||||
</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-section">
|
||||
<div className="vm-table-settings-modal-section__title">
|
||||
Customize columns
|
||||
</div>
|
||||
<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>
|
||||
)}
|
||||
</div>
|
||||
<div className="vm-table-settings-popper-list-columns">
|
||||
{!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>
|
||||
)}
|
||||
{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>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,66 +1,98 @@
|
|||
@use "src/styles/variables" as *;
|
||||
|
||||
.vm-table-settings-popper {
|
||||
display: grid;
|
||||
min-width: 250px;
|
||||
|
||||
&_mobile &-list {
|
||||
gap: $padding-global;
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
padding: $padding-global;
|
||||
border-bottom: $border-divider;
|
||||
max-width: 250px;
|
||||
|
||||
&_first {
|
||||
padding-top: 0;
|
||||
.vm-table-settings {
|
||||
&-modal {
|
||||
.vm-modal-content-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&-header {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: $padding-small;
|
||||
min-height: 25px;
|
||||
&-section {
|
||||
padding-block: $padding-global;
|
||||
border-top: $border-divider;
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
&__title {
|
||||
padding-inline: $padding-global;
|
||||
font-size: $font-size;
|
||||
font-weight: bold;
|
||||
margin-bottom: $padding-global;
|
||||
}
|
||||
}
|
||||
|
||||
&-columns {
|
||||
max-height: 350px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__item {
|
||||
padding: calc($padding-global/2) $padding-global;
|
||||
font-size: $font-size;
|
||||
|
||||
&:hover,
|
||||
&_focus {
|
||||
background-color: $color-hover-black;
|
||||
&__search {
|
||||
padding-inline: $padding-global;
|
||||
}
|
||||
|
||||
&_check_all {
|
||||
padding: calc($padding-global/2) $padding-global;
|
||||
margin: 0 (-$padding-global);
|
||||
&-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 250px;
|
||||
min-height: 250px;
|
||||
overflow: auto;
|
||||
margin-bottom: $padding-global;
|
||||
|
||||
&__item {
|
||||
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;
|
||||
}
|
||||
|
||||
&_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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__no-found {
|
||||
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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -3,6 +3,7 @@ export type StorageKeys = "AUTOCOMPLETE"
|
|||
| "QUERY_TRACING"
|
||||
| "SERIES_LIMITS"
|
||||
| "TABLE_COMPACT"
|
||||
| "TABLE_COLUMNS"
|
||||
| "TIMEZONE"
|
||||
| "DISABLED_DEFAULT_TIMEZONE"
|
||||
| "THEME"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
|
Loading…
Reference in a new issue