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 => ( -
+
{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 => ( +
+
+
{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 => ( -
+
{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} /> ); });