diff --git a/ui/src/Components/FaviconBadge/index.js b/ui/src/Components/FaviconBadge/index.js index f827520a9..40fdecd81 100644 --- a/ui/src/Components/FaviconBadge/index.js +++ b/ui/src/Components/FaviconBadge/index.js @@ -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) { diff --git a/ui/src/Components/FetchPauser/index.js b/ui/src/Components/FetchPauser/index.js index 78be3973a..38af91d8e 100644 --- a/ui/src/Components/FetchPauser/index.js +++ b/ui/src/Components/FetchPauser/index.js @@ -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() { diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js index 68bbe5832..b38527c28 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js @@ -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( diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/__snapshots__/index.test.js.snap b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/__snapshots__/index.test.js.snap index 9ab14ce9b..d2bcd90db 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/__snapshots__/index.test.js.snap +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/__snapshots__/index.test.js.snap @@ -42,7 +42,7 @@ exports[` matches snapshot with showAlertmanagers=false showReceiver=fa hidden - { ); }; -const MountedAlert = (alert, showAlertmanagers, showReceiver) => { +const MountedAlert = (alert, group, showAlertmanagers, showReceiver) => { return mount( { describe("", () => { 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("", () => { 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("", () => { 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/); diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.js index 9d2cfb5aa..66bccfa33 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.js @@ -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, diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.js index 9ba34c336..72a51d520 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.js @@ -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 }; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js index 57492a20e..26943ca88 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js @@ -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( diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/index.js index 3746c1174..9bc6f68ad 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/index.js @@ -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() { diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js index 8cb510938..67cbf2cfe 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js @@ -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 }; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js index 04a742b91..45ca92894 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js @@ -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", diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js index 8287590be..89b4486e3 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js @@ -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 diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js index 1a520ba30..865f6ea9c 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js @@ -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; diff --git a/ui/src/Components/Grid/AlertGrid/index.test.js b/ui/src/Components/Grid/AlertGrid/index.test.js index 8e7e5dd6e..b9b3fce88 100644 --- a/ui/src/Components/Grid/AlertGrid/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/index.test.js @@ -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 }, diff --git a/ui/src/Components/NavBar/FetchIndicator/index.js b/ui/src/Components/NavBar/FetchIndicator/index.js index b6fee4d91..28e7c32f4 100644 --- a/ui/src/Components/NavBar/FetchIndicator/index.js +++ b/ui/src/Components/NavBar/FetchIndicator/index.js @@ -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 }) => ( ( /> ); 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() { diff --git a/ui/src/Components/NavBar/FilterInput/History.js b/ui/src/Components/NavBar/FilterInput/History.js index 03a4eb498..e4dbeebf9 100644 --- a/ui/src/Components/NavBar/FilterInput/History.js +++ b/ui/src/Components/NavBar/FilterInput/History.js @@ -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 }; diff --git a/ui/src/Components/SilenceModal/AlertManagerInput.js b/ui/src/Components/SilenceModal/AlertManagerInput.js index 19ed6397d..8a7d5f351 100644 --- a/ui/src/Components/SilenceModal/AlertManagerInput.js +++ b/ui/src/Components/SilenceModal/AlertManagerInput.js @@ -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) { diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.js b/ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.js index 3a35e1856..5a15ac4ce 100644 --- a/ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.js +++ b/ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.js @@ -21,7 +21,7 @@ const IconTd = ({ icon, onClick }) => ( ); IconTd.propTypes = { - icon: PropTypes.object.isRequired, + icon: FontAwesomeIcon.propTypes.icon.isRequired, onClick: PropTypes.func.isRequired }; diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/index.js b/ui/src/Components/SilenceModal/DateTimeSelect/index.js index 6e6c4d93f..a4bce2aa2 100644 --- a/ui/src/Components/SilenceModal/DateTimeSelect/index.js +++ b/ui/src/Components/SilenceModal/DateTimeSelect/index.js @@ -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( diff --git a/ui/src/Components/SilenceModal/LabelNameInput.js b/ui/src/Components/SilenceModal/LabelNameInput.js index 4fa029579..4a3176af8 100644 --- a/ui/src/Components/SilenceModal/LabelNameInput.js +++ b/ui/src/Components/SilenceModal/LabelNameInput.js @@ -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 }; diff --git a/ui/src/Components/SilenceModal/LabelValueInput.js b/ui/src/Components/SilenceModal/LabelValueInput.js index 4eb2e762c..da6b0d90d 100644 --- a/ui/src/Components/SilenceModal/LabelValueInput.js +++ b/ui/src/Components/SilenceModal/LabelValueInput.js @@ -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 }; diff --git a/ui/src/Components/SilenceModal/SilenceForm.js b/ui/src/Components/SilenceModal/SilenceForm.js index 9bdcf0ca3..d11b59d5b 100644 --- a/ui/src/Components/SilenceModal/SilenceForm.js +++ b/ui/src/Components/SilenceModal/SilenceForm.js @@ -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 diff --git a/ui/src/Components/SilenceModal/SilenceForm.test.js b/ui/src/Components/SilenceModal/SilenceForm.test.js index 3765ccfe5..1a3850b9c 100644 --- a/ui/src/Components/SilenceModal/SilenceForm.test.js +++ b/ui/src/Components/SilenceModal/SilenceForm.test.js @@ -161,7 +161,7 @@ describe("", () => { 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" } diff --git a/ui/src/Components/SilenceModal/SilenceMatch.js b/ui/src/Components/SilenceModal/SilenceMatch.js index 5172e2fb4..9c4357b3e 100644 --- a/ui/src/Components/SilenceModal/SilenceMatch.js +++ b/ui/src/Components/SilenceModal/SilenceMatch.js @@ -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 diff --git a/ui/src/Components/SilenceModal/SilenceModalContent.js b/ui/src/Components/SilenceModal/SilenceModalContent.js index ff83d4493..c67cdd580 100644 --- a/ui/src/Components/SilenceModal/SilenceModalContent.js +++ b/ui/src/Components/SilenceModal/SilenceModalContent.js @@ -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 }; diff --git a/ui/src/Components/SilenceModal/SilencePreview.js b/ui/src/Components/SilenceModal/SilencePreview.js index 72319d821..ba004eff2 100644 --- a/ui/src/Components/SilenceModal/SilencePreview.js +++ b/ui/src/Components/SilenceModal/SilencePreview.js @@ -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() { diff --git a/ui/src/Components/SilenceModal/SilenceSubmitController.js b/ui/src/Components/SilenceModal/SilenceSubmitController.js index 7a2de3f5c..179ec8db7 100644 --- a/ui/src/Components/SilenceModal/SilenceSubmitController.js +++ b/ui/src/Components/SilenceModal/SilenceSubmitController.js @@ -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() { diff --git a/ui/src/Components/SilenceModal/SilenceSubmitProgress.js b/ui/src/Components/SilenceModal/SilenceSubmitProgress.js index 103f3a32e..b55c500f8 100644 --- a/ui/src/Components/SilenceModal/SilenceSubmitProgress.js +++ b/ui/src/Components/SilenceModal/SilenceSubmitProgress.js @@ -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( diff --git a/ui/src/Components/SilenceModal/SilenceSubmitProgress.test.js b/ui/src/Components/SilenceModal/SilenceSubmitProgress.test.js index 355e1e159..1f95f320a 100644 --- a/ui/src/Components/SilenceModal/SilenceSubmitProgress.test.js +++ b/ui/src/Components/SilenceModal/SilenceSubmitProgress.test.js @@ -28,7 +28,13 @@ const MountedSilenceSubmitProgress = () => { ); @@ -52,7 +58,13 @@ describe("", () => { 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" + }) }); }); diff --git a/ui/src/Components/SilenceModal/index.js b/ui/src/Components/SilenceModal/index.js index b1f8b43d3..4e3b9c241 100644 --- a/ui/src/Components/SilenceModal/index.js +++ b/ui/src/Components/SilenceModal/index.js @@ -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 = () => { diff --git a/ui/src/Models/API.js b/ui/src/Models/API.js new file mode 100644 index 000000000..78fe3e704 --- /dev/null +++ b/ui/src/Models/API.js @@ -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 +}; diff --git a/ui/src/Models/SilenceForm.js b/ui/src/Models/SilenceForm.js new file mode 100644 index 000000000..afb50c7ed --- /dev/null +++ b/ui/src/Models/SilenceForm.js @@ -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 };