fix(ui): drop qs

This commit is contained in:
Lukasz Mierzwa
2026-03-06 16:14:03 +00:00
committed by Łukasz Mierzwa
parent fd947229fc
commit bbb3648b86
5 changed files with 32 additions and 59 deletions

24
ui/package-lock.json generated
View File

@@ -33,7 +33,6 @@
"mobx-react-lite": "4.1.1",
"mobx-stored": "1.1.0",
"promise-retry": "2.0.1",
"qs": "6.15.0",
"react": "19.2.4",
"react-cool-dimensions": "3.0.1",
"react-day-picker": "9.14.0",
@@ -65,7 +64,6 @@
"@types/lodash.uniqueid": "4.0.9",
"@types/node": "25.3.5",
"@types/promise-retry": "1.1.6",
"@types/qs": "6.14.0",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@typescript-eslint/eslint-plugin": "8.56.1",
@@ -4730,13 +4728,6 @@
"@types/retry": "*"
}
},
"node_modules/@types/qs": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
@@ -10936,21 +10927,6 @@
],
"license": "MIT"
},
"node_modules/qs": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
"integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",

View File

@@ -29,7 +29,6 @@
"mobx-react-lite": "4.1.1",
"mobx-stored": "1.1.0",
"promise-retry": "2.0.1",
"qs": "6.15.0",
"react": "19.2.4",
"react-cool-dimensions": "3.0.1",
"react-day-picker": "9.14.0",
@@ -61,7 +60,6 @@
"@types/lodash.uniqueid": "4.0.9",
"@types/node": "25.3.5",
"@types/promise-retry": "1.1.6",
"@types/qs": "6.14.0",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@typescript-eslint/eslint-plugin": "8.56.1",

View File

@@ -146,7 +146,7 @@ describe("<MatchCounter />", () => {
renderMatchCounter();
expect(
(useFetchGet as jest.MockedFunction<typeof useFetchGet>).mock.calls[0][0],
).toBe("./alertList.json?q=foo%3D~%5Ebar%24");
).toBe("./alertList.json?q=foo%3D%7E%5Ebar%24");
});
it("sends correct query string for a 'foo=(x)' matcher with wasCreated=true & isRegex=false", () => {
@@ -168,7 +168,7 @@ describe("<MatchCounter />", () => {
renderMatchCounter();
expect(
(useFetchGet as jest.MockedFunction<typeof useFetchGet>).mock.calls[0][0],
).toBe("./alertList.json?q=foo%3D~%5E%28x%29%24");
).toBe("./alertList.json?q=foo%3D%7E%5E%28x%29%24");
});
it("sends correct query string for a 'foo=(x)' matcher with wasCreated=false & isRegex=false", () => {
@@ -190,7 +190,7 @@ describe("<MatchCounter />", () => {
renderMatchCounter();
expect(
(useFetchGet as jest.MockedFunction<typeof useFetchGet>).mock.calls[0][0],
).toBe("./alertList.json?q=foo%3D~%5E%5C%28x%5C%29%24");
).toBe("./alertList.json?q=foo%3D%7E%5E%5C%28x%5C%29%24");
});
it("sends correct query string for a 'foo=~(bar|baz)' matcher", () => {
@@ -200,7 +200,7 @@ describe("<MatchCounter />", () => {
renderMatchCounter();
expect(
(useFetchGet as jest.MockedFunction<typeof useFetchGet>).mock.calls[0][0],
).toBe("./alertList.json?q=foo%3D~%5E%28bar%7Cbaz%29%24");
).toBe("./alertList.json?q=foo%3D%7E%5E%28bar%7Cbaz%29%24");
});
it("selecting one Alertmanager instance appends it to the filters", () => {
@@ -213,7 +213,9 @@ describe("<MatchCounter />", () => {
renderMatchCounter();
expect(
(useFetchGet as jest.MockedFunction<typeof useFetchGet>).mock.calls[0][0],
).toBe("./alertList.json?q=foo%3Dbar&q=%40alertmanager%3D~%5E%28am1%29%24");
).toBe(
"./alertList.json?q=foo%3Dbar&q=%40alertmanager%3D%7E%5E%28am1%29%24",
);
});
it("selecting two Alertmanager instances appends it correctly to the filters", () => {
@@ -231,7 +233,7 @@ describe("<MatchCounter />", () => {
expect(
(useFetchGet as jest.MockedFunction<typeof useFetchGet>).mock.calls[0][0],
).toBe(
"./alertList.json?q=foo%3Dbar&q=%40alertmanager%3D~%5E%28am1%7Cam2%29%24",
"./alertList.json?q=foo%3Dbar&q=%40alertmanager%3D%7E%5E%28am1%7Cam2%29%24",
);
});
});

View File

@@ -47,7 +47,7 @@ describe("<SilencePreview />", () => {
]);
renderSilencePreview();
expect(useFetchGet).toHaveBeenCalledWith(
"./alertList.json?q=foo%3Dbar&q=%40alertmanager%3D~%5E%28amValue%29%24",
"./alertList.json?q=foo%3Dbar&q=%40alertmanager%3D%7E%5E%28amValue%29%24",
);
});
@@ -57,7 +57,7 @@ describe("<SilencePreview />", () => {
]);
renderSilencePreview();
expect(useFetchGet).toHaveBeenCalledWith(
"./alertList.json?q=foo%3Dbar&q=%40alertmanager%3D~%5E%28am1%7Cam2%29%24",
"./alertList.json?q=foo%3Dbar&q=%40alertmanager%3D%7E%5E%28am1%7Cam2%29%24",
);
});

