diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js index 8ccba09d3..355deaadd 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js @@ -15,7 +15,7 @@ import { faShareSquare } from "@fortawesome/free-solid-svg-icons/faShareSquare"; import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash"; import { APIGroup } from "Models/API"; -import { FormatAPIFilterQuery } from "Stores/AlertStore"; +import { FormatAlertsQ } from "Stores/AlertStore"; import { AlertStore } from "Stores/AlertStore"; import { SilenceFormStore } from "Stores/SilenceFormStore"; import { QueryOperators, StaticLabels, FormatQuery } from "Common/Query"; @@ -47,9 +47,13 @@ const MenuContent = onClickOutside( groupFilters.push( FormatQuery(StaticLabels.Receiver, QueryOperators.Equal, group.receiver) ); - const groupLink = `${window.location.href}?${FormatAPIFilterQuery( - groupFilters - )}`; + const baseURL = [ + window.location.protocol, + "//", + window.location.host, + window.location.pathname + ].join(""); + const groupLink = `${baseURL}?${FormatAlertsQ(groupFilters)}`; return ( @@ -160,9 +164,7 @@ const GroupMenu = observer( silenceFormStore={silenceFormStore} afterClick={this.collapse.hide} handleClickOutside={this.collapse.hide} - outsideClickIgnoreClass={`components-grid-alertgroup-${ - group.id - }`} + outsideClickIgnoreClass={`components-grid-alertgroup-${group.id}`} /> )} diff --git a/ui/src/Stores/AlertStore.js b/ui/src/Stores/AlertStore.js index eb0b1a972..e6025abb5 100644 --- a/ui/src/Stores/AlertStore.js +++ b/ui/src/Stores/AlertStore.js @@ -46,8 +46,11 @@ function DecodeLocationSearch(searchString) { if (parsed.q === "") { params.q = []; } else if (Array.isArray(parsed.q)) { - // filter out empty strings, so 'q=' doesn't end up [""] but rather [] - params.q = parsed.q.filter(v => v !== ""); + // first filter out duplicates + // then filter out empty strings, so 'q=' doesn't end up [""] but rather [] + params.q = parsed.q + .filter((v, i) => parsed.q.indexOf(v) === i) + .filter(v => v !== ""); } else { params.q = [parsed.q]; } diff --git a/ui/src/Stores/AlertStore.test.js b/ui/src/Stores/AlertStore.test.js index 3f82a11a1..210ccba93 100644 --- a/ui/src/Stores/AlertStore.test.js +++ b/ui/src/Stores/AlertStore.test.js @@ -181,6 +181,13 @@ describe("DecodeLocationSearch", () => { }); }); + it("no value q[]=&q[]= search param is decoded correctly", () => { + expect(DecodeLocationSearch("?q=")).toMatchObject({ + defaultsUsed: false, + params: { q: [] } + }); + }); + it("single value q=foo search param is decoded correctly", () => { expect(DecodeLocationSearch("?q=foo")).toMatchObject({ defaultsUsed: false, @@ -201,6 +208,20 @@ describe("DecodeLocationSearch", () => { params: { q: ["foo", "bar"] } }); }); + + it("multi value q[]=foo&q[]=bar&q[]=foo search param is decoded correctly", () => { + expect(DecodeLocationSearch("?q[]=foo&q[]=bar&q[]=foo")).toMatchObject({ + defaultsUsed: false, + params: { q: ["foo", "bar"] } + }); + }); + + it("multi value q[]=foo&q[]=&q[]=foo search param is decoded correctly", () => { + expect(DecodeLocationSearch("?q[]=foo&q[]=&q[]=foo")).toMatchObject({ + defaultsUsed: false, + params: { q: ["foo"] } + }); + }); }); describe("UpdateLocationSearch", () => {