feat(tests): strict validation for props

This commit is contained in:
Łukasz Mierzwa
2018-09-24 13:47:47 +01:00
parent dd3ef03b37
commit 83aa8b9649
33 changed files with 237 additions and 71 deletions

View File

@@ -5,10 +5,12 @@ import { observer } from "mobx-react";
import Favico from "favico.js";
import { AlertStore } from "Stores/AlertStore";
const FaviconBadge = observer(
class FaviconBadge extends Component {
static propTypes = {
alertStore: PropTypes.object.isRequired
alertStore: PropTypes.instanceOf(AlertStore).isRequired
};
constructor(props) {

View File

@@ -3,11 +3,13 @@ import PropTypes from "prop-types";
import { inject } from "mobx-react";
import { AlertStore } from "Stores/AlertStore";
const FetchPauser = inject("alertStore")(
class FetchPauser extends Component {
static propTypes = {
children: PropTypes.any,
alertStore: PropTypes.object.isRequired
alertStore: PropTypes.instanceOf(AlertStore).isRequired
};
componentDidMount() {

View File

@@ -15,6 +15,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCaretDown } from "@fortawesome/free-solid-svg-icons/faCaretDown";
import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash";
import { APIAlert, APIGroup } from "Models/API";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { FetchPauser } from "Components/FetchPauser";
import { DropdownSlide } from "Components/Animations/DropdownSlide";
@@ -71,17 +73,17 @@ MenuContent.propTypes = {
popperPlacement: PropTypes.string,
popperRef: PropTypes.func,
popperStyle: PropTypes.object,
group: PropTypes.object.isRequired,
alert: PropTypes.object.isRequired,
group: APIGroup.isRequired,
alert: APIAlert.isRequired,
afterClick: PropTypes.func.isRequired
};
const AlertMenu = observer(
class AlertMenu extends Component {
static propTypes = {
group: PropTypes.object.isRequired,
alert: PropTypes.object.isRequired,
silenceFormStore: PropTypes.object.isRequired
group: APIGroup.isRequired,
alert: APIAlert.isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
};
collapse = observable(

View File

@@ -42,7 +42,7 @@ exports[`<Alert /> matches snapshot with showAlertmanagers=false showReceiver=fa
hidden
</div>
</div>
<span class=\\"components-label-with-hover text-nowrap text-truncate px-1 mr-1 badge badge-secondary cursor-pointer components-grid-alert-undefined-0988cb349635341c67d91bfe3454d2b3178c443c\\"
<span class=\\"components-label-with-hover text-nowrap text-truncate px-1 mr-1 badge badge-secondary cursor-pointer components-grid-alert-099c5ca6d1c92f615b13056b935d0c8dee70f18c-0988cb349635341c67d91bfe3454d2b3178c443c\\"
data-toggle=\\"dropdown\\"
>
<svg aria-hidden=\\"true\\"

View File

@@ -3,6 +3,8 @@ import PropTypes from "prop-types";
import { observer } from "mobx-react";
import { APIAlert, APIGroup } from "Models/API";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { GetLabelColorClass } from "Common/Colors";
import { StaticLabels } from "Common/Query";
import { FilteringLabel } from "Components/Labels/FilteringLabel";
@@ -15,12 +17,12 @@ import "./index.css";
const Alert = observer(
class Alert extends Component {
static propTypes = {
group: PropTypes.object.isRequired,
alert: PropTypes.object.isRequired,
group: APIGroup.isRequired,
alert: APIAlert.isRequired,
showAlertmanagers: PropTypes.bool.isRequired,
showReceiver: PropTypes.bool.isRequired,
afterUpdate: PropTypes.func.isRequired,
silenceFormStore: PropTypes.object.isRequired
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
};
render() {

View File

@@ -8,7 +8,7 @@ import { advanceTo, clear } from "jest-date-mock";
import toDiffableHtml from "diffable-html";
import { MockAlert, MockAnnotation } from "__mocks__/Alerts.js";
import { MockAlert, MockAnnotation, MockAlertGroup } from "__mocks__/Alerts.js";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { Alert } from ".";
@@ -41,12 +41,12 @@ const MockedAlert = () => {
);
};
const MountedAlert = (alert, showAlertmanagers, showReceiver) => {
const MountedAlert = (alert, group, showAlertmanagers, showReceiver) => {
return mount(
<Provider alertStore={alertStore}>
<Alert
alert={alert}
group={{}}
group={group}
showAlertmanagers={showAlertmanagers}
showReceiver={showReceiver}
afterUpdate={MockAfterUpdate}
@@ -59,13 +59,15 @@ const MountedAlert = (alert, showAlertmanagers, showReceiver) => {
describe("<Alert />", () => {
it("matches snapshot with showAlertmanagers=false showReceiver=false", () => {
const alert = MockedAlert();
const tree = MountedAlert(alert, false, false);
const group = MockAlertGroup({}, [alert], [], {});
const tree = MountedAlert(alert, group, false, false);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
it("renders @alertmanager label with showAlertmanagers=true", () => {
const alert = MockedAlert();
const tree = MountedAlert(alert, true, false);
const group = MockAlertGroup({}, [alert], [], {});
const tree = MountedAlert(alert, group, true, false);
const label = tree
.find("FilteringLabel")
.filterWhere(elem => elem.props().name === "@alertmanager");
@@ -74,7 +76,8 @@ describe("<Alert />", () => {
it("renders @receiver label with showReceiver=true", () => {
const alert = MockedAlert();
const tree = MountedAlert(alert, false, true);
const group = MockAlertGroup({}, [alert], [], {});
const tree = MountedAlert(alert, group, false, true);
const label = tree
.find("FilteringLabel")
.filterWhere(elem => elem.props().name === "@receiver");
@@ -84,7 +87,8 @@ describe("<Alert />", () => {
it("renders a silence if alert is silenced", () => {
const alert = MockedAlert();
alert.alertmanager[0].silencedBy = ["silence123456789"];
const tree = MountedAlert(alert, false, false);
const group = MockAlertGroup({}, [alert], [], {});
const tree = MountedAlert(alert, group, false, false);
const silence = tree.find("Silence");
expect(silence).toHaveLength(1);
expect(silence.html()).toMatch(/silence123456789/);

View File

@@ -11,13 +11,15 @@ import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons/faExternalL
import { faSearchPlus } from "@fortawesome/free-solid-svg-icons/faSearchPlus";
import { faSearchMinus } from "@fortawesome/free-solid-svg-icons/faSearchMinus";
import { AlertStore } from "Stores/AlertStore";
import "./index.css";
const RenderNonLinkAnnotation = inject("alertStore")(
observer(
class RenderNonLinkAnnotation extends Component {
static propTypes = {
alertStore: PropTypes.object.isRequired,
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
visible: PropTypes.bool.isRequired,

View File

@@ -3,6 +3,7 @@ import PropTypes from "prop-types";
import { observer } from "mobx-react";
import { APIGroup } from "Models/API";
import { StaticLabels } from "Common/Query";
import { FilteringLabel } from "Components/Labels/FilteringLabel";
import { RenderNonLinkAnnotation, RenderLinkAnnotation } from "../Annotation";
@@ -10,7 +11,7 @@ import { RenderNonLinkAnnotation, RenderLinkAnnotation } from "../Annotation";
const GroupFooter = observer(
class GroupFooter extends Component {
static propTypes = {
group: PropTypes.object.isRequired,
group: APIGroup.isRequired,
alertmanagers: PropTypes.arrayOf(PropTypes.string).isRequired,
afterUpdate: PropTypes.func.isRequired
};

View File

@@ -14,7 +14,9 @@ import { faEllipsisV } from "@fortawesome/free-solid-svg-icons/faEllipsisV";
import { faShareSquare } from "@fortawesome/free-solid-svg-icons/faShareSquare";
import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash";
import { APIGroup } from "Models/API";
import { FormatAPIFilterQuery } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { QueryOperators, StaticLabels, FormatQuery } from "Common/Query";
import { DropdownSlide } from "Components/Animations/DropdownSlide";
import { FetchPauser } from "Components/FetchPauser";
@@ -76,15 +78,15 @@ MenuContent.propTypes = {
popperPlacement: PropTypes.string,
popperRef: PropTypes.func,
popperStyle: PropTypes.object,
group: PropTypes.object.isRequired,
group: APIGroup.isRequired,
afterClick: PropTypes.func.isRequired
};
const GroupMenu = observer(
class GroupMenu extends Component {
static propTypes = {
group: PropTypes.object.isRequired,
silenceFormStore: PropTypes.object.isRequired
group: APIGroup.isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
};
collapse = observable(

View File

@@ -7,6 +7,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp";
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
import { APIGroup } from "Models/API";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { FilteringLabel } from "Components/Labels/FilteringLabel";
import { FilteringCounterBadge } from "Components/Labels/FilteringCounterBadge";
import { GroupMenu } from "./GroupMenu";
@@ -14,9 +16,12 @@ import { GroupMenu } from "./GroupMenu";
const GroupHeader = observer(
class GroupHeader extends Component {
static propTypes = {
collapseStore: PropTypes.object.isRequired,
group: PropTypes.object.isRequired,
silenceFormStore: PropTypes.object.isRequired
collapseStore: PropTypes.shape({
value: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired
}).isRequired,
group: APIGroup.isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
};
render() {

View File

@@ -14,6 +14,12 @@ import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons/faExternalL
import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp";
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
import {
APIAlertAlertmanagerState,
APIAlertmanagerUpstream,
APISilence
} from "Models/API";
import { AlertStore } from "Stores/AlertStore";
import { StaticLabels, QueryOperators } from "Common/Query";
import { FilteringLabel } from "Components/Labels/FilteringLabel";
@@ -31,7 +37,7 @@ const SilenceComment = ({ silence }) => {
return silence.comment;
};
SilenceComment.propTypes = {
silence: PropTypes.object.isRequired
silence: APISilence.isRequired
};
const SilenceExpiryBadgeWithProgress = ({ silence, progress }) => {
@@ -70,7 +76,7 @@ const SilenceExpiryBadgeWithProgress = ({ silence, progress }) => {
);
};
SilenceExpiryBadgeWithProgress.propTypes = {
silence: PropTypes.object.isRequired,
silence: APISilence.isRequired,
progress: PropTypes.number.isRequired
};
@@ -118,8 +124,8 @@ const SilenceDetails = ({ alertmanager, silence }) => {
);
};
SilenceDetails.propTypes = {
alertmanager: PropTypes.object.isRequired,
silence: PropTypes.object.isRequired
alertmanager: APIAlertmanagerUpstream.isRequired,
silence: APISilence.isRequired
};
//
@@ -141,8 +147,8 @@ const Silence = inject("alertStore")(
observer(
class Silence extends Component {
static propTypes = {
alertStore: PropTypes.object.isRequired,
alertmanagerState: PropTypes.object.isRequired,
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
alertmanagerState: APIAlertAlertmanagerState.isRequired,
silenceID: PropTypes.string.isRequired,
afterUpdate: PropTypes.func.isRequired
};

View File

@@ -16,6 +16,7 @@ const mockAfterUpdate = jest.fn();
const alertmanager = {
name: "default",
uri: "http://localhost",
state: "suppressed",
startsAt: "2000-01-01T10:00:00Z",
endsAt: "0001-01-01T00:00:00Z",

View File

@@ -10,6 +10,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlus } from "@fortawesome/free-solid-svg-icons/faPlus";
import { faMinus } from "@fortawesome/free-solid-svg-icons/faMinus";
import { APIGroup } from "Models/API";
import { MountFade } from "Components/Animations/MountFade";
import { Settings } from "Stores/Settings";
import { SilenceFormStore } from "Stores/SilenceFormStore";
@@ -25,7 +26,7 @@ const LoadButton = ({ icon, action }) => {
);
};
LoadButton.propTypes = {
icon: PropTypes.object.isRequired,
icon: FontAwesomeIcon.propTypes.icon.isRequired,
action: PropTypes.func.isRequired
};
@@ -42,7 +43,7 @@ const AlertGroup = observer(
class AlertGroup extends Component {
static propTypes = {
afterUpdate: PropTypes.func.isRequired,
group: PropTypes.object.isRequired,
group: APIGroup.isRequired,
showAlertmanagers: PropTypes.bool.isRequired,
settingsStore: PropTypes.instanceOf(Settings).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired

View File

@@ -36,7 +36,7 @@ beforeEach(() => {
const MockAlerts = alertCount => {
for (let i = 1; i <= alertCount; i++) {
let alert = MockAlert([], { instance: `instance${i}` });
let alert = MockAlert([], { instance: `instance${i}` }, "active");
const startsAt = moment().toISOString();
alert.startsAt = startsAt;
alert.alertmanager[0].startsAt = startsAt;

View File

@@ -31,7 +31,7 @@ const ShallowAlertGrid = () => {
const MockGroup = (groupName, alertCount) => {
let alerts = [];
for (let i = 1; i <= alertCount; i++) {
alerts.push(MockAlert([], { instance: `instance${i}` }));
alerts.push(MockAlert([], { instance: `instance${i}` }, "active"));
}
const group = MockAlertGroup(
{ alertname: "Fake Alert", group: groupName },

View File

@@ -7,7 +7,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch";
import { faPauseCircle } from "@fortawesome/free-regular-svg-icons/faPauseCircle";
import { AlertStoreStatuses } from "Stores/AlertStore";
import { AlertStore, AlertStoreStatuses } from "Stores/AlertStore";
const FetchIcon = ({ icon, color, visible, spin }) => (
<FontAwesomeIcon
@@ -19,7 +19,7 @@ const FetchIcon = ({ icon, color, visible, spin }) => (
/>
);
FetchIcon.propTypes = {
icon: PropTypes.object.isRequired,
icon: FontAwesomeIcon.propTypes.icon.isRequired,
color: PropTypes.string,
visible: PropTypes.bool,
spin: PropTypes.bool
@@ -33,7 +33,7 @@ FetchIcon.defaultProps = {
const FetchIndicator = observer(
class FetchIndicator extends Component {
static propTypes = {
alertStore: PropTypes.object.isRequired
alertStore: PropTypes.instanceOf(AlertStore).isRequired
};
render() {

View File

@@ -54,7 +54,7 @@ const ActionButton = ({ color, icon, title, action, afterClick }) => (
ActionButton.propTypes = {
color: PropTypes.string.isRequired,
title: PropTypes.node.isRequired,
icon: PropTypes.object.isRequired,
icon: FontAwesomeIcon.propTypes.icon.isRequired,
action: PropTypes.func.isRequired,
afterClick: PropTypes.func.isRequired
};

View File

@@ -6,6 +6,8 @@ import { observer } from "mobx-react";
import ReactSelect from "react-select";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { MultiSelect, ReactSelectStyles } from "Components/MultiSelect";
import { ValidationError } from "Components/MultiSelect/ValidationError";
@@ -18,8 +20,8 @@ const AlertmanagerInstancesToOptions = instances =>
const AlertManagerInput = observer(
class AlertManagerInput extends MultiSelect {
static propTypes = {
alertStore: PropTypes.object.isRequired,
silenceFormStore: PropTypes.object.isRequired
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
};
constructor(props) {

View File

@@ -21,7 +21,7 @@ const IconTd = ({ icon, onClick }) => (
</td>
);
IconTd.propTypes = {
icon: PropTypes.object.isRequired,
icon: FontAwesomeIcon.propTypes.icon.isRequired,
onClick: PropTypes.func.isRequired
};

View File

@@ -8,6 +8,7 @@ import moment from "moment";
import DatePicker from "react-datepicker";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { Duration } from "./Duration";
import { HourMinute } from "./HourMinute";
@@ -161,13 +162,13 @@ const TabContentDuration = observer(({ silenceFormStore }) => {
);
});
TabContentDuration.propTypes = {
silenceFormStore: PropTypes.object.isRequired
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
};
const DateTimeSelect = observer(
class DateTimeSelect extends Component {
static propTypes = {
silenceFormStore: PropTypes.object.isRequired
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
};
tab = observable(

View File

@@ -4,6 +4,7 @@ import PropTypes from "prop-types";
import { action } from "mobx";
import { observer } from "mobx-react";
import { SilenceFormMatcher } from "Models/SilenceForm";
import { MultiSelect } from "Components/MultiSelect";
import { ValidationError } from "Components/MultiSelect/ValidationError";
import { FormatBackendURI } from "Stores/AlertStore";
@@ -11,7 +12,7 @@ import { FormatBackendURI } from "Stores/AlertStore";
const LabelNameInput = observer(
class LabelNameInput extends MultiSelect {
static propTypes = {
matcher: PropTypes.object.isRequired,
matcher: SilenceFormMatcher.isRequired,
isValid: PropTypes.bool.isRequired
};

View File

@@ -4,13 +4,14 @@ import PropTypes from "prop-types";
import { action } from "mobx";
import { observer } from "mobx-react";
import { SilenceFormMatcher } from "Models/SilenceForm";
import { MultiSelect } from "Components/MultiSelect";
import { ValidationError } from "Components/MultiSelect/ValidationError";
const LabelValueInput = observer(
class LabelValueInput extends MultiSelect {
static propTypes = {
matcher: PropTypes.object.isRequired,
matcher: SilenceFormMatcher.isRequired,
isValid: PropTypes.bool.isRequired
};

View File

@@ -12,6 +12,9 @@ import { faSave } from "@fortawesome/free-regular-svg-icons/faSave";
import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp";
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { Settings } from "Stores/Settings";
import { AlertManagerInput } from "./AlertManagerInput";
import { SilenceMatch } from "./SilenceMatch";
import { DateTimeSelect } from "./DateTimeSelect";
@@ -45,7 +48,7 @@ const IconInput = ({
IconInput.propTypes = {
type: PropTypes.string.isRequired,
autoComplete: PropTypes.string.isRequired,
icon: PropTypes.object.isRequired,
icon: FontAwesomeIcon.propTypes.icon.isRequired,
placeholder: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
@@ -54,9 +57,9 @@ IconInput.propTypes = {
const SilenceForm = observer(
class SilenceForm extends Component {
static propTypes = {
alertStore: PropTypes.object.isRequired,
silenceFormStore: PropTypes.object.isRequired,
settingsStore: PropTypes.object.isRequired
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
settingsStore: PropTypes.instanceOf(Settings).isRequired
};
// store preview visibility state here, by default preview is collapsed

View File

@@ -161,7 +161,7 @@ describe("<SilenceForm />", () => {
it("calling submit marks form as in progress when form is valid", () => {
const matcher = NewEmptyMatcher();
matcher.name = "job";
matcher.values = ["node_exporter"];
matcher.values = [{ label: "node_exporter", value: "node_exporter" }];
silenceFormStore.data.matchers = [matcher];
silenceFormStore.data.alertmanagers = [
{ label: "am1", value: "http://example.com" }

View File

@@ -7,18 +7,14 @@ import { observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faTrash } from "@fortawesome/free-solid-svg-icons/faTrash";
import { SilenceFormMatcher } from "Models/SilenceForm";
import { LabelNameInput } from "./LabelNameInput";
import { LabelValueInput } from "./LabelValueInput";
const SilenceMatch = observer(
class SilenceMatch extends Component {
static propTypes = {
matcher: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
values: PropTypes.array.isRequired,
isRegex: PropTypes.bool.isRequired
}),
matcher: SilenceFormMatcher.isRequired,
showDelete: PropTypes.bool.isRequired,
onDelete: PropTypes.func.isRequired,
isValid: PropTypes.bool.isRequired

View File

@@ -6,15 +6,18 @@ import { observer } from "mobx-react";
import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { Settings } from "Stores/Settings";
import { SilenceForm } from "./SilenceForm";
import { SilenceSubmitController } from "./SilenceSubmitController";
const SilenceModalContent = observer(
class SilenceModalContent extends Component {
static propTypes = {
alertStore: PropTypes.object.isRequired,
silenceFormStore: PropTypes.object.isRequired,
settingsStore: PropTypes.object.isRequired,
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
settingsStore: PropTypes.instanceOf(Settings).isRequired,
onHide: PropTypes.func.isRequired
};

View File

@@ -6,10 +6,12 @@ import { observer } from "mobx-react";
import JSONPretty from "react-json-pretty";
import "react-json-pretty/src/JSONPretty.monikai.css";
import { SilenceFormStore } from "Stores/SilenceFormStore";
const SilencePreview = observer(
class SilencePreview extends Component {
static propTypes = {
silenceFormStore: PropTypes.object.isRequired
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
};
render() {

View File

@@ -4,12 +4,14 @@ import PropTypes from "prop-types";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowLeft } from "@fortawesome/free-solid-svg-icons/faArrowLeft";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { SilenceSubmitProgress } from "./SilenceSubmitProgress";
class SilenceSubmitController extends Component {
static propTypes = {
alertStore: PropTypes.object.isRequired,
silenceFormStore: PropTypes.object.isRequired
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
};
render() {

View File

@@ -9,6 +9,9 @@ import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch";
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons/faCheckCircle";
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
import { APISilenceMatcher } from "Models/API";
import { AlertStore } from "Stores/AlertStore";
const SubmitState = Object.freeze({
InProgress: "InProgress",
Done: "Done",
@@ -46,8 +49,14 @@ const SilenceSubmitProgress = observer(
static propTypes = {
name: PropTypes.string.isRequired,
uri: PropTypes.string.isRequired,
payload: PropTypes.object.isRequired,
alertStore: PropTypes.object.isRequired
payload: PropTypes.exact({
matchers: PropTypes.arrayOf(APISilenceMatcher).isRequired,
startsAt: PropTypes.string.isRequired,
endsAt: PropTypes.string.isRequired,
createdBy: PropTypes.string.isRequired,
comment: PropTypes.string.isRequired
}).isRequired,
alertStore: PropTypes.instanceOf(AlertStore).isRequired
};
submitState = observable(

View File

@@ -28,7 +28,13 @@ const MountedSilenceSubmitProgress = () => {
<SilenceSubmitProgress
name="mockAlertmanager"
uri="http://localhost/mock"
payload={{ foo: "bar" }}
payload={{
matchers: [],
startsAt: "now",
endsAt: "later",
createdBy: "me@example.com",
comment: "fake payload"
}}
alertStore={alertStore}
/>
);
@@ -52,7 +58,13 @@ describe("<SilenceSubmitProgress />", () => {
expect(payload).toMatchObject({
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ foo: "bar" })
body: JSON.stringify({
matchers: [],
startsAt: "now",
endsAt: "later",
createdBy: "me@example.com",
comment: "fake payload"
})
});
});

View File

@@ -6,6 +6,9 @@ import { observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { Settings } from "Stores/Settings";
import { MountFade } from "Components/Animations/MountFade";
import { SilenceModalContent } from "./SilenceModalContent";
@@ -14,9 +17,9 @@ import "./index.css";
const SilenceModal = observer(
class SilenceModal extends Component {
static propTypes = {
alertStore: PropTypes.object.isRequired,
silenceFormStore: PropTypes.object.isRequired,
settingsStore: PropTypes.object.isRequired
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
settingsStore: PropTypes.instanceOf(Settings).isRequired
};
toggleModal = () => {

82
ui/src/Models/API.js Normal file
View File

@@ -0,0 +1,82 @@
import PropTypes from "prop-types";
const AlertState = PropTypes.oneOf(["unprocessed", "active", "suppressed"]);
const Annotation = PropTypes.exact({
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
visible: PropTypes.bool.isRequired,
isLink: PropTypes.bool.isRequired
});
const APIAlertAlertmanagerState = PropTypes.exact({
name: PropTypes.string.isRequired,
uri: PropTypes.string.isRequired,
state: AlertState.isRequired,
startsAt: PropTypes.string.isRequired,
endsAt: PropTypes.string.isRequired,
source: PropTypes.string.isRequired,
silencedBy: PropTypes.arrayOf(PropTypes.string).isRequired
});
const APIAlert = PropTypes.exact({
annotations: PropTypes.arrayOf(Annotation).isRequired,
labels: PropTypes.object.isRequired,
startsAt: PropTypes.string.isRequired,
endsAt: PropTypes.string.isRequired,
state: AlertState.isRequired,
alertmanager: PropTypes.arrayOf(APIAlertAlertmanagerState).isRequired,
receiver: PropTypes.string.isRequired
});
const APIGroup = PropTypes.exact({
receiver: PropTypes.string.isRequired,
labels: PropTypes.object.isRequired,
alerts: PropTypes.arrayOf(APIAlert),
id: PropTypes.string.isRequired,
hash: PropTypes.string.isRequired,
alertmanagerCount: PropTypes.objectOf(PropTypes.number).isRequired,
stateCount: PropTypes.exact({
active: PropTypes.number.isRequired,
suppressed: PropTypes.number.isRequired,
unprocessed: PropTypes.number.isRequired
}),
shared: PropTypes.exact({
annotations: PropTypes.arrayOf(Annotation).isRequired,
labels: PropTypes.object.isRequired
}).isRequired
});
const APISilenceMatcher = PropTypes.exact({
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
isRegex: PropTypes.bool.isRequired
});
const APISilence = PropTypes.exact({
id: PropTypes.string.isRequired,
matchers: PropTypes.arrayOf(APISilenceMatcher).isRequired,
startsAt: PropTypes.string.isRequired,
endsAt: PropTypes.string.isRequired,
createdAt: PropTypes.string.isRequired,
createdBy: PropTypes.string.isRequired,
comment: PropTypes.string.isRequired,
jiraID: PropTypes.string.isRequired,
jiraURL: PropTypes.string.isRequired
});
const APIAlertmanagerUpstream = PropTypes.exact({
name: PropTypes.string.isRequired,
uri: PropTypes.string.isRequired,
publicURI: PropTypes.string.isRequired,
error: PropTypes.string.isRequired
});
export {
APIAlert,
APIGroup,
APISilence,
APISilenceMatcher,
APIAlertAlertmanagerState,
APIAlertmanagerUpstream
};

View File

@@ -0,0 +1,19 @@
import PropTypes from "prop-types";
const SilenceFormSuggestion = PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired
});
const SilenceFormMatcher = PropTypes.exact({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
values: PropTypes.arrayOf(SilenceFormSuggestion).isRequired,
suggestions: PropTypes.exact({
names: PropTypes.arrayOf(SilenceFormSuggestion).isRequired,
values: PropTypes.arrayOf(SilenceFormSuggestion).isRequired
}).isRequired,
isRegex: PropTypes.bool.isRequired
});
export { SilenceFormMatcher, SilenceFormSuggestion };