View File

@@ -2,8 +2,6 @@ import { observable, action, computed, toJS } from "mobx";
import throttle from "lodash.throttle";
import qs from "qs";
import { FetchGet } from "Common/Fetch";
import type {
APIAlertmanagerUpstreamT,
@@ -18,23 +16,15 @@ import type {
AlertsRequestT,
} from "Models/APITypes";
const QueryStringEncodeOptions = {
encodeValuesOnly: true, // don't encode q[]
indices: false, // go-gin doesn't support parsing q[0]=foo&q[1]=bar
};
function FormatAlertsQ(filters: string[]): string {
return qs.stringify({ q: filters }, QueryStringEncodeOptions);
return new URLSearchParams(filters.map((f) => ["q", f])).toString();
}
// generate URL for the UI with a set of filters
function FormatAPIFilterQuery(filters: string[]): string {
return qs.stringify(
Object.assign(DecodeLocationSearch(window.location.search).params, {
q: filters,
}),
QueryStringEncodeOptions,
);
const params = DecodeLocationSearch(window.location.search).params;
const merged = { ...params, q: filters || [] };
return new URLSearchParams(merged.q.map((f) => ["q", f])).toString();
}
// format URI for react UI -> Go backend requests
@@ -46,6 +36,7 @@ function FormatBackendURI(path: string): string {
// and decodes it into a dict with some extra metadata
interface QueryParamsT {
q: string[];
m?: string;
}
interface DecodeLocationSearchReturnT {
params: QueryParamsT;
@@ -55,26 +46,32 @@ function DecodeLocationSearch(
searchString: string,
): DecodeLocationSearchReturnT {
let defaultsUsed = true;
let params: QueryParamsT = { q: [] };
const params: QueryParamsT = { q: [] };
if (searchString !== "") {
const parsed = qs.parse(searchString.split("?")[1]) as {
[key: string]: string | string[];
};
params = Object.assign(params, parsed);
const usp = new URLSearchParams(searchString.split("?")[1]);
const mValue = usp.get("m");
if (mValue !== null) {
params.m = mValue;
}
const qValues = [...usp.getAll("q"), ...usp.getAll("q[]")];
let parsedQ: string | string[] | undefined;
if (qValues.length > 0) {
parsedQ = qValues.length === 1 ? qValues[0] : qValues;
}
if (parsed.q !== undefined) {
if (parsedQ !== undefined) {
defaultsUsed = false;
if (parsed.q === "") {
if (parsedQ === "") {
params.q = [];
} else if (Array.isArray(parsed.q)) {
} else if (Array.isArray(parsedQ)) {
// first filter out duplicates
// then filter out empty strings, so 'q=' doesn't end up [""] but rather []
params.q = parsed.q
.filter((v: string, i: number) => parsed.q.indexOf(v) === i)
params.q = parsedQ
.filter((v: string, i: number) => parsedQ.indexOf(v) === i)
.filter((v: string) => v !== "");
} else {
params.q = [parsed.q];
params.q = [parsedQ];
}
}
}