From d0f59977b0774fdd046a2e05915ce8c535b06217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 12 Feb 2020 14:14:29 +0000 Subject: [PATCH] fix(ui): remove alertmanager API V1 support --- ui/package-lock.json | 3 +- ui/package.json | 1 - ui/src/Components/AlertAck/index.js | 45 +++----- ui/src/Components/AlertAck/index.test.js | 92 ++------------- .../AlertGroup/Alert/AlertMenu.test.js | 2 +- .../AlertGroup/GroupHeader/GroupMenu.test.js | 2 +- .../ManagedSilence/DeleteSilence.js | 53 ++------- .../ManagedSilence/DeleteSilence.test.js | 102 ++++++----------- .../ManagedSilence/SilenceComment.test.js | 6 +- .../ManagedSilence/SilenceDetails.test.js | 2 +- .../ManagedSilence/index.stories.js | 4 +- .../Components/ManagedSilence/index.test.js | 10 +- .../AlertManagerInput/index.test.js | 6 +- .../SilenceModal/Browser/index.test.js | 2 +- .../SilenceModal/SilenceModalContent.test.js | 2 +- .../SilenceSubmit/SilenceSubmitProgress.js | 67 +++-------- .../SilenceSubmitProgress.test.js | 108 +++++------------- .../Components/SilenceModal/index.stories.js | 8 +- ui/src/Stores/AlertStore.test.js | 8 +- ui/src/__mocks__/Alerts.js | 2 +- 20 files changed, 142 insertions(+), 383 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 50c96fdb6..b4c9b6f84 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -20942,7 +20942,8 @@ "semver": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==" + "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", + "dev": true }, "send": { "version": "0.17.1", diff --git a/ui/package.json b/ui/package.json index 6aaa62a97..d4c7f311e 100644 --- a/ui/package.json +++ b/ui/package.json @@ -56,7 +56,6 @@ "react-select": "3.0.8", "react-tippy": "1.3.4", "react-transition-group": "4.3.0", - "semver": "7.1.3", "typeface-open-sans": "0.0.75", "typescript": "3.7.5" }, diff --git a/ui/src/Components/AlertAck/index.js b/ui/src/Components/AlertAck/index.js index 4f6839bc5..657682e6b 100644 --- a/ui/src/Components/AlertAck/index.js +++ b/ui/src/Components/AlertAck/index.js @@ -6,8 +6,6 @@ import { observer } from "mobx-react"; import moment from "moment"; -import satisfies from "semver/functions/satisfies"; - import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCheck } from "@fortawesome/free-solid-svg-icons/faCheck"; import { faCheckCircle } from "@fortawesome/free-solid-svg-icons/faCheckCircle"; @@ -142,38 +140,23 @@ const AlertAck = observer( return; } - const isOpenAPI = satisfies(am.version, ">=0.16.0"); - - const uri = isOpenAPI - ? `${am.uri}/api/v2/silences` - : `${am.uri}/api/v1/silences`; - - this.submitState.silencesByCluster[cluster].fetch = FetchPost(uri, { - body: JSON.stringify( - this.submitState.silencesByCluster[cluster].payload - ), - headers: { - "Content-Type": "application/json", - ...am.headers + this.submitState.silencesByCluster[cluster].fetch = FetchPost( + `${am.uri}/api/v2/silences`, + { + body: JSON.stringify( + this.submitState.silencesByCluster[cluster].payload + ), + headers: { + "Content-Type": "application/json", + ...am.headers + } } - }) + ) .then(result => { - if (isOpenAPI) { - if (result.ok) { - return result - .json() - .then(r => this.submitState.markDone(cluster)); - } else { - this.maybeTryAgainAfterError(cluster); - } + if (result.ok) { + return result.json().then(r => this.submitState.markDone(cluster)); } else { - return result - .json() - .then(r => - r.status === "success" - ? this.submitState.markDone(cluster) - : this.maybeTryAgainAfterError(cluster) - ); + this.maybeTryAgainAfterError(cluster); } }) .catch(() => { diff --git a/ui/src/Components/AlertAck/index.test.js b/ui/src/Components/AlertAck/index.test.js index 98b33af45..03e20c8b0 100644 --- a/ui/src/Components/AlertAck/index.test.js +++ b/ui/src/Components/AlertAck/index.test.js @@ -38,7 +38,7 @@ beforeEach(() => { readonly: false, headers: { foo: "bar" }, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "default", clusterMembers: ["default"] } @@ -102,22 +102,8 @@ describe("", () => { expect(toDiffableHtml(tree.html())).toMatch(/fa-exclamation-circle/); }); - it("[v1] uses faCheckCircle after successful fetch", async () => { - fetch.mockResponse( - JSON.stringify({ status: "success", data: { silenceId: "123456789" } }) - ); - const tree = MountedAlertAck(); - const button = tree.find("span.badge"); - button.simulate("click"); - await expect( - tree.instance().submitState.silencesByCluster["default"].fetch - ).resolves.toBeUndefined(); - expect(toDiffableHtml(tree.html())).toMatch(/fa-check-circle/); - }); - - it("[v2] uses faCheckCircle after successful fetch", async () => { + it("uses faCheckCircle after successful fetch", async () => { fetch.mockResponse(JSON.stringify({ silenceID: "123" })); - alertStore.data.upstreams.instances[0].version = "0.16.2"; const tree = MountedAlertAck(); const button = tree.find("span.badge"); button.simulate("click"); @@ -166,9 +152,7 @@ describe("", () => { }); it("sends correct payload", () => { - fetch.mockResponse( - JSON.stringify({ status: "success", data: { silenceId: "123456789" } }) - ); + fetch.mockResponse(JSON.stringify({ silenceID: "123456789" })); silenceFormStore.data.author = "karma/ui"; MountAndClick(); @@ -262,71 +246,13 @@ describe("", () => { }); }); - it("[v1] sends POST request to /api/v1/silences", () => { - MountAndClick(); - const uri = fetch.mock.calls[0][0]; - expect(uri).toBe("http://localhost/api/v1/silences"); - }); - - it("[v2] sends POST request to /api/v2/silences", () => { - alertStore.data.upstreams.instances[0].version = "0.16.2"; + it("sends POST request to /api/v2/silences", () => { MountAndClick(); const uri = fetch.mock.calls[0][0]; expect(uri).toBe("http://localhost/api/v2/silences"); }); - it("[v1] will retry on another cluster member after fetch failure", async () => { - fetch - .mockResponseOnce(JSON.stringify({ status: "error" })) - .mockResponseOnce( - JSON.stringify({ status: "success", data: { silenceId: "123456789" } }) - ); - alertStore.data.upstreams = { - clusters: { default: ["default", "fallback"] }, - instances: [ - { - name: "default", - uri: "http://am1.example.com", - publicURI: "http://am1.example.com", - readonly: false, - headers: {}, - error: "", - version: "0.15.0", - cluster: "default", - clusterMembers: ["default", "fallback"] - }, - { - name: "fallback", - uri: "http://am2.example.com", - publicURI: "http://am2.example.com", - readonly: false, - headers: {}, - error: "", - version: "0.15.0", - cluster: "default", - clusterMembers: ["default", "fallback"] - } - ] - }; - - const tree = MountedAlertAck(); - const button = tree.find("span.badge"); - button.simulate("click"); - await expect( - tree.instance().submitState.silencesByCluster["default"].fetch - ).resolves.toBeUndefined(); - expect(fetch.mock.calls[0][0]).toBe( - "http://am2.example.com/api/v1/silences" - ); - await expect( - tree.instance().submitState.silencesByCluster["default"].fetch - ).resolves.toBeUndefined(); - expect(fetch.mock.calls[1][0]).toBe( - "http://am1.example.com/api/v1/silences" - ); - }); - - it("[v2] will retry on another cluster member after fetch failure", async () => { + it("will retry on another cluster member after fetch failure", async () => { fetch .mockResponseOnce("error message", { status: 500 }) .mockResponseOnce(JSON.stringify({ silenceID: "123" })); @@ -340,7 +266,7 @@ describe("", () => { readonly: false, headers: {}, error: "", - version: "0.16.2", + version: "0.17.0", cluster: "default", clusterMembers: ["default", "fallback"] }, @@ -351,7 +277,7 @@ describe("", () => { readonly: false, headers: {}, error: "", - version: "0.16.2", + version: "0.17.0", cluster: "default", clusterMembers: ["default", "fallback"] } @@ -389,7 +315,7 @@ describe("", () => { readonly: false, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "default", clusterMembers: ["default", "fallback"] } @@ -403,7 +329,7 @@ describe("", () => { tree.instance().submitState.silencesByCluster["default"].fetch ).resolves.toBeUndefined(); expect(fetch.mock.calls[0][0]).toBe( - "http://am1.example.com/api/v1/silences" + "http://am1.example.com/api/v2/silences" ); expect(consoleSpy).toHaveBeenCalledTimes(1); }); diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js index 883139233..1c8a122ef 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js @@ -28,7 +28,7 @@ beforeEach(() => { readonly: false, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "default", clusterMembers: ["am1"] } diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js index cf5769438..a1c6df152 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js @@ -26,7 +26,7 @@ beforeEach(() => { readonly: false, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "default", clusterMembers: ["am1"] } diff --git a/ui/src/Components/ManagedSilence/DeleteSilence.js b/ui/src/Components/ManagedSilence/DeleteSilence.js index 21317e000..c9caee3f2 100644 --- a/ui/src/Components/ManagedSilence/DeleteSilence.js +++ b/ui/src/Components/ManagedSilence/DeleteSilence.js @@ -4,8 +4,6 @@ import PropTypes from "prop-types"; import { observable, action } from "mobx"; import { observer } from "mobx-react"; -import satisfies from "semver/functions/satisfies"; - import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faTrash } from "@fortawesome/free-solid-svg-icons/faTrash"; import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle"; @@ -115,26 +113,6 @@ const DeleteSilenceModalContent = observer( .filter(u => u.cluster === this.props.cluster) .slice(0, 1)[0]; - parseAlertmanagerResponse = response => { - /* - {"status": "success"} - or - { - "status": "error", - "errorType": "bad_data", - "error": "silence 706959fd-4590-4e21-b983-859ba6ec0e1a already expired" - } - */ - if (response.status === "success") { - this.deleteState.setError(null); - } else if (response.status === "error" && response.error) { - this.deleteState.setError(response.error); - } else { - this.deleteState.setError(JSON.stringify(response)); - } - this.deleteState.setDone(); - }; - onFetchPreview = () => { const { silence } = this.props; @@ -169,32 +147,25 @@ const DeleteSilenceModalContent = observer( const alertmanager = this.getAlertmanager(); - const isOpenAPI = satisfies(alertmanager.version, ">=0.16.0"); - - const uri = isOpenAPI - ? `${alertmanager.uri}/api/v2/silence/${silence.id}` - : `${alertmanager.uri}/api/v1/silence/${silence.id}`; - - this.deleteState.fetch = FetchDelete(uri, { - headers: alertmanager.headers - }) + this.deleteState.fetch = FetchDelete( + `${alertmanager.uri}/api/v2/silence/${silence.id}`, + { + headers: alertmanager.headers + } + ) .then(result => { - if (isOpenAPI) { - if (result.ok) { - this.deleteState.setError(null); - this.deleteState.setDone(); - } else { - result.text().then(this.deleteState.setError); - this.deleteState.setDone(); - } + if (result.ok) { + this.deleteState.setError(null); + this.deleteState.setDone(); } else { - result.json().then(this.parseAlertmanagerResponse); + result.text().then(this.deleteState.setError); + this.deleteState.setDone(); } }) .catch(err => { console.trace(err); this.deleteState.setDone(); - this.deleteState.setError( + return this.deleteState.setError( `Delete request failed with: ${err.message}` ); }); diff --git a/ui/src/Components/ManagedSilence/DeleteSilence.test.js b/ui/src/Components/ManagedSilence/DeleteSilence.test.js index 0d1583d42..c30e6000e 100644 --- a/ui/src/Components/ManagedSilence/DeleteSilence.test.js +++ b/ui/src/Components/ManagedSilence/DeleteSilence.test.js @@ -18,7 +18,7 @@ beforeEach(() => { silenceFormStore = new SilenceFormStore(); cluster = "am"; silence = MockSilence(); - fetch.mockResponse(JSON.stringify(MockAPIResponse())); + fetch.mockResponseOnce(JSON.stringify(MockAPIResponse())); alertStore.data.upstreams = { instances: [ @@ -28,7 +28,7 @@ beforeEach(() => { uri: "http://localhost:9093", readonly: false, error: "", - version: "0.15.3", + version: "0.17.0", headers: {}, clusterMembers: ["am1"] } @@ -94,6 +94,17 @@ const VerifyResponse = async response => { return tree; }; +const VerifyError = async error => { + const tree = MountedDeleteSilenceModalContent(); + await expect(tree.instance().previewState.fetch).resolves.toBeUndefined(); + + fetch.mockResponseOnce(error, { status: 500 }); + tree.find(".btn-danger").simulate("click"); + await expect(tree.instance().deleteState.fetch).resolves.toBeUndefined(); + + return tree; +}; + describe("", () => { it("label is 'Delete' by default", () => { const tree = MountedDeleteSilence(); @@ -147,8 +158,10 @@ describe("", () => { expect(fetch).toHaveBeenCalled(); }); - it("renders ErrorMessage on failed fetch", async () => { - jest.spyOn(console, "trace").mockImplementation(() => {}); + it("renders ErrorMessage on failed preview fetch", async () => { + const consoleSpy = jest + .spyOn(console, "trace") + .mockImplementation(() => {}); fetch.resetMocks(); fetch.mockReject(new Error("Fetch error")); @@ -156,9 +169,10 @@ describe("", () => { await expect(tree.instance().previewState.fetch).resolves.toBeUndefined(); tree.update(); expect(tree.find("ErrorMessage")).toHaveLength(1); + expect(consoleSpy).toHaveBeenCalledTimes(1); }); - it("renders ErrorMessage on fetch with non-JSON response", async () => { + it("renders ErrorMessage on preview fetch with non-JSON response", async () => { fetch.mockResponseOnce("not json"); jest.spyOn(console, "trace").mockImplementation(() => {}); fetch.resetMocks(); @@ -170,16 +184,7 @@ describe("", () => { expect(tree.find("ErrorMessage")).toHaveLength(1); }); - it("[v1] sends a DELETE request after clicking 'Confirm' button", async () => { - await VerifyResponse({ status: "success" }); - expect(fetch.mock.calls[1][0]).toBe( - "http://localhost:9093/api/v1/silence/04d37636-2350-4878-b382-e0b50353230f" - ); - expect(fetch.mock.calls[1][1]).toMatchObject({ method: "DELETE" }); - }); - - it("[v2] sends a DELETE request after clicking 'Confirm' button", async () => { - alertStore.data.upstreams.instances[0].version = "0.16.2"; + it("sends a DELETE request after clicking 'Confirm' button", async () => { await VerifyResponse({ status: "success" }); expect(fetch.mock.calls[1][0]).toBe( "http://localhost:9093/api/v2/silence/04d37636-2350-4878-b382-e0b50353230f" @@ -187,27 +192,11 @@ describe("", () => { expect(fetch.mock.calls[1][1]).toMatchObject({ method: "DELETE" }); }); - it("[v1] sends headers from alertmanager config", async () => { + it("sends headers from alertmanager config", async () => { alertStore.data.upstreams.instances[0].headers = { Authorization: "Basic ***" }; await VerifyResponse({ status: "success" }); - expect(fetch.mock.calls[1][0]).toBe( - "http://localhost:9093/api/v1/silence/04d37636-2350-4878-b382-e0b50353230f" - ); - expect(fetch.mock.calls[1][1]).toMatchObject({ - credentials: "include", - method: "DELETE", - headers: { Authorization: "Basic ***" } - }); - }); - - it("[v1] sends headers from alertmanager config", async () => { - alertStore.data.upstreams.instances[0].headers = { - Authorization: "Basic ***" - }; - alertStore.data.upstreams.instances[0].version = "0.16.2"; - await VerifyResponse({ status: "success" }); expect(fetch.mock.calls[1][0]).toBe( "http://localhost:9093/api/v2/silence/04d37636-2350-4878-b382-e0b50353230f" ); @@ -221,7 +210,7 @@ describe("", () => { it("'Confirm' button is no-op after successful DELETE", async () => { const tree = await VerifyResponse({ status: "success" }); expect(fetch.mock.calls[1][0]).toBe( - "http://localhost:9093/api/v1/silence/04d37636-2350-4878-b382-e0b50353230f" + "http://localhost:9093/api/v2/silence/04d37636-2350-4878-b382-e0b50353230f" ); expect(fetch.mock.calls[1][1]).toMatchObject({ method: "DELETE" }); @@ -232,58 +221,37 @@ describe("", () => { expect(fetch.mock.calls).toHaveLength(2); }); - it("renders SuccessMessage on 'success' response status", async () => { + it("renders SuccessMessage on successful response status", async () => { const tree = await VerifyResponse({ status: "success" }); tree.update(); expect(tree.find("SuccessMessage")).toHaveLength(1); }); - it("renders ErrorMessage on 'error' response status", async () => { - const tree = await VerifyResponse({ status: "error", error: "fake error" }); + it("renders ErrorMessage on failed delete fetch request", async () => { + const tree = await VerifyError("mock error"); + + tree.find(".btn-danger").simulate("click"); + await expect(tree.instance().deleteState.fetch).resolves.toBeUndefined(); + tree.update(); expect(tree.find("ErrorMessage")).toHaveLength(1); }); - it("renders ErrorMessage on unhandled response status", async () => { - const tree = await VerifyResponse({ status: "foo bar" }); - tree.update(); - expect(tree.find("ErrorMessage")).toHaveLength(1); - }); - - it("renders ErrorMessage on unhandled response body", async () => { - const tree = await VerifyResponse({ foo: "bar" }); - tree.update(); - expect(tree.find("ErrorMessage")).toHaveLength(1); - }); - - it("[v1] renders ErrorMessage on failed fetch request", async () => { - const tree = MountedDeleteSilenceModalContent(); - await expect(tree.instance().previewState.fetch).resolves.toBeUndefined(); - - jest.spyOn(console, "trace").mockImplementation(() => {}); + it("renders ErrorMessage on failed delete fetch", async () => { + const consoleSpy = jest + .spyOn(console, "trace") + .mockImplementation(() => {}); fetch.resetMocks(); + fetch.mockResponseOnce(JSON.stringify(MockAPIResponse())); fetch.mockReject(new Error("Fetch error")); - tree.find(".btn-danger").simulate("click"); - await expect(tree.instance().deleteState.fetch).resolves.toBeUndefined(); - - tree.update(); - expect(tree.find("ErrorMessage")).toHaveLength(1); - }); - - it("[v2] renders ErrorMessage on failed fetch request", async () => { - alertStore.data.upstreams.instances[0].version = "0.16.2"; const tree = MountedDeleteSilenceModalContent(); await expect(tree.instance().previewState.fetch).resolves.toBeUndefined(); - jest.spyOn(console, "trace").mockImplementation(() => {}); - fetch.resetMocks(); - fetch.mockResponseOnce("500 Internal Server Error", { status: 500 }); - tree.find(".btn-danger").simulate("click"); await expect(tree.instance().deleteState.fetch).resolves.toBeUndefined(); - tree.update(); expect(tree.find("ErrorMessage")).toHaveLength(1); + expect(consoleSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/ui/src/Components/ManagedSilence/SilenceComment.test.js b/ui/src/Components/ManagedSilence/SilenceComment.test.js index f1f575a0e..c9aa463f2 100644 --- a/ui/src/Components/ManagedSilence/SilenceComment.test.js +++ b/ui/src/Components/ManagedSilence/SilenceComment.test.js @@ -47,7 +47,7 @@ const MockMultipleClusters = () => { readonly: false, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "default", clusterMembers: ["default", "fallback"] }, @@ -58,7 +58,7 @@ const MockMultipleClusters = () => { readonly: false, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "default", clusterMembers: ["default", "fallback"] }, @@ -69,7 +69,7 @@ const MockMultipleClusters = () => { readonly: false, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "second", clusterMembers: ["second"] } diff --git a/ui/src/Components/ManagedSilence/SilenceDetails.test.js b/ui/src/Components/ManagedSilence/SilenceDetails.test.js index 5695bd2e1..c711568ed 100644 --- a/ui/src/Components/ManagedSilence/SilenceDetails.test.js +++ b/ui/src/Components/ManagedSilence/SilenceDetails.test.js @@ -36,7 +36,7 @@ beforeEach(() => { publicURI: "http://example.com", readonly: false, error: "", - version: "0.15.3", + version: "0.17.0", headers: {} } ], diff --git a/ui/src/Components/ManagedSilence/index.stories.js b/ui/src/Components/ManagedSilence/index.stories.js index fdbfb9f7e..e950a95e0 100644 --- a/ui/src/Components/ManagedSilence/index.stories.js +++ b/ui/src/Components/ManagedSilence/index.stories.js @@ -32,7 +32,7 @@ storiesOf("ManagedSilence", module) publicURI: "http://example.com", readonly: false, error: "", - version: "0.15.3", + version: "0.17.0", headers: {} } ], @@ -50,7 +50,7 @@ storiesOf("ManagedSilence", module) readonly: true, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "ro", clusterMembers: ["readonly"] } diff --git a/ui/src/Components/ManagedSilence/index.test.js b/ui/src/Components/ManagedSilence/index.test.js index 4ffdc9ad5..ab299a74e 100644 --- a/ui/src/Components/ManagedSilence/index.test.js +++ b/ui/src/Components/ManagedSilence/index.test.js @@ -35,7 +35,7 @@ beforeEach(() => { publicURI: "http://example.com", readonly: false, error: "", - version: "0.15.3", + version: "0.17.0", headers: {} } ], @@ -98,7 +98,7 @@ describe("", () => { publicURI: "http://example.com", readonly: false, error: "", - version: "0.15.3", + version: "0.17.0", headers: {} }); }); @@ -114,7 +114,7 @@ describe("", () => { publicURI: "http://example.com", readonly: false, error: "", - version: "0.15.3", + version: "0.17.0", headers: {} }, { @@ -125,7 +125,7 @@ describe("", () => { publicURI: "http://example.com", readonly: true, error: "", - version: "0.15.3", + version: "0.17.0", headers: {} } ], @@ -143,7 +143,7 @@ describe("", () => { publicURI: "http://example.com", readonly: false, error: "", - version: "0.15.3", + version: "0.17.0", headers: {} }); }); diff --git a/ui/src/Components/SilenceModal/AlertManagerInput/index.test.js b/ui/src/Components/SilenceModal/AlertManagerInput/index.test.js index a6cf97797..71cf47468 100644 --- a/ui/src/Components/SilenceModal/AlertManagerInput/index.test.js +++ b/ui/src/Components/SilenceModal/AlertManagerInput/index.test.js @@ -30,7 +30,7 @@ beforeEach(() => { readonly: false, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "ha", clusterMembers: ["am1", "am2"] }, @@ -41,7 +41,7 @@ beforeEach(() => { readonly: false, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "ha", clusterMembers: ["am1", "am2"] }, @@ -52,7 +52,7 @@ beforeEach(() => { readonly: false, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "am3", clusterMembers: ["am3"] } diff --git a/ui/src/Components/SilenceModal/Browser/index.test.js b/ui/src/Components/SilenceModal/Browser/index.test.js index 006616411..25ce5a651 100644 --- a/ui/src/Components/SilenceModal/Browser/index.test.js +++ b/ui/src/Components/SilenceModal/Browser/index.test.js @@ -38,7 +38,7 @@ beforeEach(() => { publicURI: "http://example.com", readonly: false, error: "", - version: "0.15.3", + version: "0.17.0", headers: {} } ], diff --git a/ui/src/Components/SilenceModal/SilenceModalContent.test.js b/ui/src/Components/SilenceModal/SilenceModalContent.test.js index 1c369203c..5a83f52ce 100644 --- a/ui/src/Components/SilenceModal/SilenceModalContent.test.js +++ b/ui/src/Components/SilenceModal/SilenceModalContent.test.js @@ -28,7 +28,7 @@ beforeEach(() => { uri: "http://localhost:9093", readonly: false, error: "", - version: "0.15.3", + version: "0.17.0", headers: {} } ], diff --git a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js index 8e094b666..8a805db15 100644 --- a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js +++ b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js @@ -4,8 +4,6 @@ import PropTypes from "prop-types"; import { action, observable } from "mobx"; import { observer } from "mobx-react"; -import satisfies from "semver/functions/satisfies"; - import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch"; import { faCheckCircle } from "@fortawesome/free-regular-svg-icons/faCheckCircle"; @@ -33,18 +31,18 @@ const SubmitIcon = observer(({ stateValue }) => { return ; }); -const SilenceLink = ({ uri, silenceId }) => ( +const SilenceLink = ({ uri, silenceID }) => ( - {silenceId} + {silenceID} ); SilenceLink.propTypes = { uri: PropTypes.string.isRequired, - silenceId: PropTypes.string.isRequired + silenceID: PropTypes.string.isRequired }; const SilenceSubmitProgress = observer( @@ -84,7 +82,7 @@ const SilenceSubmitProgress = observer( maybeTryAgainAfterError = err => { if (this.submitState.membersToTry.length) { - this.handleAlertmanagerRequest(); + return this.handleAlertmanagerRequest(); } else { this.submitState.markFailed(err.message); } @@ -106,17 +104,10 @@ const SilenceSubmitProgress = observer( if (am === undefined) { const err = `Alertmanager instance "${member}" not found`; console.error(err); - this.maybeTryAgainAfterError(err); - return; + return this.maybeTryAgainAfterError(err); } - const isOpenAPI = satisfies(am.version, ">=0.16.0"); - - const uri = isOpenAPI - ? `${am.uri}/api/v2/silences` - : `${am.uri}/api/v1/silences`; - - this.submitState.fetch = FetchPost(uri, { + this.submitState.fetch = FetchPost(`${am.uri}/api/v2/silences`, { body: JSON.stringify(payload), headers: { "Content-Type": "application/json", @@ -124,51 +115,27 @@ const SilenceSubmitProgress = observer( } }) .then(result => { - if (isOpenAPI) { - if (result.ok) { - return result - .json() - .then(r => this.parseOpenAPIResponse(am.publicURI, r)); - } else { - return result.text().then(text => { - this.submitState.markFailed(text); - return text; - }); - } - } else { + if (result.ok) { return result .json() - .then(r => this.parseAlertmanagerResponse(am.publicURI, r)); + .then(r => this.parseOpenAPIResponse(am.publicURI, r)); + } else { + return result.text().then(text => { + this.submitState.markFailed(text); + return text; + }); } }) - .catch(err => { - this.maybeTryAgainAfterError(err); - }); + .catch(this.maybeTryAgainAfterError); }; parseOpenAPIResponse = (uri, response) => { - const link = ; + const link = ; this.submitState.markDone(link); - // return silenceId so we can assert it in tests + // return silenceID so we can assert it in tests return response.silenceID; }; - parseAlertmanagerResponse = (uri, response) => { - if (response.status === "success") { - const link = ( - - ); - this.submitState.markDone(link); - } else if (response.status === "error") { - this.submitState.markFailed(response.error); - } else { - this.submitState.markFailed(JSON.stringify(response)); - } - - // return status so we can assert it in tests - return response.status; - }; - componentDidMount() { const { members } = this.props; this.submitState.membersToTry = [...members]; diff --git a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js index 8af0ca694..baf40f331 100644 --- a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js +++ b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.test.js @@ -18,16 +18,19 @@ beforeEach(() => { readonly: false, headers: { foo: "bar" }, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "mockAlertmanager", clusterMembers: ["mockAlertmanager"] } ] }; + + fetch.resetMocks(); }); afterEach(() => { jest.restoreAllMocks(); + fetch.resetMocks(); }); const MountedSilenceSubmitProgress = () => { @@ -54,19 +57,11 @@ describe("", () => { expect(fetch.mock.calls).toHaveLength(1); }); - it("[v1] appends /api/v1/silences to the passed URI", async () => { + it("appends /api/v2/silences to the passed URI", async () => { const tree = MountedSilenceSubmitProgress(); await expect(tree.instance().submitState.fetch).resolves.toBeUndefined(); const uri = fetch.mock.calls[0][0]; - expect(uri).toBe("http://localhost/api/v1/silences"); - }); - - it("[v2] appends /api/v2/silences to the passed URI", async () => { - alertStore.data.upstreams.instances[0].version = "0.16.2"; - const tree = MountedSilenceSubmitProgress(); - await expect(tree.instance().submitState.fetch).resolves.toBeUndefined(); - const uri = fetch.mock.calls[0][0]; - expect(uri).toBe("http://localhost/api/v1/silences"); + expect(uri).toBe("http://localhost/api/v2/silences"); }); it("sends correct JSON payload", () => { @@ -86,12 +81,9 @@ describe("", () => { }); it("will retry on another cluster member after fetch failure", async () => { - fetch.resetMocks(); fetch .mockRejectOnce(new Error("mock error message")) - .mockResponseOnce( - JSON.stringify({ status: "success", data: { silenceId: "123456789" } }) - ); + .mockResponseOnce(JSON.stringify({ silenceID: "123456789" })); alertStore.data.upstreams = { clusters: { ha: ["am1", "am2"] }, instances: [ @@ -102,7 +94,7 @@ describe("", () => { readonly: false, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "ha", clusterMembers: ["am1", "am2"] }, @@ -113,7 +105,7 @@ describe("", () => { readonly: false, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "ha", clusterMembers: ["am1", "am2"] } @@ -136,11 +128,11 @@ describe("", () => { ); await expect(tree.instance().submitState.fetch).resolves.toBeUndefined(); expect(fetch.mock.calls[0][0]).toBe( - "http://am2.example.com/api/v1/silences" + "http://am2.example.com/api/v2/silences" ); - await expect(tree.instance().submitState.fetch).resolves.toBe("success"); + await expect(tree.instance().submitState.fetch).resolves.toBe("123456789"); expect(fetch.mock.calls[1][0]).toBe( - "http://am1.example.com/api/v1/silences" + "http://am1.example.com/api/v2/silences" ); }); @@ -160,7 +152,7 @@ describe("", () => { readonly: false, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "ha", clusterMembers: ["am1", "am2"] } @@ -183,7 +175,7 @@ describe("", () => { ); await expect(tree.instance().submitState.fetch).resolves.toBeUndefined(); expect(fetch.mock.calls[0][0]).toBe( - "http://am1.example.com/api/v1/silences" + "http://am1.example.com/api/v2/silences" ); expect(consoleSpy).toHaveBeenCalledTimes(1); }); @@ -207,7 +199,7 @@ describe("", () => { readonly: false, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "ha", clusterMembers: ["am1", "am2"] }, @@ -218,7 +210,7 @@ describe("", () => { readonly: true, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "ha", clusterMembers: ["am1", "am2"] } @@ -242,18 +234,16 @@ describe("", () => { await expect(tree.instance().submitState.fetch).resolves.toBeUndefined(); expect(fetch.mock.calls).toHaveLength(1); expect(fetch.mock.calls[0][0]).toBe( - "http://am1.example.com/api/v1/silences" + "http://am1.example.com/api/v2/silences" ); expect(logs).toEqual(['Alertmanager instance "am2" is read-only']); expect(consoleSpy).toHaveBeenCalledTimes(1); }); it("renders returned silence ID on successful fetch", async () => { - fetch.mockResponseOnce( - JSON.stringify({ status: "success", data: { silenceId: "123456789" } }) - ); + fetch.mockResponseOnce(JSON.stringify({ silenceID: "123456789" })); const tree = MountedSilenceSubmitProgress(); - await expect(tree.instance().submitState.fetch).resolves.toBe("success"); + await expect(tree.instance().submitState.fetch).resolves.toBe("123456789"); // force re-render tree.update(); const silenceLink = tree.find("a"); @@ -262,40 +252,15 @@ describe("", () => { }); it("renders returned error message on failed fetch", async () => { - fetch.mockRejectOnce(new Error("mock error message")); + fetch.mockResponseOnce("mock error message", { status: 500 }); const tree = MountedSilenceSubmitProgress(); - await expect(tree.instance().submitState.fetch).resolves.toBeUndefined(); + await expect(tree.instance().submitState.fetch).resolves.toBe( + "mock error message" + ); expect(tree.text()).toBe("mockAlertmanagermock error message"); }); - it("[v1] renders success icon on successful fetch", async () => { - fetch.mockResponseOnce( - JSON.stringify({ status: "success", data: { silenceId: "123" } }) - ); - const tree = MountedSilenceSubmitProgress(); - await expect(tree.instance().submitState.fetch).resolves.toBe("success"); - tree.update(); - expect(tree.find("FontAwesomeIcon.text-success")).toHaveLength(1); - expect(tree.find("FontAwesomeIcon.text-danger")).toHaveLength(0); - }); - - it("[v1] renders silence link on successful fetch", async () => { - fetch.mockResponseOnce( - JSON.stringify({ status: "success", data: { silenceId: "123" } }) - ); - const tree = MountedSilenceSubmitProgress(); - await expect(tree.instance().submitState.fetch).resolves.toBe("success"); - tree.update(); - expect( - tree - .find("a") - .getDOMNode() - .getAttribute("href") - ).toBe("http://example.com/#/silences/123"); - }); - - it("[v2] renders success icon on successful fetch", async () => { - alertStore.data.upstreams.instances[0].version = "0.16.2"; + it("renders success icon on successful fetch", async () => { fetch.mockResponseOnce(JSON.stringify({ silenceID: "123" })); const tree = MountedSilenceSubmitProgress(); await expect(tree.instance().submitState.fetch).resolves.toBe("123"); @@ -304,8 +269,7 @@ describe("", () => { expect(tree.find("FontAwesomeIcon.text-danger")).toHaveLength(0); }); - it("[v2] renders silence link on successful fetch", async () => { - alertStore.data.upstreams.instances[0].version = "0.16.2"; + it("renders silence link on successful fetch", async () => { fetch.mockResponseOnce(JSON.stringify({ silenceID: "123" })); const tree = MountedSilenceSubmitProgress(); await expect(tree.instance().submitState.fetch).resolves.toBe("123"); @@ -318,17 +282,7 @@ describe("", () => { ).toBe("http://example.com/#/silences/123"); }); - it("[v1] renders error icon on failed fetch", async () => { - fetch.mockResponseOnce(JSON.stringify({ status: "error" })); - const tree = MountedSilenceSubmitProgress(); - await expect(tree.instance().submitState.fetch).resolves.toBe("error"); - tree.update(); - expect(tree.find("FontAwesomeIcon.text-success")).toHaveLength(0); - expect(tree.find("FontAwesomeIcon.text-danger")).toHaveLength(1); - }); - - it("[v2] renders error icon on failed fetch", async () => { - alertStore.data.upstreams.instances[0].version = "0.16.2"; + it("renders error icon on failed fetch", async () => { fetch.mockResponseOnce("error message", { status: 500 }); const tree = MountedSilenceSubmitProgress(); await expect(tree.instance().submitState.fetch).resolves.toBe( @@ -338,14 +292,4 @@ describe("", () => { expect(tree.find("FontAwesomeIcon.text-success")).toHaveLength(0); expect(tree.find("FontAwesomeIcon.text-danger")).toHaveLength(1); }); - - it("renders unhandled 'status' values in the response as error", async () => { - fetch.mockResponseOnce(JSON.stringify({ status: "unhandled" })); - const tree = MountedSilenceSubmitProgress(); - await expect(tree.instance().submitState.fetch).resolves.toBe("unhandled"); - tree.update(); - expect(tree.find("FontAwesomeIcon.text-success")).toHaveLength(0); - expect(tree.find("FontAwesomeIcon.text-danger")).toHaveLength(1); - expect(tree.text()).toBe('mockAlertmanager{"status":"unhandled"}'); - }); }); diff --git a/ui/src/Components/SilenceModal/index.stories.js b/ui/src/Components/SilenceModal/index.stories.js index 7afee41d6..abc9a6b21 100644 --- a/ui/src/Components/SilenceModal/index.stories.js +++ b/ui/src/Components/SilenceModal/index.stories.js @@ -50,7 +50,7 @@ storiesOf("SilenceModal", module) readonly: false, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "default", clusterMembers: ["default"] } @@ -124,7 +124,7 @@ storiesOf("SilenceModal", module) readonly: true, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "default", clusterMembers: ["readonly"] } @@ -189,7 +189,7 @@ storiesOf("SilenceModal", module) publicURI: "http://example.com", readonly: false, error: "", - version: "0.15.3", + version: "0.17.0", headers: {} } ], @@ -252,7 +252,7 @@ storiesOf("SilenceModal", module) publicURI: "http://example.com", readonly: false, error: "", - version: "0.15.3", + version: "0.17.0", headers: {} } ], diff --git a/ui/src/Stores/AlertStore.test.js b/ui/src/Stores/AlertStore.test.js index 98c5e67c7..f02cd8f62 100644 --- a/ui/src/Stores/AlertStore.test.js +++ b/ui/src/Stores/AlertStore.test.js @@ -33,7 +33,7 @@ describe("AlertStore.data", () => { readonly: false, headers: { foo: "bar" }, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "default", clusterMembers: ["default", "readonly"] }, @@ -44,7 +44,7 @@ describe("AlertStore.data", () => { readonly: true, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "default", clusterMembers: ["default", "readonly"] } @@ -69,7 +69,7 @@ describe("AlertStore.data", () => { readonly: true, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "default", clusterMembers: ["ro1", "ro2"] }, @@ -80,7 +80,7 @@ describe("AlertStore.data", () => { readonly: true, headers: {}, error: "", - version: "0.15.0", + version: "0.17.0", cluster: "default", clusterMembers: ["ro1", "ro2"] } diff --git a/ui/src/__mocks__/Alerts.js b/ui/src/__mocks__/Alerts.js index 9b6225f18..1244a7d4f 100644 --- a/ui/src/__mocks__/Alerts.js +++ b/ui/src/__mocks__/Alerts.js @@ -75,7 +75,7 @@ const MockAlertmanager = () => ({ Authorization: "Basic foo bar" }, error: "", - version: "0.15.0", + version: "0.17.0", clusterMembers: ["default"] });