diff --git a/assets/static/__mocks__/ajaxMock.js b/assets/static/__mocks__/ajaxMock.js new file mode 100644 index 000000000..c561cbae6 --- /dev/null +++ b/assets/static/__mocks__/ajaxMock.js @@ -0,0 +1,12 @@ +const mockXHR = require("mock-xhr"); + +function createServer(status, payload) { + var server = new mockXHR.server(); + server.handle = function (request) { + request.setResponseHeader("Content-Type", "application/json"); + request.receive(status, JSON.stringify(payload)); + }; + return server; +} + +exports.createServer = createServer; diff --git a/assets/static/__snapshots__/silence.test.js.snap b/assets/static/__snapshots__/silence.test.js.snap new file mode 100644 index 000000000..8c674ea2e --- /dev/null +++ b/assets/static/__snapshots__/silence.test.js.snap @@ -0,0 +1,280 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`failed sendSilencePOST() 1`] = ` +"

+ + request failed +

" +`; + +exports[`silence form 1`] = ` +"
+
+ +
+

+ New silence form rendering failed. +

+

+ request failed +

+
" +`; + +exports[`silence form 2`] = ` +"
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
+ + 1 + + +
+
+ + 1 + + +
+
+ + 1 + + +
+
+ +
+ +
+
+
January 2050
SuMoTuWeThFrSa
2627282930311
2345678
9101112131415
16171819202122
23242526272829
303112345
2050
JanFebMarAprMayJunJulAugSepOctNovDec
2045-2056
204520462047204820492050205120522053205420552056
2000-2107
2000 - 20112012 - 20232024 - 20352036 - 20472048 - 20592060 - 20712072 - 20832084 - 20952096 - 2107
01:00
00010203
04050607
08091011
12131415
16171819
20212223
00051015
20253035
40455055
+
+
+
January 2050
SuMoTuWeThFrSa
2627282930311
2345678
9101112131415
16171819202122
23242526272829
303112345
2050
JanFebMarAprMayJunJulAugSepOctNovDec
2045-2056
204520462047204820492050205120522053205420552056
2000-2107
2000 - 20112012 - 20232024 - 20352036 - 20472048 - 20592060 - 20712072 - 20832084 - 20952096 - 2107
02:00
00010203
04050607
08091011
12131415
16171819
20212223
00051015
20253035
40455055
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+
0
+
+ days + +
1
+
+ hours + +
0
+
+ minutes +
+ + + + + + + + + + + +
+
+
+
+ +
+
+ + + + +
+
+ +
+
+ + + + +
+
+ +
+ +
+ +
+ +
+
+ +
+
curl http://localhost/api/v1/silences
+  -X POST --data {
+  \\"matchers\\": [
+    {
+      \\"name\\": \\"foo\\",
+      \\"value\\": \\"bar\\",
+      \\"isRegex\\": false
+    }
+  ],
+  \\"startsAt\\": \\"2050-01-01T01:00:00.000Z\\",
+  \\"endsAt\\": \\"2050-01-01T02:00:00.000Z\\",
+  \\"createdBy\\": \\"\\",
+  \\"comment\\": \\"\\"
+}
+
+ + +
+
" +`; + +exports[`silence form 3`] = ` +"
+ + + + + + + +
+ + mock + + +

+ + abcdef +

+
+
" +`; + +exports[`successful sendSilencePOST() 1`] = ` +"

+ + abcdef +

" +`; diff --git a/assets/static/silence.js b/assets/static/silence.js index 73ccb8366..d4216758e 100644 --- a/assets/static/silence.js +++ b/assets/static/silence.js @@ -328,3 +328,4 @@ function setupSilenceForm() { } exports.setupSilenceForm = setupSilenceForm; +exports.sendSilencePOST = sendSilencePOST; diff --git a/assets/static/silence.test.js b/assets/static/silence.test.js index 2ce6d91dd..0150f02f8 100644 --- a/assets/static/silence.test.js +++ b/assets/static/silence.test.js @@ -1,5 +1,173 @@ -test("silence setupSilenceForm()", () => { - window.jQuery = require("jquery"); +const $ = window.jQuery = require("jquery"); +const moment = require("moment"); +const templatesMock = require("./__mocks__/templatesMock"); +const ajaxMock = require("./__mocks__/ajaxMock"); + +jest.useFakeTimers(); + +test("silence form", () => { + let body = templatesMock.loadTemplates(); + body.push( + "" + + "" + + "" + ); + document.body.innerHTML = body; + + const templates = require("./templates"); + templates.init(); + + const config = require("./config"); + config.init({ + CopySelector: "#copy-settings-with-filter", + SaveSelector: "#save-default-filter", + ResetSelector: "#reset-settings" + }); + const silence = require("./silence"); silence.setupSilenceForm(); + + require("bootstrap/js/tooltip.js"); + require("bootstrap/js/modal.js"); + + // rendering silence form requires AJAX call to pull data + // first check failed request + let ajaxServer = ajaxMock.createServer(500, { + "status": "error", + "errorType": "server_error", + "error": "request failed" + }); + ajaxServer.start(); + // click on the button, modal should show and render via ajax call + $("#silenceButton").click(); + jest.runOnlyPendingTimers(); + let silenceModal = $("#silenceModal").html().trim(); + expect(silenceModal).toMatchSnapshot(); + ajaxServer.stop(); + + // hide the form + $("#silenceModal").modal("hide"); + + // next try successful request + ajaxServer = ajaxMock.createServer(200, { + "groups": [ { + "receiver": "default", + "labels": {"alertname": "fakeAlert"}, + "alerts": [ { + "annotations": {}, + "labels": { + "alertname": "fakeAlert", + "cluster": "prod", + "foo": "bar" + }, + "startsAt": "2017-07-22T01:07:54.32189391Z", + "endsAt": "0001-01-01T00:00:00Z", + "state": "active", + "alertmanager": [ { + "name": "mock", + "uri": "http://localhost", + "state": "active", + "startsAt": "2017-07-22T01:07:54.32189391Z", + "endsAt": "0001-01-01T00:00:00Z", + "source": "localhost/prometheus", + "silences": {} + } ], + "receiver": "default", + "links": {} + } ], + "id": "12345", + "hash": "abcdef", + "stateCount": {"active": 1, "suppressed": 0, "unprocessed": 0} + } ] + }); + ajaxServer.start(); + // click on the button, modal should show and render via ajax call + $("#silenceButton").click(); + jest.runOnlyPendingTimers(); + // default times are relative to current time, use fixed values + let startsAt = moment("2050-01-01T01:00:00.000Z").utc(); + let endsAt = moment("2050-01-01T02:00:00.000Z").utc(); + $("#endsAt").data("DateTimePicker").date(endsAt); + $("#startsAt").data("DateTimePicker").date(startsAt); + // compare html to a snapshot + silenceModal = $("#silenceModal").html().trim(); + expect(silenceModal).toMatchSnapshot(); + ajaxServer.stop(); + + // submit silence + ajaxServer = ajaxMock.createServer(200, { + "status": "success", + "data": {"silenceId": "abcdef"} + }); + ajaxServer.start(); + $("#newSilenceForm").submit(); + ajaxServer.stop(); + silenceModal = $("#silenceModal").html().trim(); + expect(silenceModal).toMatchSnapshot(); +}); + +test("successful sendSilencePOST()", () => { + var body = templatesMock.loadTemplates(); + body.push( + "" + + "" + ); + document.body.innerHTML = body; + + const templates = require("./templates"); + templates.init(); + + const ajaxServer = ajaxMock.createServer(200, { + "status": "success", + "data": {"silenceId": "abcdef"} + }); + ajaxServer.start(); + + const silence = require("./silence"); + silence.sendSilencePOST("http://localhost", {}); + + let resultElem = $(".silence-post-result").html().trim(); + expect(resultElem).toMatchSnapshot(); + + ajaxServer.stop(); +}); + +test("failed sendSilencePOST()", () => { + var body = templatesMock.loadTemplates(); + body.push( + "" + + "" + ); + document.body.innerHTML = body; + + const templates = require("./templates"); + templates.init(); + + const ajaxServer = ajaxMock.createServer(500, { + "status": "error", + "errorType": "server_error", + "error": "request failed" + }); + ajaxServer.start(); + + const silence = require("./silence"); + silence.sendSilencePOST("http://localhost", {}); + + let resultElem = $(".silence-post-result").html().trim(); + expect(resultElem).toMatchSnapshot(); + + ajaxServer.stop(); }); diff --git a/assets/static/unsilence.js b/assets/static/unsilence.js index f11d6d671..6eb7a9212 100644 --- a/assets/static/unsilence.js +++ b/assets/static/unsilence.js @@ -18,7 +18,7 @@ function markInProgress(alertmanagerURI, silenceID) { var elem = unsilenceButtonByID(alertmanagerURI, silenceID); elem.attr("title", "Silence is being deleted from Alertmanager"); elem.tooltip("fixTitle"); - elem.find(".fa").removeClass("fa-times").addClass("fa-refresh fa-spin"); + elem.find(".fa").removeClass("fa-trash-o").addClass("fa-refresh fa-spin"); } function markFailed(alertmanagerURI, silenceID, xhr) { @@ -26,12 +26,12 @@ function markFailed(alertmanagerURI, silenceID, xhr) { var elem = unsilenceButtonByID(alertmanagerURI, silenceID); elem.attr("title", err); elem.tooltip("fixTitle"); - elem.find(".fa").removeClass("fa-times fa-refresh fa-spin").addClass("fa-exclamation-circle text-danger"); + elem.find(".fa").removeClass("fa-trash-o fa-refresh fa-spin").addClass("fa-exclamation-circle text-danger"); // Disable button, wait 5s and reset button to the original state elem.data("disabled", "true"); setTimeout(function() { - elem.find(".fa").removeClass("fa-exclamation-circle text-danger").addClass("fa-times"); + elem.find(".fa").removeClass("fa-exclamation-circle text-danger").addClass("fa-trash-o"); elem.removeData("disabled"); elem.attr("title", "Delete this silence"); elem.tooltip("fixTitle"); @@ -42,7 +42,7 @@ function markSuccess(alertmanagerURI, silenceID) { var elem = unsilenceButtonByID(alertmanagerURI, silenceID); elem.attr("title", "Silence deleted from Alertmanager"); elem.tooltip("fixTitle"); - elem.find(".fa").removeClass("fa-times fa-refresh fa-spin").addClass("fa-check-circle text-success"); + elem.find(".fa").removeClass("fa-trash-o fa-refresh fa-spin").addClass("fa-check-circle text-success"); // disable button so it's no longer clickable elem.data("disabled", "true"); } diff --git a/assets/static/unsilence.test.js b/assets/static/unsilence.test.js index 1e94b8d4d..7d2bf3027 100644 --- a/assets/static/unsilence.test.js +++ b/assets/static/unsilence.test.js @@ -1,5 +1,66 @@ -test("silence setupSilenceForm()", () => { - window.jQuery = require("jquery"); +const $ = window.jQuery = require("jquery"); +const templatesMock = require("./__mocks__/templatesMock"); +const ajaxMock = require("./__mocks__/ajaxMock"); + +const unsilenceButtonHTML = + ""; + +jest.useFakeTimers(); + +test("unsilence button icons after success", () => { + var body = templatesMock.loadTemplates(); + body.push(unsilenceButtonHTML); + document.body.innerHTML = body; + + require("bootstrap/js/tooltip.js"); const unsilence = require("./unsilence"); + + const ajaxServer = ajaxMock.createServer(200, {"status":"success"}); + ajaxServer.start(); + unsilence.init(); + // icon should be trash-o before clicking + expect($("button > span.fa").hasClass("fa-trash-o")).toBe(true); + $("button.silence-delete").click(); + // and switch to green check mark in circle after + expect($("button > span.fa").hasClass("fa-trash-o")).toBe(false); + expect($("button > span.fa").hasClass("fa-check-circle")).toBe(true); + expect($("button > span.fa").hasClass("text-success")).toBe(true); + + ajaxServer.stop(); +}); + +test("unsilence button icons after failed delete", () => { + var body = templatesMock.loadTemplates(); + body.push(unsilenceButtonHTML); + document.body.innerHTML = body; + + require("bootstrap/js/tooltip.js"); + const unsilence = require("./unsilence"); + + const ajaxServer = ajaxMock.createServer(500, { + "status": "error", + "errorType": "server_error", + "error": "end time must not be modified for elapsed silence" + }); + ajaxServer.start(); + + unsilence.init(); + // icon should be trash-o before clicking + expect($("button > span.fa").hasClass("fa-trash-o")).toBe(true); + $("button.silence-delete").click(); + // and switch to green check mark in circle after + expect($("button > span.fa").hasClass("fa-trash-o")).toBe(false); + expect($("button > span.fa").hasClass("fa-exclamation-circle")).toBe(true); + expect($("button > span.fa").hasClass("text-danger")).toBe(true); + + // run timers, it should reset the icon back to trash-o + jest.runOnlyPendingTimers(); + expect($("button > span.fa").hasClass("fa-trash-o")).toBe(true); + + ajaxServer.stop(); });