fix(ui): escape label values in silence form

Fixes #3866
This commit is contained in:
Łukasz Mierzwa
2022-01-11 22:35:53 +00:00
committed by Łukasz Mierzwa
parent 8d41c67681
commit ce4a9c3e67
18 changed files with 434 additions and 73 deletions

View File

@@ -6,6 +6,7 @@
- Messages are now logged correctly when both `--log.format=json` and
`--log.timestamp=true` flags are set #3822.
- Escape label values in silence form #3866.
### Changed

View File

@@ -557,6 +557,26 @@ class RichAnnotations(AlertGenerator):
]
class RegexEscapeValue(AlertGenerator):
name = "Labels with rich values"
comment = "This alert will have rich labels"
def alerts(self):
return [
newAlert(
self._labels(
instance="server{}".format(i),
cluster="staging",
job="textfile_exporter",
region="SA",
device="Device {} (main)".format(i % 2),
regex="^device{}(.+)bar\\$".format(i),
),
)
for i in range(0, 10)
]
if __name__ == "__main__":
generators = [
AlwaysOnAlert(MAX_INTERVAL),
@@ -573,6 +593,7 @@ if __name__ == "__main__":
SilencedAlertWithJiraLink(MAX_INTERVAL),
PaginationTest(MAX_INTERVAL),
RichAnnotations(MAX_INTERVAL),
RegexEscapeValue(MAX_INTERVAL),
]
while True:
for g in generators:

View File

