fix(ui): escape label values in silence form

Fixes #3866
This commit is contained in:
Łukasz Mierzwa
2022-01-11 22:48:45 +00:00
committed by Łukasz Mierzwa
parent 8d41c67681
commit ce4a9c3e67
18 changed files with 434 additions and 73 deletions
@@ -27,10 +27,10 @@ import { ThemeContext } from "Components/Theme";
import { useOnClickOutside } from "Hooks/useOnClickOutside";
const specialLabels: OptionT[] = [
{ label: "Automatic selection", value: "@auto" },
{ label: "@alertmanager", value: "@alertmanager" },
{ label: "@cluster", value: "@cluster" },
{ label: "@receiver", value: "@receiver" },
{ label: "Automatic selection", value: "@auto", wasCreated: false },
{ label: "@alertmanager", value: "@alertmanager", wasCreated: false },
{ label: "@cluster", value: "@cluster", wasCreated: false },
{ label: "@receiver", value: "@receiver", wasCreated: false },
];
const NullContainer: FC = () => null;
@@ -24,6 +24,7 @@ const AlertGroupCollapseConfiguration: FC<{
return {
label: settingsStore.alertGroupConfig.options[val].label,
value: val,
wasCreated: false,
};
};
@@ -30,7 +30,11 @@ const AlertGroupSortConfiguration: FC<{
};
const valueToOption = (val: SortOrderT): OptionT => {
return { label: settingsStore.gridConfig.options[val].label, value: val };
return {
label: settingsStore.gridConfig.options[val].label,
value: val,
wasCreated: false,
};
};
const hideReverse =
@@ -14,14 +14,15 @@ const disabledLabel = "Disable multi-grid";
const valueToOption = (v: string) => ({
label: v ? v : disabledLabel,
value: v,
wasCreated: false,
});
const staticValues = [
{ label: disabledLabel, value: "" },
{ label: "Automatic selection", value: "@auto" },
{ label: "@alertmanager", value: "@alertmanager" },
{ label: "@cluster", value: "@cluster" },
{ label: "@receiver", value: "@receiver" },
{ label: disabledLabel, value: "", wasCreated: false },
{ label: "Automatic selection", value: "@auto", wasCreated: false },
{ label: "@alertmanager", value: "@alertmanager", wasCreated: false },
{ label: "@cluster", value: "@cluster", wasCreated: false },
{ label: "@receiver", value: "@receiver", wasCreated: false },
];
const GridLabelName: FC<{
@@ -35,7 +36,7 @@ const GridLabelName: FC<{
const defaultValue =
settingsStore.multiGridConfig.config.gridLabel === "@auto"
? { label: "Automatic selection", value: "@auto" }
? { label: "Automatic selection", value: "@auto", wasCreated: false }
: valueToOption(settingsStore.multiGridConfig.config.gridLabel);
return (
@@ -24,6 +24,7 @@ const ThemeConfiguration: FC<{
return {
label: settingsStore.themeConfig.options[val].label,
value: val,
wasCreated: false,
};
};
+15 -4
View File
@@ -1,12 +1,23 @@
import { FormatQuery, QueryOperators, StaticLabels } from "Common/Query";
import type { MultiValueOptionT } from "Common/Select";
import { MatcherT, MatcherToOperator } from "Stores/SilenceFormStore";
import {
MatcherT,
MatcherToOperator,
EscapeRegex,
} from "Stores/SilenceFormStore";
const MatcherToFilter = (matcher: MatcherT): string => {
const values = matcher.values.map((v) =>
v.wasCreated
? v
: matcher.isRegex
? { ...v, value: EscapeRegex(v.value) }
: v
);
const value =
matcher.values.length > 1
? `(${matcher.values.map((v) => v.value).join("|")})`
: matcher.values[0].value;
values.length > 1
? `(${values.map((v) => v.value).join("|")})`
: values[0].value;
return FormatQuery(
matcher.name,
MatcherToOperator(matcher),
@@ -110,6 +110,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: "alertnameEqual",
value: "alertnameEqual",
wasCreated: false,
},
],
},
@@ -122,6 +123,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: "alertnameNotEqual",
value: "alertnameNotEqual",
wasCreated: false,
},
],
},
@@ -134,6 +136,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: ".*alertnameRegex.*",
value: ".*alertnameRegex.*",
wasCreated: false,
},
],
},
@@ -146,6 +149,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: ".*alertnameNegativeRegex.*",
value: ".*alertnameNegativeRegex.*",
wasCreated: false,
},
],
},
@@ -158,6 +162,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: "clusterEqual",
value: "clusterEqual",
wasCreated: false,
},
],
},
@@ -170,6 +175,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: "clusterNotEqual",
value: "clusterNotEqual",
wasCreated: false,
},
],
},
@@ -182,6 +188,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: ".*clusterRegex.*",
value: ".*clusterRegex.*",
wasCreated: false,
},
],
},
@@ -194,6 +201,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: ".*clusterNegativeRegex.*",
value: ".*clusterNegativeRegex.*",
wasCreated: false,
},
],
},
@@ -206,6 +214,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: "fooEqual",
value: "fooEqual",
wasCreated: false,
},
],
},
@@ -218,6 +227,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: "fooNotEqual",
value: "fooNotEqual",
wasCreated: false,
},
],
},
@@ -230,6 +240,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: ".*fooRegex.*",
value: ".*fooRegex.*",
wasCreated: false,
},
],
},
@@ -242,6 +253,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: ".*fooNegativeRegex.*",
value: ".*fooNegativeRegex.*",
wasCreated: false,
},
],
},
@@ -288,6 +300,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: "alertnameEqual",
value: "alertnameEqual",
wasCreated: false,
},
],
},
@@ -300,6 +313,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: "alertnameNotEqual",
value: "alertnameNotEqual",
wasCreated: false,
},
],
},
@@ -312,6 +326,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: ".*alertnameRegex.*",
value: ".*alertnameRegex.*",
wasCreated: false,
},
],
},
@@ -324,6 +339,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: ".*alertnameNegativeRegex.*",
value: ".*alertnameNegativeRegex.*",
wasCreated: false,
},
],
},
@@ -336,6 +352,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: "clusterEqual",
value: "clusterEqual",
wasCreated: false,
},
],
},
@@ -348,6 +365,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: "clusterNotEqual",
value: "clusterNotEqual",
wasCreated: false,
},
],
},
@@ -360,6 +378,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: ".*clusterRegex.*",
value: ".*clusterRegex.*",
wasCreated: false,
},
],
},
@@ -372,6 +391,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: ".*clusterNegativeRegex.*",
value: ".*clusterNegativeRegex.*",
wasCreated: false,
},
],
},
@@ -384,6 +404,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: "fooEqual",
value: "fooEqual",
wasCreated: false,
},
],
},
@@ -396,6 +417,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: "fooNotEqual",
value: "fooNotEqual",
wasCreated: false,
},
],
},
@@ -408,6 +430,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: ".*fooRegex.*",
value: ".*fooRegex.*",
wasCreated: false,
},
],
},
@@ -420,6 +443,7 @@ describe("<SilenceForm /> matchers", () => {
{
label: ".*fooNegativeRegex.*",
value: ".*fooNegativeRegex.*",
wasCreated: false,
},
],
},
@@ -531,7 +555,9 @@ describe("<SilenceForm /> preview", () => {
it("clicking on the copy button copies form link to the clipboard", () => {
const matcher = NewEmptyMatcher();
matcher.name = "job";
matcher.values = [{ label: "node_exporter", value: "node_exporter" }];
matcher.values = [
{ label: "node_exporter", value: "node_exporter", wasCreated: false },
];
silenceFormStore.data.setMatchers([matcher]);
silenceFormStore.data.setAlertmanagers([{ label: "am1", value: ["am1"] }]);
silenceFormStore.data.setAuthor("me@example.com");
@@ -550,7 +576,9 @@ describe("<SilenceForm /> preview", () => {
it("silence form share link doesn't change on new input", () => {
const matcher = NewEmptyMatcher();
matcher.name = "job";
matcher.values = [{ label: "node_exporter", value: "node_exporter" }];
matcher.values = [
{ label: "node_exporter", value: "node_exporter", wasCreated: false },
];
silenceFormStore.data.setMatchers([matcher]);
silenceFormStore.data.setAlertmanagers([{ label: "am1", value: ["am1"] }]);
silenceFormStore.data.setAuthor("me@example.com");
@@ -620,7 +648,9 @@ describe("<SilenceForm />", () => {
it("calling submit move form to the 'Preview' stage when form is valid", () => {
const matcher = NewEmptyMatcher();
matcher.name = "job";
matcher.values = [{ label: "node_exporter", value: "node_exporter" }];
matcher.values = [
{ label: "node_exporter", value: "node_exporter", wasCreated: false },
];
silenceFormStore.data.setMatchers([matcher]);
silenceFormStore.data.setAlertmanagers([{ label: "am1", value: ["am1"] }]);
silenceFormStore.data.setAuthor("me@example.com");
@@ -10,8 +10,9 @@ import {
MatcherWithIDT,
} from "Stores/SilenceFormStore";
import { ThemeContext } from "Components/Theme";
import { StringToOption } from "Common/Select";
import { OptionT, StringToOption } from "Common/Select";
import { LabelValueInput } from "./LabelValueInput";
import { act } from "react-dom/test-utils";
let silenceFormStore: SilenceFormStore;
let matcher: MatcherWithIDT;
@@ -123,6 +124,22 @@ describe("<LabelValueInput />", () => {
expect(matcher.isRegex).toBe(true);
});
it("creating a manual option sets wasCreated=true", () => {
const tree = MountedLabelValueInput(true);
const input = tree.find("Select").instance();
const options: OptionT[] = [
{ label: "foo", value: "foo", wasCreated: false },
];
act(() => {
(input.props as any).onChange(options, { action: "create-option" });
});
expect(matcher.values[0]).toStrictEqual({
label: "foo",
value: "foo",
wasCreated: true,
});
});
it("removing last value sets matcher.values to []", () => {
matcher.values = [StringToOption("dev"), StringToOption("staging")];
const tree = MountedLabelValueInput(true);
@@ -90,13 +90,16 @@ const LabelValueInput: FC<{
placeholder={isValid ? "Label value" : <ValidationError />}
onChange={(
newValue: OnChangeValue<OptionT, true>,
_: ActionMeta<OptionT>
meta: ActionMeta<OptionT>
) => {
matcher.values = newValue as OptionT[];
// force regex if we have multiple values
if (matcher.values.length > 1 && matcher.isRegex === false) {
matcher.isRegex = true;
}
if (meta.action === "create-option") {
matcher.values[matcher.values.length - 1].wasCreated = true;
}
}}
hideSelectedOptions
isMulti
@@ -145,6 +145,50 @@ describe("<MatchCounter />", () => {
).toBe("./alertList.json?q=foo%3D~%5Ebar%24");
});
it("sends correct query string for a 'foo=(x)' matcher with wasCreated=true & isRegex=false", () => {
const v = StringToOption("(x)");
v.wasCreated = true;
matcher.values = [v];
matcher.isRegex = false;
MountedMatchCounter();
expect(
(useFetchGet as jest.MockedFunction<typeof useFetchGet>).mock.calls[0][0]
).toBe("./alertList.json?q=foo%3D%28x%29");
});
it("sends correct query string for a 'foo=(x)' matcher with wasCreated=true & isRegex=true", () => {
const v = StringToOption("(x)");
v.wasCreated = true;
matcher.values = [v];
matcher.isRegex = true;
MountedMatchCounter();
expect(
(useFetchGet as jest.MockedFunction<typeof useFetchGet>).mock.calls[0][0]
).toBe("./alertList.json?q=foo%3D~%5E%28x%29%24");
});
it("sends correct query string for a 'foo=(x)' matcher with wasCreated=false & isRegex=false", () => {
const v = StringToOption("(x)");
v.wasCreated = false;
matcher.values = [v];
matcher.isRegex = false;
MountedMatchCounter();
expect(
(useFetchGet as jest.MockedFunction<typeof useFetchGet>).mock.calls[0][0]
).toBe("./alertList.json?q=foo%3D%28x%29");
});
it("sends correct query string for a 'foo=(x)' matcher with wasCreated=false & isRegex=true", () => {
const v = StringToOption("(x)");
v.wasCreated = false;
matcher.values = [v];
matcher.isRegex = true;
MountedMatchCounter();
expect(
(useFetchGet as jest.MockedFunction<typeof useFetchGet>).mock.calls[0][0]
).toBe("./alertList.json?q=foo%3D~%5E%5C%28x%5C%29%24");
});
it("sends correct query string for a 'foo=~(bar|baz)' matcher", () => {
matcher.values = [StringToOption("bar"), StringToOption("baz")];
matcher.isRegex = true;