mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-03-11 15:34:56 +00:00
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4848
This commit is contained in:
parent
8287749c05
commit
ca44b8da1f
7 changed files with 112 additions and 112 deletions
|
@ -5,8 +5,6 @@ import TextField from "../../Main/TextField/TextField";
|
||||||
import Autocomplete from "../../Main/Autocomplete/Autocomplete";
|
import Autocomplete from "../../Main/Autocomplete/Autocomplete";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import { QueryStats } from "../../../api/types";
|
import { QueryStats } from "../../../api/types";
|
||||||
import Tooltip from "../../Main/Tooltip/Tooltip";
|
|
||||||
import { WarningIcon } from "../../Main/Icons";
|
|
||||||
import { partialWarning, seriesFetchedWarning } from "./warningText";
|
import { partialWarning, seriesFetchedWarning } from "./warningText";
|
||||||
|
|
||||||
export interface QueryEditorProps {
|
export interface QueryEditorProps {
|
||||||
|
@ -41,7 +39,7 @@ const QueryEditor: FC<QueryEditorProps> = ({
|
||||||
const [openAutocomplete, setOpenAutocomplete] = useState(false);
|
const [openAutocomplete, setOpenAutocomplete] = useState(false);
|
||||||
const autocompleteAnchorEl = useRef<HTMLDivElement>(null);
|
const autocompleteAnchorEl = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const warnings = [
|
const warning = [
|
||||||
{
|
{
|
||||||
show: stats?.seriesFetched === "0" && !stats.resultLength,
|
show: stats?.seriesFetched === "0" && !stats.resultLength,
|
||||||
text: seriesFetchedWarning
|
text: seriesFetchedWarning
|
||||||
|
@ -50,7 +48,7 @@ const QueryEditor: FC<QueryEditorProps> = ({
|
||||||
show: stats?.isPartial,
|
show: stats?.isPartial,
|
||||||
text: partialWarning
|
text: partialWarning
|
||||||
}
|
}
|
||||||
].filter((warning) => warning.show);
|
].filter((w) => w.show).map(w => w.text).join("");
|
||||||
|
|
||||||
const handleSelect = (val: string) => {
|
const handleSelect = (val: string) => {
|
||||||
onChange(val);
|
onChange(val);
|
||||||
|
@ -104,6 +102,7 @@ const QueryEditor: FC<QueryEditorProps> = ({
|
||||||
type={"textarea"}
|
type={"textarea"}
|
||||||
autofocus={!!value}
|
autofocus={!!value}
|
||||||
error={error}
|
error={error}
|
||||||
|
warning={warning}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -119,20 +118,6 @@ const QueryEditor: FC<QueryEditorProps> = ({
|
||||||
onFoundOptions={handleChangeFoundOptions}
|
onFoundOptions={handleChangeFoundOptions}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!!warnings.length && (
|
|
||||||
<div className="vm-query-editor-warning">
|
|
||||||
<Tooltip
|
|
||||||
placement="bottom-right"
|
|
||||||
title={(
|
|
||||||
<div className="vm-query-editor-warning__tooltip">
|
|
||||||
{warnings.map((warning, index) => <p key={index}>{warning.text}</p>)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<WarningIcon/>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,29 +7,4 @@
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-warning {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: $padding-global;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
display: grid;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
color: $color-warning;
|
|
||||||
|
|
||||||
&__tooltip {
|
|
||||||
white-space: pre-line;
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-bottom: $padding-small;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import classNames from "classnames";
|
||||||
import { useMemo } from "preact/compat";
|
import { useMemo } from "preact/compat";
|
||||||
import { useAppState } from "../../../state/common/StateContext";
|
import { useAppState } from "../../../state/common/StateContext";
|
||||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
import TextFieldError from "./TextFieldError";
|
import TextFieldMessage from "./TextFieldMessage";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
|
||||||
interface TextFieldProps {
|
interface TextFieldProps {
|
||||||
|
@ -11,6 +11,7 @@ interface TextFieldProps {
|
||||||
value?: string | number
|
value?: string | number
|
||||||
type?: HTMLInputTypeAttribute | "textarea"
|
type?: HTMLInputTypeAttribute | "textarea"
|
||||||
error?: string
|
error?: string
|
||||||
|
warning?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
endIcon?: ReactNode
|
endIcon?: ReactNode
|
||||||
startIcon?: ReactNode
|
startIcon?: ReactNode
|
||||||
|
@ -30,12 +31,13 @@ const TextField: FC<TextFieldProps> = ({
|
||||||
value,
|
value,
|
||||||
type = "text",
|
type = "text",
|
||||||
error = "",
|
error = "",
|
||||||
|
warning = "",
|
||||||
|
helperText = "",
|
||||||
placeholder,
|
placeholder,
|
||||||
endIcon,
|
endIcon,
|
||||||
startIcon,
|
startIcon,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
autofocus = false,
|
autofocus = false,
|
||||||
helperText,
|
|
||||||
inputmode = "text",
|
inputmode = "text",
|
||||||
onChange,
|
onChange,
|
||||||
onEnter,
|
onEnter,
|
||||||
|
@ -53,6 +55,7 @@ const TextField: FC<TextFieldProps> = ({
|
||||||
const inputClasses = classNames({
|
const inputClasses = classNames({
|
||||||
"vm-text-field__input": true,
|
"vm-text-field__input": true,
|
||||||
"vm-text-field__input_error": error,
|
"vm-text-field__input_error": error,
|
||||||
|
"vm-text-field__input_warning": !error && warning,
|
||||||
"vm-text-field__input_icon-start": startIcon,
|
"vm-text-field__input_icon-start": startIcon,
|
||||||
"vm-text-field__input_disabled": disabled,
|
"vm-text-field__input_disabled": disabled,
|
||||||
"vm-text-field__input_textarea": type === "textarea",
|
"vm-text-field__input_textarea": type === "textarea",
|
||||||
|
@ -133,12 +136,11 @@ const TextField: FC<TextFieldProps> = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{label && <span className="vm-text-field__label">{label}</span>}
|
{label && <span className="vm-text-field__label">{label}</span>}
|
||||||
<TextFieldError error={error}/>
|
<TextFieldMessage
|
||||||
{helperText && !error && (
|
error={error}
|
||||||
<span className="vm-text-field__helper-text">
|
warning={warning}
|
||||||
{helperText}
|
info={helperText}
|
||||||
</span>
|
/>
|
||||||
)}
|
|
||||||
</label>
|
</label>
|
||||||
;
|
;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
import React, { FC, useEffect, useRef, useState } from "react";
|
|
||||||
import useEventListener from "../../../hooks/useEventListener";
|
|
||||||
import classNames from "classnames";
|
|
||||||
import "./style.scss";
|
|
||||||
|
|
||||||
interface TextFieldErrorProps {
|
|
||||||
error: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TextFieldError: FC<TextFieldErrorProps> = ({ error }) => {
|
|
||||||
const errorRef = useRef<HTMLSpanElement>(null);
|
|
||||||
const [isErrorTruncated, setIsErrorTruncated] = useState(false);
|
|
||||||
const [showFull, setShowFull] = useState(false);
|
|
||||||
|
|
||||||
const checkIfTextTruncated = () => {
|
|
||||||
const el = errorRef.current;
|
|
||||||
if (el) {
|
|
||||||
const { offsetWidth, scrollWidth, offsetHeight, scrollHeight } = el;
|
|
||||||
// The "+1" is for the scrollbar in Firefox
|
|
||||||
const overflowed = (offsetWidth + 1) < scrollWidth || (offsetHeight + 1) < scrollHeight;
|
|
||||||
setIsErrorTruncated(overflowed);
|
|
||||||
} else {
|
|
||||||
setIsErrorTruncated(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClickError = () => {
|
|
||||||
if (!isErrorTruncated) return;
|
|
||||||
setShowFull(true);
|
|
||||||
setIsErrorTruncated(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setShowFull(false);
|
|
||||||
checkIfTextTruncated();
|
|
||||||
}, [errorRef, error]);
|
|
||||||
|
|
||||||
useEventListener("resize", checkIfTextTruncated);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={classNames({
|
|
||||||
"vm-text-field__error": true,
|
|
||||||
"vm-text-field__error_overflowed": isErrorTruncated,
|
|
||||||
"vm-text-field__error_full": showFull,
|
|
||||||
})}
|
|
||||||
data-show={!!error}
|
|
||||||
ref={errorRef}
|
|
||||||
onClick={handleClickError}
|
|
||||||
>
|
|
||||||
{error}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TextFieldError;
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
import React, { FC, useEffect, useRef, useState } from "react";
|
||||||
|
import useEventListener from "../../../hooks/useEventListener";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import "./style.scss";
|
||||||
|
import { useMemo } from "preact/compat";
|
||||||
|
|
||||||
|
interface TextFieldErrorProps {
|
||||||
|
error: string;
|
||||||
|
warning: string;
|
||||||
|
info: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextFieldMessage: FC<TextFieldErrorProps> = ({ error, warning, info }) => {
|
||||||
|
console.log(warning);
|
||||||
|
const messageRef = useRef<HTMLSpanElement>(null);
|
||||||
|
const [isMessageTruncated, setIsMessageTruncated] = useState(false);
|
||||||
|
const [showFull, setShowFull] = useState(false);
|
||||||
|
|
||||||
|
const prefix = useMemo(() => {
|
||||||
|
if (error) return "ERROR: ";
|
||||||
|
if (warning) return "WARNING: ";
|
||||||
|
return "";
|
||||||
|
}, [error, warning]);
|
||||||
|
|
||||||
|
const message = `${prefix}${error || warning || info}`;
|
||||||
|
|
||||||
|
const checkIfTextTruncated = () => {
|
||||||
|
const el = messageRef.current;
|
||||||
|
if (el) {
|
||||||
|
const { offsetWidth, scrollWidth, offsetHeight, scrollHeight } = el;
|
||||||
|
// The "+1" is for the scrollbar in Firefox
|
||||||
|
const overflowed = (offsetWidth + 1) < scrollWidth || (offsetHeight + 1) < scrollHeight;
|
||||||
|
setIsMessageTruncated(overflowed);
|
||||||
|
} else {
|
||||||
|
setIsMessageTruncated(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickError = () => {
|
||||||
|
if (!isMessageTruncated) return;
|
||||||
|
setShowFull(true);
|
||||||
|
setIsMessageTruncated(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setShowFull(false);
|
||||||
|
checkIfTextTruncated();
|
||||||
|
}, [messageRef, message]);
|
||||||
|
|
||||||
|
useEventListener("resize", checkIfTextTruncated);
|
||||||
|
|
||||||
|
if (!error && !warning && !info) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={classNames({
|
||||||
|
"vm-text-field__error": true,
|
||||||
|
"vm-text-field__warning": warning && !error,
|
||||||
|
"vm-text-field__helper-text": !warning && !error,
|
||||||
|
"vm-text-field__error_overflowed": isMessageTruncated,
|
||||||
|
"vm-text-field__error_full": showFull,
|
||||||
|
})}
|
||||||
|
data-show={!!message}
|
||||||
|
ref={messageRef}
|
||||||
|
onClick={handleClickError}
|
||||||
|
>
|
||||||
|
{message}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TextFieldMessage;
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
&__label,
|
&__label,
|
||||||
&__error,
|
&__error,
|
||||||
|
&__warning,
|
||||||
&__helper-text, {
|
&__helper-text, {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: calc($padding-global/2);
|
left: calc($padding-global/2);
|
||||||
|
@ -50,12 +51,13 @@
|
||||||
color: $color-text-secondary;
|
color: $color-text-secondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__helper-text,
|
||||||
|
&__warning,
|
||||||
&__error {
|
&__error {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: calc($font-size-small/-2);
|
top: calc($font-size-small/-2);
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
color: $color-error;
|
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
|
|
||||||
|
@ -69,8 +71,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__error {
|
||||||
|
color: $color-error;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__warning {
|
||||||
|
color: $color-warning;
|
||||||
|
}
|
||||||
|
|
||||||
&__helper-text {
|
&__helper-text {
|
||||||
bottom: calc($font-size-small/-2);
|
|
||||||
color: $color-text-secondary;
|
color: $color-text-secondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,14 +102,26 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&_error {
|
&_error {
|
||||||
border: 1px solid $color-error;
|
border-color: $color-error;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border: 1px solid $color-error;
|
border-color: $color-error;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
border: 1px solid $color-error;
|
border-color: $color-error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&_warning {
|
||||||
|
border-color: $color-warning;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $color-warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: $color-warning;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ The following `tip` changes can be tested by building VictoriaMetrics components
|
||||||
|
|
||||||
* FEATURE: [vmbackup](https://docs.victoriametrics.com/vmbackup.html): add support for server-side copy of existing backups. See [these docs](https://docs.victoriametrics.com/vmbackup.html#server-side-copy-of-the-existing-backup) for details.
|
* FEATURE: [vmbackup](https://docs.victoriametrics.com/vmbackup.html): add support for server-side copy of existing backups. See [these docs](https://docs.victoriametrics.com/vmbackup.html#server-side-copy-of-the-existing-backup) for details.
|
||||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly handle `unexpected EOF` error when parsing metrics in Prometheus exposition format. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4817).
|
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly handle `unexpected EOF` error when parsing metrics in Prometheus exposition format. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4817).
|
||||||
|
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): make the warning message more noticeable for text fields. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4848).
|
||||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add button for auto-formatting PromQL/MetricsQL queries. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4681). Thanks to @aramattamara for the [pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/4694).
|
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add button for auto-formatting PromQL/MetricsQL queries. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4681). Thanks to @aramattamara for the [pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/4694).
|
||||||
|
|
||||||
* BUGFIX: do not allow starting VictoriaMetrics components with improperly set boolean command-line flags in the form `-boolFlagName value`, since this leads to silent incomplete flags' parsing. This form should be replaced with `-boolFlagName=value`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4845).
|
* BUGFIX: do not allow starting VictoriaMetrics components with improperly set boolean command-line flags in the form `-boolFlagName value`, since this leads to silent incomplete flags' parsing. This form should be replaced with `-boolFlagName=value`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4845).
|
||||||
|
|
Loading…
Reference in a new issue