diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50888efed..7f80f2e6b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/demo/generator.py b/demo/generator.py
index 07b0802d5..ec22afe31 100755
--- a/demo/generator.py
+++ b/demo/generator.py
@@ -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:
diff --git a/ui/src/Common/Select.ts b/ui/src/Common/Select.ts
index d66bd1a93..0bba00709 100644
--- a/ui/src/Common/Select.ts
+++ b/ui/src/Common/Select.ts
@@ -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,
});
diff --git a/ui/src/Components/Grid/AlertGrid/GridLabelSelect.tsx b/ui/src/Components/Grid/AlertGrid/GridLabelSelect.tsx
index 4634a8416..4975e4bf5 100644
--- a/ui/src/Components/Grid/AlertGrid/GridLabelSelect.tsx
+++ b/ui/src/Components/Grid/AlertGrid/GridLabelSelect.tsx
@@ -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;
diff --git a/ui/src/Components/MainModal/Configuration/AlertGroupCollapseConfiguration.tsx b/ui/src/Components/MainModal/Configuration/AlertGroupCollapseConfiguration.tsx
index 147ba93bb..74426a3a8 100644
--- a/ui/src/Components/MainModal/Configuration/AlertGroupCollapseConfiguration.tsx
+++ b/ui/src/Components/MainModal/Configuration/AlertGroupCollapseConfiguration.tsx
@@ -24,6 +24,7 @@ const AlertGroupCollapseConfiguration: FC<{
return {
label: settingsStore.alertGroupConfig.options[val].label,
value: val,
+ wasCreated: false,
};
};
diff --git a/ui/src/Components/MainModal/Configuration/AlertGroupSortConfiguration.tsx b/ui/src/Components/MainModal/Configuration/AlertGroupSortConfiguration.tsx
index c858067f8..95bb86b65 100644
--- a/ui/src/Components/MainModal/Configuration/AlertGroupSortConfiguration.tsx
+++ b/ui/src/Components/MainModal/Configuration/AlertGroupSortConfiguration.tsx
@@ -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 =
diff --git a/ui/src/Components/MainModal/Configuration/GridLabelName.tsx b/ui/src/Components/MainModal/Configuration/GridLabelName.tsx
index b05cbcf15..3d8649075 100644
--- a/ui/src/Components/MainModal/Configuration/GridLabelName.tsx
+++ b/ui/src/Components/MainModal/Configuration/GridLabelName.tsx
@@ -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 (
diff --git a/ui/src/Components/MainModal/Configuration/ThemeConfiguration.tsx b/ui/src/Components/MainModal/Configuration/ThemeConfiguration.tsx
index 3b4f9e958..4d4afe093 100644
--- a/ui/src/Components/MainModal/Configuration/ThemeConfiguration.tsx
+++ b/ui/src/Components/MainModal/Configuration/ThemeConfiguration.tsx
@@ -24,6 +24,7 @@ const ThemeConfiguration: FC<{
return {
label: settingsStore.themeConfig.options[val].label,
value: val,
+ wasCreated: false,
};
};
diff --git a/ui/src/Components/SilenceModal/Matchers.ts b/ui/src/Components/SilenceModal/Matchers.ts
index b30c2ecf9..00a6af3cb 100644
--- a/ui/src/Components/SilenceModal/Matchers.ts
+++ b/ui/src/Components/SilenceModal/Matchers.ts
@@ -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),
diff --git a/ui/src/Components/SilenceModal/SilenceForm.test.tsx b/ui/src/Components/SilenceModal/SilenceForm.test.tsx
index ae1ee71c4..8935dba7a 100644
--- a/ui/src/Components/SilenceModal/SilenceForm.test.tsx
+++ b/ui/src/Components/SilenceModal/SilenceForm.test.tsx
@@ -110,6 +110,7 @@ describe(" matchers", () => {
{
label: "alertnameEqual",
value: "alertnameEqual",
+ wasCreated: false,
},
],
},
@@ -122,6 +123,7 @@ describe(" matchers", () => {
{
label: "alertnameNotEqual",
value: "alertnameNotEqual",
+ wasCreated: false,
},
],
},
@@ -134,6 +136,7 @@ describe(" matchers", () => {
{
label: ".*alertnameRegex.*",
value: ".*alertnameRegex.*",
+ wasCreated: false,
},
],
},
@@ -146,6 +149,7 @@ describe(" matchers", () => {
{
label: ".*alertnameNegativeRegex.*",
value: ".*alertnameNegativeRegex.*",
+ wasCreated: false,
},
],
},
@@ -158,6 +162,7 @@ describe(" matchers", () => {
{
label: "clusterEqual",
value: "clusterEqual",
+ wasCreated: false,
},
],
},
@@ -170,6 +175,7 @@ describe(" matchers", () => {
{
label: "clusterNotEqual",
value: "clusterNotEqual",
+ wasCreated: false,
},
],
},
@@ -182,6 +188,7 @@ describe(" matchers", () => {
{
label: ".*clusterRegex.*",
value: ".*clusterRegex.*",
+ wasCreated: false,
},
],
},
@@ -194,6 +201,7 @@ describe(" matchers", () => {
{
label: ".*clusterNegativeRegex.*",
value: ".*clusterNegativeRegex.*",
+ wasCreated: false,
},
],
},
@@ -206,6 +214,7 @@ describe(" matchers", () => {
{
label: "fooEqual",
value: "fooEqual",
+ wasCreated: false,
},
],
},
@@ -218,6 +227,7 @@ describe(" matchers", () => {
{
label: "fooNotEqual",
value: "fooNotEqual",
+ wasCreated: false,
},
],
},
@@ -230,6 +240,7 @@ describe(" matchers", () => {
{
label: ".*fooRegex.*",
value: ".*fooRegex.*",
+ wasCreated: false,
},
],
},
@@ -242,6 +253,7 @@ describe(" matchers", () => {
{
label: ".*fooNegativeRegex.*",
value: ".*fooNegativeRegex.*",
+ wasCreated: false,
},
],
},
@@ -288,6 +300,7 @@ describe(" matchers", () => {
{
label: "alertnameEqual",
value: "alertnameEqual",
+ wasCreated: false,
},
],
},
@@ -300,6 +313,7 @@ describe(" matchers", () => {
{
label: "alertnameNotEqual",
value: "alertnameNotEqual",
+ wasCreated: false,
},
],
},
@@ -312,6 +326,7 @@ describe(" matchers", () => {
{
label: ".*alertnameRegex.*",
value: ".*alertnameRegex.*",
+ wasCreated: false,
},
],
},
@@ -324,6 +339,7 @@ describe(" matchers", () => {
{
label: ".*alertnameNegativeRegex.*",
value: ".*alertnameNegativeRegex.*",
+ wasCreated: false,
},
],
},
@@ -336,6 +352,7 @@ describe(" matchers", () => {
{
label: "clusterEqual",
value: "clusterEqual",
+ wasCreated: false,
},
],
},
@@ -348,6 +365,7 @@ describe(" matchers", () => {
{
label: "clusterNotEqual",
value: "clusterNotEqual",
+ wasCreated: false,
},
],
},
@@ -360,6 +378,7 @@ describe(" matchers", () => {
{
label: ".*clusterRegex.*",
value: ".*clusterRegex.*",
+ wasCreated: false,
},
],
},
@@ -372,6 +391,7 @@ describe(" matchers", () => {
{
label: ".*clusterNegativeRegex.*",
value: ".*clusterNegativeRegex.*",
+ wasCreated: false,
},
],
},
@@ -384,6 +404,7 @@ describe(" matchers", () => {
{
label: "fooEqual",
value: "fooEqual",
+ wasCreated: false,
},
],
},
@@ -396,6 +417,7 @@ describe(" matchers", () => {
{
label: "fooNotEqual",
value: "fooNotEqual",
+ wasCreated: false,
},
],
},
@@ -408,6 +430,7 @@ describe(" matchers", () => {
{
label: ".*fooRegex.*",
value: ".*fooRegex.*",
+ wasCreated: false,
},
],
},
@@ -420,6 +443,7 @@ describe(" matchers", () => {
{
label: ".*fooNegativeRegex.*",
value: ".*fooNegativeRegex.*",
+ wasCreated: false,
},
],
},
@@ -531,7 +555,9 @@ describe(" 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(" 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("", () => {
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");
diff --git a/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.test.tsx b/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.test.tsx
index 9831dbdd0..40876ebbd 100644
--- a/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.test.tsx
+++ b/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.test.tsx
@@ -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("", () => {
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);
diff --git a/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.tsx b/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.tsx
index 1290f92b5..3d6abce64 100644
--- a/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.tsx
+++ b/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.tsx
@@ -90,13 +90,16 @@ const LabelValueInput: FC<{
placeholder={isValid ? "Label value" : }
onChange={(
newValue: OnChangeValue,
- _: ActionMeta
+ meta: ActionMeta
) => {
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
diff --git a/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.test.tsx b/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.test.tsx
index 164c01863..50edd2dc1 100644
--- a/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.test.tsx
+++ b/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.test.tsx
@@ -145,6 +145,50 @@ describe("", () => {
).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).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).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).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).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;
diff --git a/ui/src/Stores/Settings.ts b/ui/src/Stores/Settings.ts
index 208ea13b1..79cb3482c 100644
--- a/ui/src/Stores/Settings.ts
+++ b/ui/src/Stores/Settings.ts
@@ -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(
diff --git a/ui/src/Stores/SilenceFormStore.test.ts b/ui/src/Stores/SilenceFormStore.test.ts
index 7a87e356e..1a8f0388b 100644
--- a/ui/src/Stores/SilenceFormStore.test.ts
+++ b/ui/src/Stores/SilenceFormStore.test.ts
@@ -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");
diff --git a/ui/src/Stores/SilenceFormStore.ts b/ui/src/Stores/SilenceFormStore.ts
index f319ec7ec..12ee14531 100644
--- a/ui/src/Stores/SilenceFormStore.ts
+++ b/ui/src/Stores/SilenceFormStore.ts
@@ -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,
diff --git a/ui/src/Stores/__snapshots__/SilenceFormStore.test.ts.snap b/ui/src/Stores/__snapshots__/SilenceFormStore.test.ts.snap
index 199e8079e..c714589cc 100644
--- a/ui/src/Stores/__snapshots__/SilenceFormStore.test.ts.snap
+++ b/ui/src/Stores/__snapshots__/SilenceFormStore.test.ts.snap
@@ -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",
+}
+`;
diff --git a/ui/src/__fixtures__/useFetchGet.ts b/ui/src/__fixtures__/useFetchGet.ts
index 6502c172d..0fa0691b3 100644
--- a/ui/src/__fixtures__/useFetchGet.ts
+++ b/ui/src/__fixtures__/useFetchGet.ts
@@ -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=/,