diff --git a/ui/src/Components/Grid/index.stories.js b/ui/src/Components/Grid/index.stories.js
new file mode 100644
index 000000000..ab3fc7ca6
--- /dev/null
+++ b/ui/src/Components/Grid/index.stories.js
@@ -0,0 +1,170 @@
+import React from "react";
+
+import { storiesOf } from "@storybook/react";
+
+import { Provider } from "mobx-react";
+
+import { MockAlert, MockAlertGroup } from "__mocks__/Alerts.js";
+import { AlertStore } from "Stores/AlertStore";
+import { Settings } from "Stores/Settings";
+import { SilenceFormStore } from "Stores/SilenceFormStore";
+import { UpstreamError } from "./UpstreamError";
+import { FatalError } from "./FatalError";
+import { UpgradeNeeded } from "./UpgradeNeeded";
+import { AlertGrid } from "./AlertGrid";
+
+import "App.scss";
+
+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";
+ }
+ alerts.push(
+ 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
+ )
+ );
+ }
+ const group = MockAlertGroup(
+ { alertname: "Fake Alert", group: groupName },
+ alerts,
+ [],
+ {},
+ {}
+ );
+ return group;
+};
+
+storiesOf("Grid", module)
+ .addDecorator(storyFn =>
{storyFn()}
)
+ .add("UpstreamError", () => {
+ return ;
+ })
+ .add("FatalError", () => {
+ return ;
+ })
+ .add("UpgradeNeeded", () => {
+ return ;
+ })
+ .add("AlertGrid", () => {
+ const alertStore = new AlertStore([]);
+ const settingsStore = new Settings();
+ const silenceFormStore = new SilenceFormStore();
+
+ 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 }
+ }
+ }
+ };
+
+ 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);
+ 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: 0, healthy: 1, failed: 0 },
+ instances: [{ name: "am", uri: "http://am", error: "" }],
+ clusters: { am: ["am"] }
+ };
+ alertStore.data.groups = groups;
+
+ return (
+
+
+
+ );
+ });
diff --git a/ui/src/Components/Labels/FilterInputLabel/index.stories.js b/ui/src/Components/Labels/FilterInputLabel/index.stories.js
deleted file mode 100644
index 22fc072a8..000000000
--- a/ui/src/Components/Labels/FilterInputLabel/index.stories.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import React from "react";
-
-import { storiesOf } from "@storybook/react";
-
-import { AlertStore, NewUnappliedFilter } from "Stores/AlertStore";
-import { FilterInputLabel } from ".";
-
-import "App.scss";
-
-storiesOf("FilterInputLabel", module)
- .addDecorator(storyFn => {storyFn()}
)
- .add("applied => false", () => {
- const alertStore = new AlertStore([]);
- const filter = NewUnappliedFilter(`@state=active`);
- alertStore.filters.values = [filter];
- return ;
- })
- .add("isValid => false", () => {
- const alertStore = new AlertStore([]);
- const filter = NewUnappliedFilter(`@state=active`);
- filter.isValid = false;
- alertStore.filters.values = [filter];
- return ;
- })
- .add("applied => true without counter badge", () => {
- const alertStore = new AlertStore([]);
- alertStore.info.totalAlerts = 99;
- const filter = NewUnappliedFilter(`@state=active`);
- filter.applied = true;
- filter.hits = 99;
- alertStore.filters.values = [filter];
- return ;
- })
- .add("applied => true with counter badge", () => {
- const alertStore = new AlertStore([]);
- const filter = NewUnappliedFilter(`@state=active`);
- filter.applied = true;
- filter.hits = 99;
- alertStore.filters.values = [filter];
- return ;
- });
diff --git a/ui/src/Components/MainModal/MainModalContent.js b/ui/src/Components/MainModal/MainModalContent.js
index ee10a468d..55c27c08e 100644
--- a/ui/src/Components/MainModal/MainModalContent.js
+++ b/ui/src/Components/MainModal/MainModalContent.js
@@ -35,18 +35,26 @@ const MainModalContent = observer(
static propTypes = {
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
settingsStore: PropTypes.instanceOf(Settings).isRequired,
- onHide: PropTypes.func.isRequired
+ onHide: PropTypes.func.isRequired,
+ openTab: PropTypes.oneOf(Object.values(TabNames))
+ };
+ static defaultProps = {
+ openTab: TabNames.Configuration
};
- tab = observable(
- {
- current: TabNames.Configuration,
- setTab(newTab) {
- this.current = newTab;
- }
- },
- { setTab: action.bound }
- );
+ constructor(props) {
+ super(props);
+
+ this.tab = observable(
+ {
+ current: props.openTab,
+ setTab(newTab) {
+ this.current = newTab;
+ }
+ },
+ { setTab: action.bound }
+ );
+ }
render() {
const { alertStore, settingsStore, onHide } = this.props;
@@ -87,4 +95,4 @@ const MainModalContent = observer(
}
);
-export { MainModalContent };
+export { MainModalContent, TabNames };
diff --git a/ui/src/Components/MainModal/index.stories.js b/ui/src/Components/MainModal/index.stories.js
index fb2f70b25..572274930 100644
--- a/ui/src/Components/MainModal/index.stories.js
+++ b/ui/src/Components/MainModal/index.stories.js
@@ -4,19 +4,19 @@ import { storiesOf } from "@storybook/react";
import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
-import { MainModalContent } from "./MainModalContent";
+import { MainModalContent, TabNames } from "./MainModalContent";
import "App.scss";
storiesOf("MainModal", module)
.addDecorator(storyFn => (
-
+
))
- .add("content", () => {
+ .add("Configuration", () => {
const alertStore = new AlertStore([]);
const settingsStore = new Settings();
return (
@@ -27,4 +27,17 @@ storiesOf("MainModal", module)
isVisible={true}
/>
);
+ })
+ .add("Help", () => {
+ const alertStore = new AlertStore([]);
+ const settingsStore = new Settings();
+ return (
+
{}}
+ isVisible={true}
+ openTab={TabNames.Help}
+ />
+ );
});
diff --git a/ui/src/Components/NavBar/index.stories.js b/ui/src/Components/NavBar/index.stories.js
new file mode 100644
index 000000000..ebf5745ff
--- /dev/null
+++ b/ui/src/Components/NavBar/index.stories.js
@@ -0,0 +1,75 @@
+import React from "react";
+
+import { storiesOf } from "@storybook/react";
+
+import { AlertStore, NewUnappliedFilter } from "Stores/AlertStore";
+import { Settings } from "Stores/Settings";
+import { SilenceFormStore } from "Stores/SilenceFormStore";
+import { NavBar } from ".";
+
+import "App.scss";
+
+const NewFilter = (raw, name, matcher, value, applied, isValid, hits) => {
+ const filter = NewUnappliedFilter(raw);
+ filter.name = name;
+ filter.matcher = matcher;
+ filter.value = value;
+ filter.applied = applied;
+ filter.isValid = isValid;
+ filter.hits = hits;
+ return filter;
+};
+
+storiesOf("NavBar", module)
+ .addDecorator(storyFn => (
+
+ ))
+ .add("NavBar", () => {
+ const alertStore = new AlertStore([]);
+ const settingsStore = new Settings();
+ const silenceFormStore = new SilenceFormStore();
+
+ alertStore.info.totalAlerts = 197;
+ alertStore.data.colors = {
+ cluster: {
+ staging: {
+ brightness: 205,
+ background: { red: 246, green: 176, blue: 247, alpha: 255 }
+ }
+ },
+ region: {
+ AF: {
+ brightness: 111,
+ background: { red: 115, green: 101, blue: 152, alpha: 255 }
+ }
+ }
+ };
+
+ alertStore.filters.values = [
+ 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("server!!!=", "", "", "", true, false, 0),
+ NewFilter("foo", "", "", "", true, true, 2)
+ ];
+
+ return (
+
+ );
+ });
diff --git a/ui/src/Components/SilenceModal/SilenceForm.js b/ui/src/Components/SilenceModal/SilenceForm.js
index 3987aade8..bf614a56d 100644
--- a/ui/src/Components/SilenceModal/SilenceForm.js
+++ b/ui/src/Components/SilenceModal/SilenceForm.js
@@ -61,21 +61,29 @@ const SilenceForm = observer(
static propTypes = {
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
- settingsStore: PropTypes.instanceOf(Settings).isRequired
+ settingsStore: PropTypes.instanceOf(Settings).isRequired,
+ previewOpen: PropTypes.bool
+ };
+ static defaultProps = {
+ previewOpen: false
};
- // store preview visibility state here, by default preview is collapsed
- // and user needs to expand it
- previewCollapse = observable(
- {
- hidden: true,
- toggle() {
- this.hidden = !this.hidden;
- }
- },
- { toggle: action.bound },
- { name: "Silence preview collpase toggle" }
- );
+ constructor(props) {
+ super(props);
+
+ // store preview visibility state here, by default preview is collapsed
+ // and user needs to expand it
+ this.previewCollapse = observable(
+ {
+ hidden: !props.previewOpen,
+ toggle() {
+ this.hidden = !this.hidden;
+ }
+ },
+ { toggle: action.bound },
+ { name: "Silence preview collpase toggle" }
+ );
+ }
componentDidMount() {
const { silenceFormStore } = this.props;
diff --git a/ui/src/Components/SilenceModal/SilenceModalContent.js b/ui/src/Components/SilenceModal/SilenceModalContent.js
index 5b183ec1a..24e5a081b 100644
--- a/ui/src/Components/SilenceModal/SilenceModalContent.js
+++ b/ui/src/Components/SilenceModal/SilenceModalContent.js
@@ -10,6 +10,8 @@ import { SilenceForm } from "./SilenceForm";
import { SilencePreview } from "./SilencePreview";
import { SilenceSubmitController } from "./SilenceSubmit/SilenceSubmitController";
+import "./index.css";
+
const SilenceModalContent = observer(
class SilenceModalContent extends Component {
static propTypes = {
diff --git a/ui/src/Components/SilenceModal/index.js b/ui/src/Components/SilenceModal/index.js
index 5b81f1cfb..4397a729a 100644
--- a/ui/src/Components/SilenceModal/index.js
+++ b/ui/src/Components/SilenceModal/index.js
@@ -13,8 +13,6 @@ import { Settings } from "Stores/Settings";
import { Modal } from "Components/Modal";
import { TooltipWrapper } from "Components/TooltipWrapper";
-import "./index.css";
-
// https://github.com/facebook/react/issues/14603
const SilenceModalContent = React.lazy(() =>
import("./SilenceModalContent").then(module => ({
diff --git a/ui/src/Components/SilenceModal/index.stories.js b/ui/src/Components/SilenceModal/index.stories.js
index 3474dbb47..a4dda4e78 100644
--- a/ui/src/Components/SilenceModal/index.stories.js
+++ b/ui/src/Components/SilenceModal/index.stories.js
@@ -4,30 +4,61 @@ import { storiesOf } from "@storybook/react";
import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
-import { SilenceFormStore } from "Stores/SilenceFormStore";
+import {
+ SilenceFormStore,
+ NewEmptyMatcher,
+ MatcherValueToObject
+} from "Stores/SilenceFormStore";
import { SilenceModalContent } from "./SilenceModalContent";
import "App.scss";
+const MockMatcher = (name, values, isRegex) => {
+ const matcher = NewEmptyMatcher();
+ matcher.name = name;
+ matcher.values = values.map(v => MatcherValueToObject(v));
+ matcher.isRegex = isRegex;
+ return matcher;
+};
+
storiesOf("SilenceModalContent", module)
.addDecorator(storyFn => (
-
+
))
- .add("content", () => {
+ .add("SilenceModalContent", () => {
const alertStore = new AlertStore([]);
const settingsStore = new Settings();
const silenceFormStore = new SilenceFormStore();
+
silenceFormStore.toggle.visible = true;
+ silenceFormStore.data.matchers = [
+ MockMatcher("cluster", ["prod"], false),
+ MockMatcher("instance", ["server1", "server3"], true),
+ MockMatcher(
+ "tooLong",
+ [
+ "12345",
+ "Some Alerts With A Ridiculously Long Name To Test Label Truncation In All The Places We Render Those Alerts"
+ ],
+ true
+ )
+ ];
+ silenceFormStore.data.addEmptyMatcher();
+ silenceFormStore.data.author = "me@example.com";
+ silenceFormStore.data.comment = "fake silence";
+ silenceFormStore.data.resetStartEnd();
+
return (
{}}
+ previewOpen={true}
/>
);
});