diff --git a/ui/src/Components/MainModal/index.js b/ui/src/Components/MainModal/index.js
index 542c480dc..9b570428a 100644
--- a/ui/src/Components/MainModal/index.js
+++ b/ui/src/Components/MainModal/index.js
@@ -72,6 +72,7 @@ const MainModal = observer(
settingsStore={settingsStore}
onHide={this.toggle.hide}
isVisible={this.toggle.show}
+ expandAllOptions={false}
/>
diff --git a/ui/src/Components/MainModal/index.stories.js b/ui/src/Components/MainModal/index.stories.js
index fccf34890..5b2ab9e86 100644
--- a/ui/src/Components/MainModal/index.stories.js
+++ b/ui/src/Components/MainModal/index.stories.js
@@ -25,6 +25,7 @@ storiesOf("MainModal", module)
settingsStore={settingsStore}
onHide={() => {}}
isVisible={true}
+ expandAllOptions={true}
/>
);
})
@@ -38,6 +39,7 @@ storiesOf("MainModal", module)
onHide={() => {}}
isVisible={true}
openTab={TabNames.Help}
+ expandAllOptions={true}
/>
);
});
diff --git a/ui/src/Components/ManagedSilence/index.js b/ui/src/Components/ManagedSilence/index.js
index 8db044877..f8620865c 100644
--- a/ui/src/Components/ManagedSilence/index.js
+++ b/ui/src/Components/ManagedSilence/index.js
@@ -23,20 +23,28 @@ const ManagedSilence = observer(
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
onDidUpdate: PropTypes.func,
- onDeleteModalClose: PropTypes.func
+ onDeleteModalClose: PropTypes.func,
+ isOpen: PropTypes.bool
+ };
+ static defaultProps = {
+ isOpen: false
};
- // store collapse state, by default only silence comment is visible
- // the rest of the silence is hidden until expanded by a click
- collapse = observable(
- {
- value: true,
- toggle() {
- this.value = !this.value;
- }
- },
- { toggle: action.bound }
- );
+ constructor(props) {
+ super(props);
+
+ // store collapse state, by default only silence comment is visible
+ // the rest of the silence is hidden until expanded by a click
+ this.collapse = observable(
+ {
+ value: !props.isOpen,
+ toggle() {
+ this.value = !this.value;
+ }
+ },
+ { toggle: action.bound }
+ );
+ }
getAlertmanager = () =>
this.props.alertStore.data.upstreams.instances
diff --git a/ui/src/Components/ManagedSilence/index.stories.js b/ui/src/Components/ManagedSilence/index.stories.js
new file mode 100644
index 000000000..0dd2902ff
--- /dev/null
+++ b/ui/src/Components/ManagedSilence/index.stories.js
@@ -0,0 +1,91 @@
+import React from "react";
+
+import { storiesOf } from "@storybook/react";
+
+import { MockSilence } from "__mocks__/Alerts";
+import { AlertStore } from "Stores/AlertStore";
+import { SilenceFormStore } from "Stores/SilenceFormStore";
+import { ManagedSilence } from ".";
+
+import "Percy.scss";
+
+storiesOf("ManagedSilence", module)
+ .addDecorator(storyFn => (
+
+ ))
+ .add("Silence", () => {
+ const alertStore = new AlertStore([]);
+ const silenceFormStore = new SilenceFormStore();
+ const cluster = "am";
+
+ alertStore.data.upstreams = {
+ instances: [
+ {
+ name: "am1",
+ cluster: cluster,
+ clusterMembers: ["am1"],
+ uri: "http://localhost:9093",
+ publicURI: "http://example.com",
+ error: "",
+ version: "0.15.3",
+ headers: {}
+ }
+ ],
+ clusters: { am: ["am1"] }
+ };
+
+ const silence = MockSilence();
+ silence.startsAt = "2018-08-14T16:00:00Z";
+ silence.endsAt = "2018-08-14T18:00:00Z";
+
+ const expiredSilence = MockSilence();
+ expiredSilence.startsAt = "2018-08-14T10:00:00Z";
+ expiredSilence.endsAt = "2018-08-14T11:00:00Z";
+
+ return (
+
+ );
+ });
diff --git a/ui/src/Components/NavBar/FilterInput/History.js b/ui/src/Components/NavBar/FilterInput/History.js
index d3d384be8..b90072ee0 100644
--- a/ui/src/Components/NavBar/FilterInput/History.js
+++ b/ui/src/Components/NavBar/FilterInput/History.js
@@ -59,87 +59,85 @@ ActionButton.propTypes = {
afterClick: PropTypes.func.isRequired
};
-const HistoryMenu = onClickOutside(
- ({
- popperPlacement,
- popperRef,
- popperStyle,
- filters,
- alertStore,
- settingsStore,
- afterClick,
- onClear
- }) => {
- return (
-
-
-
- Last used filters
-
- {filters.length === 0 ? (
-
Empty
- ) : (
- filters.map(historyFilters => (
-
{
- alertStore.filters.setFilters(historyFilters.map(f => f.raw));
- afterClick();
- }}
- >
-
- {historyFilters.map(f => (
-
- ))}
-
-
- ))
- )}
-
-
-
{
- settingsStore.savedFilters.save(
- alertStore.filters.values.map(f => f.raw)
- );
+const HistoryMenuContent = ({
+ popperPlacement,
+ popperRef,
+ popperStyle,
+ filters,
+ alertStore,
+ settingsStore,
+ afterClick,
+ onClear
+}) => {
+ return (
+
+
+
+ Last used filters
+
+ {filters.length === 0 ? (
+
Empty
+ ) : (
+ filters.map(historyFilters => (
+
{
+ alertStore.filters.setFilters(historyFilters.map(f => f.raw));
+ afterClick();
}}
- afterClick={afterClick}
- />
-
-
-
+ >
+
+ {historyFilters.map(f => (
+
+ ))}
+
+
+ ))
+ )}
+
+
+
{
+ settingsStore.savedFilters.save(
+ alertStore.filters.values.map(f => f.raw)
+ );
+ }}
+ afterClick={afterClick}
+ />
+
+
- );
- }
-);
-HistoryMenu.propTypes = {
+
+ );
+};
+HistoryMenuContent.propTypes = {
popperPlacement: PropTypes.string,
popperRef: PropTypes.func,
popperStyle: PropTypes.object,
@@ -150,6 +148,8 @@ HistoryMenu.propTypes = {
onClear: PropTypes.func.isRequired
};
+const HistoryMenu = onClickOutside(HistoryMenuContent);
+
const History = observer(
class History extends Component {
static propTypes = {
@@ -278,4 +278,4 @@ const History = observer(
}
);
-export { History, HistoryMenu, ReduceFilter };
+export { History, HistoryMenu, HistoryMenuContent, ReduceFilter };
diff --git a/ui/src/Components/NavBar/index.stories.js b/ui/src/Components/NavBar/index.stories.js
index 4698f760f..068bd2fe0 100644
--- a/ui/src/Components/NavBar/index.stories.js
+++ b/ui/src/Components/NavBar/index.stories.js
@@ -5,6 +5,7 @@ import { storiesOf } from "@storybook/react";
import { AlertStore, NewUnappliedFilter } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { SilenceFormStore } from "Stores/SilenceFormStore";
+import { HistoryMenuContent } from "./FilterInput/History";
import { NavBar } from ".";
import "Percy.scss";
@@ -49,11 +50,60 @@ storiesOf("NavBar", module).add("NavBar", () => {
NewFilter("foo", "", "", "", true, true, 2)
];
+ const history = [
+ [NewFilter("alertname=Foo", "alertname", "=", "foo", true, true, 15)],
+ [NewFilter("cluster=staging", "cluster", "=", "staging", true, true, 15)],
+ [
+ NewFilter("cluster=staging", "cluster", "=", "staging", true, true, 15),
+ NewFilter("region=AF", "region", "=", "AF", true, true, 180)
+ ],
+ [
+ NewFilter("cluster=staging", "cluster", "=", "staging", true, true, 15),
+ NewFilter("region=AF", "region", "=", "AF", true, true, 180),
+ NewFilter(
+ "instance!=server1",
+ "instance",
+ "!=",
+ "server1",
+ false,
+ true,
+ 0
+ )
+ ],
+ [
+ NewFilter(
+ "alertname=VeryVeryVeryVeryVeryLoooooooooooooooongAlertnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
+ "alertname",
+ "=",
+ "VeryVeryVeryVeryVeryLoooooooooooooooongAlertnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
+ true,
+ true,
+ 15
+ )
+ ]
+ ];
+
return (
-
+
+
+
+ {}}
+ popperStyle={{}}
+ filters={history}
+ onClear={() => {}}
+ alertStore={alertStore}
+ settingsStore={settingsStore}
+ afterClick={() => {}}
+ handleClickOutside={() => {}}
+ outsideClickIgnoreClass="components-navbar-history"
+ />
+
+
);
});
diff --git a/ui/src/Components/OverviewModal/index.stories.js b/ui/src/Components/OverviewModal/index.stories.js
new file mode 100644
index 000000000..5fa80bb8c
--- /dev/null
+++ b/ui/src/Components/OverviewModal/index.stories.js
@@ -0,0 +1,25 @@
+import React from "react";
+
+import { storiesOf } from "@storybook/react";
+
+import { MockGrid } from "__mocks__/Stories.js";
+import { AlertStore } from "Stores/AlertStore";
+import { OverviewModalContent } from "./OverviewModalContent";
+
+import "Percy.scss";
+
+storiesOf("OverviewModal", module)
+ .addDecorator(storyFn => (
+
+ ))
+ .add("OverviewModal", () => {
+ const alertStore = new AlertStore([]);
+
+ MockGrid(alertStore);
+
+ return
{}} />;
+ });
diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/index.js b/ui/src/Components/SilenceModal/DateTimeSelect/index.js
index afa187647..2c9f92595 100644
--- a/ui/src/Components/SilenceModal/DateTimeSelect/index.js
+++ b/ui/src/Components/SilenceModal/DateTimeSelect/index.js
@@ -171,33 +171,41 @@ TabContentDuration.propTypes = {
const DateTimeSelect = observer(
class DateTimeSelect extends Component {
static propTypes = {
- silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
+ silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
+ openTab: PropTypes.oneOf(Object.values(TabNames))
+ };
+ static defaultProps = {
+ openTab: TabNames.Duration
};
- tab = observable(
- {
- current: TabNames.Duration,
- setStart() {
- this.current = TabNames.Start;
+ constructor(props) {
+ super(props);
+
+ this.tab = observable(
+ {
+ current: props.openTab,
+ setStart() {
+ this.current = TabNames.Start;
+ },
+ setEnd() {
+ this.current = TabNames.End;
+ },
+ setDuration() {
+ this.current = TabNames.Duration;
+ },
+ timeNow: moment().seconds(0),
+ updateTimeNow() {
+ this.timeNow = moment().seconds(0);
+ }
},
- setEnd() {
- this.current = TabNames.End;
- },
- setDuration() {
- this.current = TabNames.Duration;
- },
- timeNow: moment().seconds(0),
- updateTimeNow() {
- this.timeNow = moment().seconds(0);
+ {
+ setStart: action.bound,
+ setEnd: action.bound,
+ setDuration: action.bound,
+ updateTimeNow: action.bound
}
- },
- {
- setStart: action.bound,
- setEnd: action.bound,
- setDuration: action.bound,
- updateTimeNow: action.bound
- }
- );
+ );
+ }
componentDidMount() {
this.tab.updateTimeNow();
@@ -274,4 +282,10 @@ const DateTimeSelect = observer(
}
);
-export { DateTimeSelect, TabContentStart, TabContentEnd, TabContentDuration };
+export {
+ DateTimeSelect,
+ TabContentStart,
+ TabContentEnd,
+ TabContentDuration,
+ TabNames
+};
diff --git a/ui/src/Components/SilenceModal/index.stories.js b/ui/src/Components/SilenceModal/index.stories.js
index f63743163..8a83b0000 100644
--- a/ui/src/Components/SilenceModal/index.stories.js
+++ b/ui/src/Components/SilenceModal/index.stories.js
@@ -13,6 +13,7 @@ import {
MatcherValueToObject,
SilenceTabNames
} from "Stores/SilenceFormStore";
+import { DateTimeSelect, TabNames } from "./DateTimeSelect";
import { SilenceModalContent } from "./SilenceModalContent";
import "Percy.scss";
@@ -25,14 +26,15 @@ const MockMatcher = (name, values, isRegex) => {
return matcher;
};
-storiesOf("SilenceModal", module)
- .addDecorator(storyFn => (
-
-
+const Modal = ({ children }) => (
+
+);
+
+storiesOf("SilenceModal", module)
.add("Editor", () => {
const alertStore = new AlertStore([]);
const settingsStore = new Settings();
@@ -74,14 +76,34 @@ storiesOf("SilenceModal", module)
);
return (
-
{}}
- previewOpen={true}
- onDeleteModalClose={() => {}}
- />
+
+
+ {}}
+ previewOpen={true}
+ onDeleteModalClose={() => {}}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
);
})
.add("Browser", () => {
@@ -135,12 +157,14 @@ storiesOf("SilenceModal", module)
});
return (
- {}}
- onDeleteModalClose={() => {}}
- />
+
+ {}}
+ onDeleteModalClose={() => {}}
+ />
+
);
});
diff --git a/ui/src/__mocks__/Stories.js b/ui/src/__mocks__/Stories.js
new file mode 100644
index 000000000..441c1c48e
--- /dev/null
+++ b/ui/src/__mocks__/Stories.js
@@ -0,0 +1,247 @@
+import moment from "moment";
+
+import { MockAlert, MockAlertGroup, MockSilence } from "./Alerts.js";
+
+const MockGroup = (groupName, alertCount, active, suppressed, unprocessed) => {
+ let alerts = [];
+ for (let i = 1; i <= alertCount; i++) {
+ let state;
+ switch (true) {
+ case i > active && i <= active + suppressed:
+ state = "suppressed";
+ break;
+ case i > active + suppressed:
+ state = "unprocessed";
+ break;
+ default:
+ state = "active";
+ }
+ const alert = MockAlert(
+ alertCount < 4
+ ? [
+ {
+ name: "dashboard",
+ value: "http://localhost",
+ visible: true,
+ isLink: true
+ },
+ {
+ name: "help",
+ value: "this is a summary text",
+ visible: true,
+ isLink: false
+ },
+ {
+ name: "hidden",
+ value: "this is hidden by default",
+ visible: false,
+ isLink: false
+ }
+ ]
+ : [],
+ { instance: `instance${i}` },
+ state
+ );
+ alert.startsAt = moment()
+ .subtract(alertCount, "minutes")
+ .toISOString();
+ alerts.push(alert);
+ }
+ const group = MockAlertGroup(
+ { alertname: "Fake Alert", group: groupName },
+ alerts,
+ [],
+ {},
+ {}
+ );
+ return group;
+};
+
+const MockGrid = alertStore => {
+ alertStore.settings.values.alertAcknowledgement.enabled = true;
+
+ const silence = MockSilence();
+ silence.startsAt = "2018-08-14T12:00:00Z";
+ silence.endsAt = "2018-08-14T18:00:00Z";
+ alertStore.data.silences = { am: {} };
+ alertStore.data.silences["am"][silence.id] = silence;
+
+ alertStore.data.colors = {
+ group: {
+ group1: {
+ brightness: 50,
+ background: { red: 178, green: 55, blue: 247, alpha: 255 }
+ },
+ group2: {
+ brightness: 50,
+ background: { red: 200, green: 100, blue: 66, alpha: 255 }
+ },
+ group3: {
+ brightness: 205,
+ background: { red: 246, green: 176, blue: 247, alpha: 255 }
+ },
+ group4: {
+ brightness: 111,
+ background: { red: 115, green: 101, blue: 152, alpha: 255 }
+ }
+ },
+ instance: {
+ instance1: {
+ brightness: 50,
+ background: { red: 111, green: 65, blue: 40, alpha: 255 }
+ },
+ instance2: {
+ brightness: 50,
+ background: { red: 66, green: 99, blue: 66, alpha: 255 }
+ },
+ instance3: {
+ brightness: 150,
+ background: { red: 66, green: 250, blue: 123, alpha: 255 }
+ }
+ }
+ };
+
+ alertStore.data.counters = [
+ {
+ name: "@receiver",
+ hits: 2,
+ values: [
+ {
+ value: "by-cluster-service",
+ raw: "@receiver=by-cluster-service",
+ hits: 2,
+ percent: 100,
+ offset: 0
+ }
+ ]
+ },
+ {
+ name: "alertname",
+ hits: 90,
+ values: [
+ {
+ value: "Fake Alert",
+ raw: "alertname=Fake Alert",
+ hits: 45,
+ percent: 50,
+ offset: 0
+ },
+ {
+ value: "Second Fake Alert",
+ raw: "alertname=Second Fake Alert",
+ hits: 45,
+ percent: 50,
+ offset: 50
+ }
+ ]
+ },
+ {
+ name: "group",
+ hits: 100,
+ values: [
+ {
+ value: "group1",
+ raw: "group=group1",
+ hits: 25,
+ percent: 25,
+ offset: 0
+ },
+ {
+ value: "group2",
+ raw: "group=group2",
+ hits: 70,
+ percent: 70,
+ offset: 25
+ },
+ {
+ value: "group3",
+ raw: "group=group3",
+ hits: 4,
+ percent: 4,
+ offset: 95
+ },
+ {
+ value: "group4",
+ raw: "group=group4",
+ hits: 1,
+ percent: 1,
+ offset: 99
+ }
+ ]
+ }
+ ];
+
+ let groups = [];
+ for (let i = 1; i <= 10; i++) {
+ const active = Math.max(1, Math.ceil(i / 3));
+ const suppressed = Math.max(0, i - 2 * Math.ceil(i / 3));
+ const unprocessed = Math.max(0, i - active - suppressed);
+
+ const id = `id${i}`;
+ const hash = `hash${i}`;
+ const group = MockGroup(`group${i}`, i, active, suppressed, unprocessed);
+
+ for (let j = 0; j < group.alerts.length; j++) {
+ if (group.alerts[j].state === "suppressed") {
+ group.alerts[j].silencedBy = [silence.id];
+ group.alerts[j].alertmanager = [
+ {
+ name: "am1",
+ cluster: "am",
+ state: "suppressed",
+ startsAt: "2018-08-14T17:36:40.017867056Z",
+ source: "localhost/prometheus",
+ silencedBy: [
+ j < 2 ? "'Fake Silence ID / Should be fallback'" : silence.id
+ ],
+ inhibitedBy: []
+ }
+ ];
+ }
+ }
+
+ group.id = id;
+ group.hash = hash;
+ group.stateCount.active = active;
+ group.stateCount.suppressed = suppressed;
+ group.stateCount.unprocessed = unprocessed;
+ if (i < 3) {
+ group.shared.labels = {
+ cluster: `prod${i}`,
+ job: "textfile_exporter"
+ };
+ }
+ if (i < 5) {
+ group.shared.annotations = [
+ {
+ name: "summary",
+ value: "Only 5% free space left on /disk",
+ visible: true,
+ isLink: false
+ }
+ ];
+ }
+ groups.push(group);
+ }
+ alertStore.data.upstreams = {
+ counters: { total: 3, healthy: 1, failed: 2 },
+ instances: [
+ { name: "am1", uri: "http://localhost:9093", error: "" },
+ {
+ name: "am2",
+ uri: "http://localhost:9094",
+ error: "Failed to connect to http://localhost:9094"
+ },
+ {
+ name: "failed",
+ uri: "https://am.example.com",
+ error:
+ "Failed to connect to https://am.example.com veryLongStringToTestTextWrappingveryLongStringToTestTextWrappingveryLongStringToTestTextWrappingveryLongStringToTestTextWrappingveryLongStringToTestTextWrappingveryLongStringToTestTextWrapping"
+ }
+ ],
+ clusters: { am: ["am1", "am2"], failed: ["failed"] }
+ };
+ alertStore.data.groups = groups;
+};
+
+export { MockGrid };