@@ -5,6 +5,7 @@ export const NewLabelValue = (v: string): string => `New value: ${v}`;
export interface OptionT {
label: string;
value: string;
wasCreated: boolean;
}
export interface MultiValueOptionT {
@@ -15,4 +16,5 @@ export interface MultiValueOptionT {
export const StringToOption = (value: string): OptionT => ({
label: value,
value: value,
wasCreated: false,
});

View File

@@ -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;

View File

@@ -24,6 +24,7 @@ const AlertGroupCollapseConfiguration: FC<{
return {
label: settingsStore.alertGroupConfig.options[val].label,
value: val,
wasCreated: false,
};
};

View File

@@ -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 =

View File

@@ -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 (

View File

@@ -24,6 +24,7 @@ const ThemeConfiguration: FC<{
return {
label: settingsStore.themeConfig.options[val].label,
value: val,
wasCreated: false,
};
};

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),

View File

@@ -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");

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -59,12 +59,21 @@ interface AlertGroupConfigStorage {
}
class AlertGroupConfig {
options = Object.freeze({
expanded: { label: "Always expanded", value: "expanded" },
expanded: {
label: "Always expanded",
value: "expanded",
wasCreated: false,
},
collapsedOnMobile: {
label: "Collapse on mobile",
value: "collapsedOnMobile",
wasCreated: false,
},
collapsed: {
label: "Always collapsed",
value: "collapsed",
wasCreated: false,
},
collapsed: { label: "Always collapsed", value: "collapsed" },
});
config: AlertGroupConfigStorage;
@@ -128,10 +137,18 @@ interface GridConfigStorage {
}
class GridConfig {
options = Object.freeze({
default: { label: "Use defaults from karma config file", value: "default" },
disabled: { label: "No sorting", value: "disabled" },
startsAt: { label: "Sort by alert timestamp", value: "startsAt" },
label: { label: "Sort by alert label", value: "label" },
default: {
label: "Use defaults from karma config file",
value: "default",
wasCreated: false,
},
disabled: { label: "No sorting", value: "disabled", wasCreated: false },
startsAt: {
label: "Sort by alert timestamp",
value: "startsAt",
wasCreated: false,
},
label: { label: "Sort by alert label", value: "label", wasCreated: false },
});
config: GridConfigStorage;
@@ -206,9 +223,10 @@ class ThemeConfig {
auto: {
label: "Automatic theme, follow browser preference",
value: "auto",
wasCreated: false,
},
light: { label: "Light theme", value: "light" },
dark: { label: "Dark theme", value: "dark" },
light: { label: "Light theme", value: "light", wasCreated: false },
dark: { label: "Dark theme", value: "dark", wasCreated: false },
});
this.config = localStored(

View File

@@ -174,14 +174,14 @@ describe("SilenceFormStore.data", () => {
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "alertname",
values: [{ label: "FakeAlert", value: "FakeAlert" }],
values: [{ label: "FakeAlert", value: "FakeAlert", wasCreated: false }],
isRegex: false,
})
);
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "job",
values: [{ label: "mock", value: "mock" }],
values: [{ label: "mock", value: "mock", wasCreated: false }],
isRegex: false,
})
);
@@ -189,12 +189,12 @@ describe("SilenceFormStore.data", () => {
expect.objectContaining({
name: "instance",
values: [
{ label: "dev1", value: "dev1" },
{ label: "prod1", value: "prod1" },
{ label: "prod2", value: "prod2" },
{ label: "dev2", value: "dev2" },
{ label: "dev3", value: "dev3" },
{ label: "dev4", value: "dev4" },
{ label: "dev1", value: "dev1", wasCreated: false },
{ label: "prod1", value: "prod1", wasCreated: false },
{ label: "prod2", value: "prod2", wasCreated: false },
{ label: "dev2", value: "dev2", wasCreated: false },
{ label: "dev3", value: "dev3", wasCreated: false },
{ label: "dev4", value: "dev4", wasCreated: false },
],
isRegex: true,
})
@@ -203,8 +203,8 @@ describe("SilenceFormStore.data", () => {
expect.objectContaining({
name: "cluster",
values: [
{ label: "dev", value: "dev" },
{ label: "prod", value: "prod" },
{ label: "dev", value: "dev", wasCreated: false },
{ label: "prod", value: "prod", wasCreated: false },
],
isRegex: true,
})
@@ -232,7 +232,7 @@ describe("SilenceFormStore.data", () => {
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "alertname",
values: [{ label: "FakeAlert", value: "FakeAlert" }],
values: [{ label: "FakeAlert", value: "FakeAlert", wasCreated: false }],
isRegex: false,
})
);
@@ -240,8 +240,8 @@ describe("SilenceFormStore.data", () => {
expect.objectContaining({
name: "instance",
values: [
{ label: "prod1", value: "prod1" },
{ label: "prod2", value: "prod2" },
{ label: "prod1", value: "prod1", wasCreated: false },
{ label: "prod2", value: "prod2", wasCreated: false },
],
isRegex: true,
})
@@ -249,7 +249,7 @@ describe("SilenceFormStore.data", () => {
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "cluster",
values: [{ label: "prod", value: "prod" }],
values: [{ label: "prod", value: "prod", wasCreated: false }],
isRegex: false,
})
);
@@ -277,7 +277,7 @@ describe("SilenceFormStore.data", () => {
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "alertname",
values: [{ label: "FakeAlert", value: "FakeAlert" }],
values: [{ label: "FakeAlert", value: "FakeAlert", wasCreated: false }],
isRegex: false,
})
);
@@ -285,8 +285,8 @@ describe("SilenceFormStore.data", () => {
expect.objectContaining({
name: "instance",
values: [
{ label: "dev1", value: "dev1" },
{ label: "prod1", value: "prod1" },
{ label: "dev1", value: "dev1", wasCreated: false },
{ label: "prod1", value: "prod1", wasCreated: false },
],
isRegex: true,
})
@@ -295,8 +295,8 @@ describe("SilenceFormStore.data", () => {
expect.objectContaining({
name: "cluster",
values: [
{ label: "dev", value: "dev" },
{ label: "prod", value: "prod" },
{ label: "dev", value: "dev", wasCreated: false },
{ label: "prod", value: "prod", wasCreated: false },
],
isRegex: true,
})
@@ -348,7 +348,7 @@ describe("SilenceFormStore.data", () => {
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "alertname",
values: [{ label: "FakeAlert", value: "FakeAlert" }],
values: [{ label: "FakeAlert", value: "FakeAlert", wasCreated: false }],
isRegex: false,
})
);
@@ -356,8 +356,8 @@ describe("SilenceFormStore.data", () => {
expect.objectContaining({
name: "instance",
values: [
{ label: "1", value: "1" },
{ label: "3", value: "3" },
{ label: "1", value: "1", wasCreated: false },
{ label: "3", value: "3", wasCreated: false },
],
isRegex: true,
})
@@ -394,7 +394,7 @@ describe("SilenceFormStore.data", () => {
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "alertname",
values: [{ label: "FakeAlert", value: "FakeAlert" }],
values: [{ label: "FakeAlert", value: "FakeAlert", wasCreated: false }],
isRegex: false,
})
);
@@ -415,28 +415,28 @@ describe("SilenceFormStore.data", () => {
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "alertname",
values: [{ label: "FakeAlert", value: "FakeAlert" }],
values: [{ label: "FakeAlert", value: "FakeAlert", wasCreated: false }],
isRegex: false,
})
);
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "job",
values: [{ label: "mock", value: "mock" }],
values: [{ label: "mock", value: "mock", wasCreated: false }],
isRegex: false,
})
);
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "instance",
values: [{ label: "prod1", value: "prod1" }],
values: [{ label: "prod1", value: "prod1", wasCreated: false }],
isRegex: false,
})
);
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "cluster",
values: [{ label: "prod", value: "prod" }],
values: [{ label: "prod", value: "prod", wasCreated: false }],
isRegex: false,
})
);
@@ -454,7 +454,7 @@ describe("SilenceFormStore.data", () => {
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "alertname",
values: [{ label: "FakeAlert", value: "FakeAlert" }],
values: [{ label: "FakeAlert", value: "FakeAlert", wasCreated: false }],
isRegex: false,
})
);
@@ -505,7 +505,7 @@ describe("SilenceFormStore.data", () => {
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "region",
values: [{ label: "AF", value: "AF" }],
values: [{ label: "AF", value: "AF", wasCreated: false }],
isRegex: false,
})
);
@@ -513,9 +513,9 @@ describe("SilenceFormStore.data", () => {
expect.objectContaining({
name: "alertname",
values: [
{ label: "Alert1", value: "Alert1" },
{ label: "Alert2", value: "Alert2" },
{ label: "Alert3", value: "Alert3" },
{ label: "Alert1", value: "Alert1", wasCreated: false },
{ label: "Alert2", value: "Alert2", wasCreated: false },
{ label: "Alert3", value: "Alert3", wasCreated: false },
],
isRegex: true,
})
@@ -523,7 +523,7 @@ describe("SilenceFormStore.data", () => {
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "job",
values: [{ label: "mock", value: "mock" }],
values: [{ label: "mock", value: "mock", wasCreated: false }],
isRegex: false,
})
);
@@ -558,7 +558,7 @@ describe("SilenceFormStore.data", () => {
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "regex",
values: [{ label: "equal", value: "equal" }],
values: [{ label: "equal", value: "equal", wasCreated: false }],
isRegex: true,
isEqual: true,
})
@@ -566,7 +566,7 @@ describe("SilenceFormStore.data", () => {
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "regex",
values: [{ label: "notEqual", value: "notEqual" }],
values: [{ label: "notEqual", value: "notEqual", wasCreated: false }],
isRegex: true,
isEqual: false,
})
@@ -574,7 +574,7 @@ describe("SilenceFormStore.data", () => {
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "notRegex",
values: [{ label: "equal", value: "equal" }],
values: [{ label: "equal", value: "equal", wasCreated: false }],
isRegex: false,
isEqual: true,
})
@@ -582,7 +582,7 @@ describe("SilenceFormStore.data", () => {
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "notRegex",
values: [{ label: "notEqual", value: "notEqual" }],
values: [{ label: "notEqual", value: "notEqual", wasCreated: false }],
isRegex: false,
isEqual: false,
})
@@ -694,7 +694,11 @@ describe("SilenceFormStore.data", () => {
expect(silenceFormStorestore.data.matchers).toContainEqual(
expect.objectContaining({
name: t.result.name,
values: t.result.values.map((v) => ({ label: v, value: v })),
values: t.result.values.map((v) => ({
label: v,
value: v,
wasCreated: false,
})),
isRegex: t.matcher.isRegex,
isEqual: t.matcher.isEqual,
})
@@ -732,6 +736,112 @@ describe("SilenceFormStore.data", () => {
expect(store.data.toAlertmanagerPayload).toMatchSnapshot();
});
it("toAlertmanagerPayload creates payload that matches snapshot with regex values", () => {
store.data.setMatchers([
{
id: "1",
name: "notEqualRegexAuto",
values: [{ label: "foo", value: "^(.+)$", wasCreated: false }],
isEqual: false,
isRegex: true,
},
{
id: "2",
name: "equalRegexAuto",
values: [{ label: "foo", value: "^(.+)$", wasCreated: false }],
isEqual: true,
isRegex: true,
},
{
id: "3",
name: "equalNotRegexAuto",
values: [{ label: "foo", value: "^(.+)$", wasCreated: false }],
isEqual: true,
isRegex: false,
},
{
id: "4",
name: "notEqualnotRegexAuto",
values: [{ label: "foo", value: "^(.+)$", wasCreated: false }],
isEqual: false,
isRegex: false,
},
{
id: "5",
name: "notEqualRegexCreated",
values: [{ label: "foo", value: "^(.+)$", wasCreated: true }],
isEqual: false,
isRegex: true,
},
{
id: "6",
name: "equalRegexCreated",
values: [{ label: "foo", value: "^(.+)$", wasCreated: true }],
isEqual: true,
isRegex: true,
},
{
id: "7",
name: "equalNotRegexCreated",
values: [{ label: "foo", value: "^(.+)$", wasCreated: true }],
isEqual: true,
isRegex: false,
},
{
id: "8",
name: "notEqualnotRegexAuto",
values: [{ label: "foo", value: "^(.+)$", wasCreated: true }],
isEqual: false,
isRegex: false,
},
{
id: "9",
name: "notEqualRegexCreatedMulti",
values: [
{ label: "foo", value: "^(.+)$", wasCreated: true },
{ label: "bar", value: "\\", wasCreated: true },
],
isEqual: false,
isRegex: true,
},
{
id: "10",
name: "equalRegexCreatedMulti",
values: [
{ label: "foo", value: "^(.+)$", wasCreated: true },
{ label: "bar", value: "\\", wasCreated: true },
],
isEqual: true,
isRegex: true,
},
{
id: "11",
name: "notEqualRegexAuto",
values: [
{ label: "foo", value: "^(.+)$", wasCreated: false },
{ label: "bar", value: "\\", wasCreated: true },
],
isEqual: false,
isRegex: true,
},
{
id: "12",
name: "equalRegexAuto",
values: [
{ label: "foo", value: "^(.+)$", wasCreated: false },
{ label: "bar", value: "\\", wasCreated: true },
],
isEqual: true,
isRegex: true,
},
]);
store.data.setStart(new Date(Date.UTC(2000, 1, 1, 0, 0, 0)));
store.data.setEnd(new Date(Date.UTC(2000, 1, 1, 1, 0, 0)));
store.data.setAuthor("me@example.com");
store.data.setComment("toAlertmanagerPayload test");
expect(store.data.toAlertmanagerPayload).toMatchSnapshot();
});
it("dumps to base64 and back", () => {
store.data.setMatchers([
MockMatcher("foo", [StringToOption("bar")]),
@@ -793,7 +903,7 @@ describe("SilenceFormStore.data", () => {
describe("SilenceFormStore.data.isValid", () => {
it("isValid returns 'false' if alertmanagers list is empty", () => {
store.data.setMatchers([
MockMatcher("foo", [{ label: "bar", value: "bar" }]),
MockMatcher("foo", [{ label: "bar", value: "bar", wasCreated: false }]),
]);
store.data.setAuthor("me@example.com");
store.data.setComment("fake silence");
@@ -810,7 +920,9 @@ describe("SilenceFormStore.data.isValid", () => {
it("isValid returns 'false' if matchers list is pupulated when a matcher without any name", () => {
store.data.setAlertmanagers([MockAlertmanagerOption()]);
store.data.setMatchers([MockMatcher("", [{ label: "bar", value: "bar" }])]);
store.data.setMatchers([
MockMatcher("", [{ label: "bar", value: "bar", wasCreated: false }]),
]);
store.data.setAuthor("me@example.com");
store.data.setComment("fake silence");
expect(store.data.isValid).toBe(false);
@@ -835,7 +947,7 @@ describe("SilenceFormStore.data.isValid", () => {
it("isValid returns 'false' if author is empty", () => {
store.data.setAlertmanagers([MockAlertmanagerOption()]);
store.data.setMatchers([
MockMatcher("foo", [{ label: "bar", value: "bar" }]),
MockMatcher("foo", [{ label: "bar", value: "bar", wasCreated: false }]),
]);
store.data.setAuthor("");
store.data.setComment("fake silence");
@@ -845,7 +957,7 @@ describe("SilenceFormStore.data.isValid", () => {
it("isValid returns 'false' if comment is empty", () => {
store.data.setAlertmanagers([MockAlertmanagerOption()]);
store.data.setMatchers([
MockMatcher("foo", [{ label: "bar", value: "bar" }]),
MockMatcher("foo", [{ label: "bar", value: "bar", wasCreated: false }]),
]);
store.data.setAuthor("me@example.com");
store.data.setComment("");
@@ -855,7 +967,7 @@ describe("SilenceFormStore.data.isValid", () => {
it("isValid returns 'true' if all fileds are set", () => {
store.data.setAlertmanagers([MockAlertmanagerOption()]);
store.data.setMatchers([
MockMatcher("foo", [{ label: "bar", value: "bar" }]),
MockMatcher("foo", [{ label: "bar", value: "bar", wasCreated: false }]),
]);
store.data.setAuthor("me@example.com");
store.data.setComment("fake silence");

View File

@@ -77,6 +77,10 @@ const AlertmanagerClustersToOption = (clusterDict: {
value: clusterMembers,
}));
export const EscapeRegex = (v: string): string => {
return v.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
};
const MatchersFromGroup = (
group: APIAlertGroupT,
stripLabels: string[],
@@ -218,9 +222,13 @@ const GenerateAlertmanagerSilenceData = (
name: m.name,
value:
m.values.length > 1
? `(${m.values.map((v) => v.value).join("|")})`
? `(${m.values
.map((v) => (v.wasCreated ? v.value : EscapeRegex(v.value)))
.join("|")})`
: m.values.length === 1
? m.values[0].value
? m.values[0].wasCreated
? m.values[0].value
: EscapeRegex(m.values[0].value)
: "",
isRegex: m.isRegex,
isEqual: m.isEqual,

View File

@@ -40,3 +40,86 @@ Object {
"startsAt": "2000-02-01T00:00:00.000Z",
}
`;
exports[`SilenceFormStore.data toAlertmanagerPayload creates payload that matches snapshot with regex values 1`] = `
Object {
"comment": "toAlertmanagerPayload test",
"createdBy": "me@example.com",
"endsAt": "2000-02-01T01:00:00.000Z",
"matchers": Array [
Object {
"isEqual": false,
"isRegex": true,
"name": "notEqualRegexAuto",
"value": "\\\\^\\\\(\\\\.\\\\+\\\\)\\\\$",
},
Object {
"isEqual": true,
"isRegex": true,
"name": "equalRegexAuto",
"value": "\\\\^\\\\(\\\\.\\\\+\\\\)\\\\$",
},
Object {
"isEqual": true,
"isRegex": false,
"name": "equalNotRegexAuto",
"value": "\\\\^\\\\(\\\\.\\\\+\\\\)\\\\$",
},
Object {
"isEqual": false,
"isRegex": false,
"name": "notEqualnotRegexAuto",
"value": "\\\\^\\\\(\\\\.\\\\+\\\\)\\\\$",
},
Object {
"isEqual": false,
"isRegex": true,
"name": "notEqualRegexCreated",
"value": "^(.+)$",
},
Object {
"isEqual": true,
"isRegex": true,
"name": "equalRegexCreated",
"value": "^(.+)$",
},
Object {
"isEqual": true,
"isRegex": false,
"name": "equalNotRegexCreated",
"value": "^(.+)$",
},
Object {
"isEqual": false,
"isRegex": false,
"name": "notEqualnotRegexAuto",
"value": "^(.+)$",
},
Object {
"isEqual": false,
"isRegex": true,
"name": "notEqualRegexCreatedMulti",
"value": "(^(.+)$|\\\\)",
},
Object {
"isEqual": true,
"isRegex": true,
"name": "equalRegexCreatedMulti",
"value": "(^(.+)$|\\\\)",
},
Object {
"isEqual": false,
"isRegex": true,
"name": "notEqualRegexAuto",
"value": "(\\\\^\\\\(\\\\.\\\\+\\\\)\\\\$|\\\\)",
},
Object {
"isEqual": true,
"isRegex": true,
"name": "equalRegexAuto",
"value": "(\\\\^\\\\(\\\\.\\\\+\\\\)\\\\$|\\\\)",
},
],
"startsAt": "2000-02-01T00:00:00.000Z",
}
`;

View File

@@ -98,6 +98,10 @@ const useFetchGetMock = (
uri: "./labelValues.json?name=cluster",
response: ["dev", "staging", "prod"],
},
{
uri: "./labelValues.json?name=regex",
response: ["(dev)", "staging (.+)", "\\prod\\"],
},
// matcher value counters
{
re: /^\.\/alerts\.json\?q=/,