feat(ui): more storybooks

This commit is contained in:
Łukasz Mierzwa
2019-09-09 23:43:38 +01:00
parent e9058ca8bf
commit f8b233ae33
9 changed files with 337 additions and 73 deletions

View File

@@ -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 => <div className="p-2">{storyFn()}</div>)
.add("UpstreamError", () => {
return <UpstreamError name="am1" message="Something failed" />;
})
.add("FatalError", () => {
return <FatalError message="Something failed" />;
})
.add("UpgradeNeeded", () => {
return <UpgradeNeeded newVersion="1.2.3" />;
})
.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 (
<Provider alertStore={alertStore}>
<AlertGrid
alertStore={alertStore}
settingsStore={settingsStore}
silenceFormStore={silenceFormStore}
/>
</Provider>
);
});

View File

@@ -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 => <div className="p-2">{storyFn()}</div>)
.add("applied => false", () => {
const alertStore = new AlertStore([]);
const filter = NewUnappliedFilter(`@state=active`);
alertStore.filters.values = [filter];
return <FilterInputLabel alertStore={alertStore} filter={filter} />;
})
.add("isValid => false", () => {
const alertStore = new AlertStore([]);
const filter = NewUnappliedFilter(`@state=active`);
filter.isValid = false;
alertStore.filters.values = [filter];
return <FilterInputLabel alertStore={alertStore} filter={filter} />;
})
.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 <FilterInputLabel alertStore={alertStore} filter={filter} />;
})
.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 <FilterInputLabel alertStore={alertStore} filter={filter} />;
});

View File

@@ -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 };

View File

@@ -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 => (
<div className="modal d-block" role="dialog">
<div className="overflow-auto modal d-block" role="dialog">
<div className="modal-dialog modal-lg" role="document">
<div className="modal-content">{storyFn()}</div>
</div>
</div>
))
.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 (
<MainModalContent
alertStore={alertStore}
settingsStore={settingsStore}
onHide={() => {}}
isVisible={true}
openTab={TabNames.Help}
/>
);
});

View File

@@ -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 => (
<div className="overflow-auto modal d-block" role="dialog">
<div className="modal-dialog modal-lg" role="document">
<div className="modal-content">{storyFn()}</div>
</div>
</div>
))
.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 (
<NavBar
alertStore={alertStore}
settingsStore={settingsStore}
silenceFormStore={silenceFormStore}
/>
);
});

View File

@@ -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;

View File

@@ -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 = {

View File

@@ -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 => ({

View File

@@ -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 => (
<div className="modal d-block" role="dialog">
<div className="modal d-block overflow-auto" role="dialog">
<div className="modal-dialog modal-lg" role="document">
<div className="modal-content">{storyFn()}</div>
</div>
</div>
))
.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 (
<SilenceModalContent
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
onHide={() => {}}
previewOpen={true}
/>
);
});