diff --git a/ui/package-lock.json b/ui/package-lock.json index c766e5e9b..997db2701 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -4956,6 +4956,11 @@ "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==" }, + "@types/fontfaceobserver": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/fontfaceobserver/-/fontfaceobserver-0.0.6.tgz", + "integrity": "sha512-QJ1znjr9CDax2L17rgBnDOfNHsC1XtVAMswu+lRWvWb+kANhHA0slUNSSBsG8FVNvM4I4yXlN9doJRot3A2hkQ==" + }, "@types/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", @@ -5116,6 +5121,14 @@ "csstype": "^2.2.0" } }, + "@types/react-autosuggest": { + "version": "9.3.14", + "resolved": "https://registry.npmjs.org/@types/react-autosuggest/-/react-autosuggest-9.3.14.tgz", + "integrity": "sha512-cvGpKaQaNsFbDxTwP56VKVj2FO6SpJ9PsrQtlVzN7aVa/SsMZoQrBLEUx5HQKfIS4Zupb6K4tHmIyTjF7AEcow==", + "requires": { + "@types/react": "*" + } + }, "@types/react-dom": { "version": "16.9.8", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", @@ -5124,6 +5137,14 @@ "@types/react": "*" } }, + "@types/react-highlighter": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@types/react-highlighter/-/react-highlighter-0.3.4.tgz", + "integrity": "sha512-wM8l3QxU1P6rAeCIJUNug407Z8igm90xX9jDpjem1+pVzEI3VQpKiQj7oEinQVq7425Dmgyon8XfqbxD0mTknA==", + "requires": { + "@types/react": "*" + } + }, "@types/react-js-pagination": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/react-js-pagination/-/react-js-pagination-3.0.3.tgz", diff --git a/ui/package.json b/ui/package.json index f9031d26b..b4b99ae95 100644 --- a/ui/package.json +++ b/ui/package.json @@ -13,6 +13,7 @@ "@sentry/browser": "5.19.2", "@types/body-scroll-lock": "2.6.1", "@types/bricks.js": "1.8.1", + "@types/fontfaceobserver": "0.0.6", "@types/lodash.debounce": "4.0.6", "@types/lodash.merge": "4.6.6", "@types/lodash.throttle": "4.1.6", @@ -20,7 +21,9 @@ "@types/promise-retry": "1.1.3", "@types/qs": "6.9.3", "@types/react": "16.9.43", + "@types/react-autosuggest": "9.3.14", "@types/react-dom": "16.9.8", + "@types/react-highlighter": "0.3.4", "@types/react-js-pagination": "3.0.3", "@types/react-select": "3.0.13", "body-scroll-lock": "3.0.3", @@ -42,7 +45,6 @@ "mobx-stored": "1.1.0", "node-sass": "4.14.1", "promise-retry": "2.0.1", - "prop-types": "15.7.2", "qs": "6.9.4", "react": "16.13.1", "react-app-polyfill": "1.0.6", diff --git a/ui/src/Common/Select.ts b/ui/src/Common/Select.ts index ea55d34bd..9ea1197e3 100644 --- a/ui/src/Common/Select.ts +++ b/ui/src/Common/Select.ts @@ -7,6 +7,11 @@ export interface OptionT { value: string; } +export interface MultiValueOptionT { + label: string; + value: string[]; +} + export const StringToOption = (value: string): OptionT => ({ label: value, value: value, diff --git a/ui/src/Components/Animations/DropdownSlide/index.tsx b/ui/src/Components/Animations/DropdownSlide/index.tsx index f7ecb4161..f59afc451 100644 --- a/ui/src/Components/Animations/DropdownSlide/index.tsx +++ b/ui/src/Components/Animations/DropdownSlide/index.tsx @@ -1,12 +1,12 @@ import React, { FC, ReactNode } from "react"; -import PropTypes from "prop-types"; import { CSSTransition } from "react-transition-group"; const DropdownSlide: FC<{ children: ReactNode; - duration: number; -}> = ({ children, duration, ...props }) => ( + in?: boolean; + unmountOnExit?: boolean; +}> = ({ children, ...props }) => ( ); -DropdownSlide.propTypes = { - children: PropTypes.node.isRequired, -}; export { DropdownSlide }; diff --git a/ui/src/Components/Animations/MountModal/index.tsx b/ui/src/Components/Animations/MountModal/index.tsx index 588b401d7..27916c707 100644 --- a/ui/src/Components/Animations/MountModal/index.tsx +++ b/ui/src/Components/Animations/MountModal/index.tsx @@ -1,5 +1,4 @@ import React, { FC, ReactNode } from "react"; -import PropTypes from "prop-types"; import { CSSTransition } from "react-transition-group"; @@ -19,9 +18,6 @@ const MountModal: FC<{ {children} ); -MountModal.propTypes = { - children: PropTypes.node.isRequired, -}; const MountModalBackdrop: FC<{ children: ReactNode; @@ -39,8 +35,5 @@ const MountModalBackdrop: FC<{ {children} ); -MountModalBackdrop.propTypes = { - children: PropTypes.node.isRequired, -}; export { MountModal, MountModalBackdrop }; diff --git a/ui/src/Components/CenteredMessage/index.tsx b/ui/src/Components/CenteredMessage/index.tsx index 43a15f073..f15fef288 100644 --- a/ui/src/Components/CenteredMessage/index.tsx +++ b/ui/src/Components/CenteredMessage/index.tsx @@ -6,7 +6,7 @@ import { ThemeContext } from "Components/Theme"; const CenteredMessage: FC<{ children: ReactNode; - className: string; + className?: string; }> = ({ children, className }) => { const context = React.useContext(ThemeContext); return ( diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.tsx similarity index 83% rename from ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js rename to ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.tsx index 58b435f8a..f84ac54f3 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.tsx @@ -1,5 +1,11 @@ -import React, { useRef, useState, useCallback } from "react"; -import PropTypes from "prop-types"; +import React, { + FC, + Ref, + CSSProperties, + useRef, + useState, + useCallback, +} from "react"; import { useObserver } from "mobx-react-lite"; @@ -10,11 +16,10 @@ import { faCaretDown } from "@fortawesome/free-solid-svg-icons/faCaretDown"; import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash"; import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons/faExternalLinkAlt"; -import { APIAlert, APIGroup } from "Models/API"; +import { APIAlertT, APIAlertGroupT } from "Models/APITypes"; import { AlertStore } from "Stores/AlertStore"; import { SilenceFormStore, - SilenceTabNames, AlertmanagerClustersToOption, } from "Stores/SilenceFormStore"; import { CommonPopperModifiers } from "Common/Popper"; @@ -28,8 +33,13 @@ const PopperModifiers = [ { name: "offset", options: { offset: "-5px, 0px" } }, ]; -const onSilenceClick = (alertStore, silenceFormStore, group, alert) => { - let clusters = {}; +const onSilenceClick = ( + alertStore: AlertStore, + silenceFormStore: SilenceFormStore, + group: APIAlertGroupT, + alert: APIAlertT +) => { + let clusters: { [cluster: string]: string[] } = {}; Object.entries(alertStore.data.clustersWithoutReadOnly).forEach( ([cluster, members]) => { if (alert.alertmanager.map((am) => am.cluster).includes(cluster)) { @@ -45,11 +55,20 @@ const onSilenceClick = (alertStore, silenceFormStore, group, alert) => { AlertmanagerClustersToOption(clusters), [alert] ); - silenceFormStore.tab.setTab(SilenceTabNames.Editor); + silenceFormStore.tab.setTab("editor"); silenceFormStore.toggle.show(); }; -const MenuContent = ({ +const MenuContent: FC<{ + popperPlacement?: string; + popperRef?: Ref; + popperStyle?: CSSProperties; + group: APIAlertGroupT; + alert: APIAlertT; + afterClick: () => void; + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; +}> = ({ popperPlacement, popperRef, popperStyle, @@ -102,22 +121,14 @@ const MenuContent = ({ ); }; -MenuContent.propTypes = { - popperPlacement: PropTypes.string, - popperRef: PropTypes.func, - popperStyle: PropTypes.object, - group: APIGroup.isRequired, - alert: APIAlert.isRequired, - afterClick: PropTypes.func.isRequired, -}; -const AlertMenu = ({ - group, - alert, - alertStore, - silenceFormStore, - setIsMenuOpen, -}) => { +const AlertMenu: FC<{ + group: APIAlertGroupT; + alert: APIAlertT; + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; + setIsMenuOpen: (isOpen: boolean) => void; +}> = ({ group, alert, alertStore, silenceFormStore, setIsMenuOpen }) => { const [isHidden, setIsHidden] = useState(true); const toggle = useCallback(() => { @@ -173,12 +184,5 @@ const AlertMenu = ({ )); }; -AlertMenu.propTypes = { - group: APIGroup.isRequired, - alert: APIAlert.isRequired, - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - setIsMenuOpen: PropTypes.func.isRequired, -}; export { AlertMenu, MenuContent }; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.tsx similarity index 77% rename from ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.js rename to ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.tsx index 7e7ff6879..dd7b9c04c 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.tsx @@ -1,9 +1,12 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC } from "react"; import { useObserver } from "mobx-react-lite"; -import { APIAlert, APIGroup } from "Models/API"; +import { + APIAlertT, + APIAlertGroupT, + APIAlertmanagerStateT, +} from "Models/APITypes"; import { AlertStore } from "Stores/AlertStore"; import { SilenceFormStore } from "Stores/SilenceFormStore"; import { BorderClassMap } from "Common/Colors"; @@ -14,7 +17,16 @@ import { RenderNonLinkAnnotation, RenderLinkAnnotation } from "../Annotation"; import { AlertMenu } from "./AlertMenu"; import { RenderSilence } from "../Silences"; -const Alert = ({ +const Alert: FC<{ + group: APIAlertGroupT; + alert: APIAlertT; + showAlertmanagers: boolean; + showReceiver: boolean; + afterUpdate: () => void; + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; + setIsMenuOpen: (isOpen: boolean) => void; +}> = ({ group, alert, showAlertmanagers, @@ -34,9 +46,14 @@ const Alert = ({ BorderClassMap[alert.state] || "border-default", ]; - const silences = {}; - let clusters = []; - let inhibitedBy = []; + const silences: { + [cluster: string]: { + alertmanager: APIAlertmanagerStateT; + silences: string[]; + }; + } = {}; + let clusters: string[] = []; + let inhibitedBy: string[] = []; for (const am of alert.alertmanager) { if (!clusters.includes(am.cluster)) { clusters.push(am.cluster); @@ -49,8 +66,8 @@ const Alert = ({ if (!silences[am.cluster]) { silences[am.cluster] = { alertmanager: am, - silences: [ - ...new Set( + silences: Array.from( + new Set( am.silencedBy.filter( (silenceID) => !( @@ -58,8 +75,8 @@ const Alert = ({ group.shared.silences[am.cluster].includes(silenceID) ) ) - ), - ], + ) + ), }; } } @@ -120,28 +137,19 @@ const Alert = ({ ))} {Object.entries(silences).map(([cluster, clusterSilences]) => - clusterSilences.silences.map((silenceID) => - RenderSilence( - alertStore, - silenceFormStore, - afterUpdate, - cluster, - silenceID - ) - ) + clusterSilences.silences.map((silenceID) => ( + + )) )} )); }; -Alert.propTypes = { - group: APIGroup.isRequired, - alert: APIAlert.isRequired, - showAlertmanagers: PropTypes.bool.isRequired, - showReceiver: PropTypes.bool.isRequired, - afterUpdate: PropTypes.func.isRequired, - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - setIsMenuOpen: PropTypes.func.isRequired, -}; export { Alert }; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.js deleted file mode 100644 index 545dc039f..000000000 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.js +++ /dev/null @@ -1,97 +0,0 @@ -import React, { useEffect, useRef, useState, memo } from "react"; -import PropTypes from "prop-types"; - -import Linkify from "react-linkify"; - -import { CSSTransition } from "react-transition-group"; - -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons/faExternalLinkAlt"; -import { faSearchPlus } from "@fortawesome/free-solid-svg-icons/faSearchPlus"; -import { faSearchMinus } from "@fortawesome/free-solid-svg-icons/faSearchMinus"; - -import { TooltipWrapper } from "Components/TooltipWrapper"; -import { useFlashTransition } from "Hooks/useFlashTransition"; - -const RenderNonLinkAnnotation = memo( - ({ name, value, visible, afterUpdate }) => { - const mountRef = useRef(false); - - const [isVisible, setIsVisible] = useState(visible); - - useEffect(() => { - if (mountRef.current) { - afterUpdate(); - } else { - mountRef.current = true; - } - }); - - const { ref, props } = useFlashTransition(value); - - const className = - "mb-1 p-1 bg-light d-inline-block rounded components-grid-annotation text-break mw-100"; - - return ( - -
setIsVisible(!isVisible)} - > - {isVisible ? ( - - setIsVisible(false)} - className="cursor-pointer" - > - - {name}: - - - - {value} - - - - ) : ( - - - {name} - - )} -
-
- ); - } -); -RenderNonLinkAnnotation.propTypes = { - name: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, - visible: PropTypes.bool.isRequired, - afterUpdate: PropTypes.func.isRequired, -}; - -const RenderLinkAnnotation = ({ name, value }) => { - return ( - - {name} - - ); -}; -RenderLinkAnnotation.propTypes = { - name: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, -}; - -export { RenderNonLinkAnnotation, RenderLinkAnnotation }; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.tsx b/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.tsx new file mode 100644 index 000000000..9910d1f18 --- /dev/null +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.tsx @@ -0,0 +1,92 @@ +import React, { FC, useEffect, useRef, useState, memo } from "react"; + +import Linkify from "react-linkify"; + +import { CSSTransition } from "react-transition-group"; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons/faExternalLinkAlt"; +import { faSearchPlus } from "@fortawesome/free-solid-svg-icons/faSearchPlus"; +import { faSearchMinus } from "@fortawesome/free-solid-svg-icons/faSearchMinus"; + +import { TooltipWrapper } from "Components/TooltipWrapper"; +import { useFlashTransition } from "Hooks/useFlashTransition"; + +const RenderNonLinkAnnotation: FC<{ + name: string; + value: string; + visible: boolean; + afterUpdate: () => void; +}> = memo(({ name, value, visible, afterUpdate }) => { + const mountRef = useRef(false); + + const [isVisible, setIsVisible] = useState(visible); + + useEffect(() => { + if (mountRef.current) { + afterUpdate(); + } else { + mountRef.current = true; + } + }); + + const { ref, props } = useFlashTransition(value); + + const className = + "mb-1 p-1 bg-light d-inline-block rounded components-grid-annotation text-break mw-100"; + + return ( + +
setIsVisible(!isVisible)} + > + {isVisible ? ( + + setIsVisible(false)} + className="cursor-pointer" + > + + {name}: + + + + {value} + + + + ) : ( + + + {name} + + )} +
+
+ ); +}); + +const RenderLinkAnnotation: FC<{ + name: string; + value: string; +}> = ({ name, value }) => { + return ( + + {name} + + ); +}; + +export { RenderNonLinkAnnotation, RenderLinkAnnotation }; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/DetailsToggle.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/DetailsToggle.ts similarity index 81% rename from ui/src/Components/Grid/AlertGrid/AlertGroup/DetailsToggle.js rename to ui/src/Components/Grid/AlertGrid/AlertGroup/DetailsToggle.ts index 94d871bd9..d4a80ff72 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/DetailsToggle.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/DetailsToggle.ts @@ -1,6 +1,7 @@ import { IsMobile } from "Common/Device"; +import { Settings } from "Stores/Settings"; -const DefaultDetailsCollapseValue = (settingsStore) => { +const DefaultDetailsCollapseValue = (settingsStore: Settings): boolean => { let defaultCollapseState; switch (settingsStore.alertGroupConfig.config.defaultCollapseState) { case settingsStore.alertGroupConfig.options.collapsed.value: diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.tsx similarity index 73% rename from ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.js rename to ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.tsx index b360660b3..a4e589984 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.tsx @@ -1,9 +1,8 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC } from "react"; import { useObserver } from "mobx-react-lite"; -import { APIGroup } from "Models/API"; +import { APIAlertGroupT } from "Models/APITypes"; import { StaticLabels } from "Common/Query"; import { AlertStore } from "Stores/AlertStore"; import { SilenceFormStore } from "Stores/SilenceFormStore"; @@ -11,13 +10,13 @@ import { FilteringLabel } from "Components/Labels/FilteringLabel"; import { RenderNonLinkAnnotation, RenderLinkAnnotation } from "../Annotation"; import { RenderSilence } from "../Silences"; -const GroupFooter = ({ - group, - alertmanagers, - afterUpdate, - alertStore, - silenceFormStore, -}) => { +const GroupFooter: FC<{ + group: APIAlertGroupT; + alertmanagers: string[]; + afterUpdate: () => void; + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; +}> = ({ group, alertmanagers, afterUpdate, alertStore, silenceFormStore }) => { return useObserver(() => (
@@ -64,27 +63,21 @@ const GroupFooter = ({ {Object.keys(group.shared.silences).length === 0 ? null : (
{Object.entries(group.shared.silences).map(([cluster, silences]) => - silences.map((silenceID) => - RenderSilence( - alertStore, - silenceFormStore, - afterUpdate, - cluster, - silenceID - ) - ) + silences.map((silenceID) => ( + + )) )}
)}
)); }; -GroupFooter.propTypes = { - group: APIGroup.isRequired, - alertmanagers: PropTypes.arrayOf(PropTypes.string).isRequired, - afterUpdate: PropTypes.func.isRequired, - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, -}; export { GroupFooter }; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.tsx similarity index 83% rename from ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js rename to ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.tsx index a0f89dcce..b3f336302 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.tsx @@ -1,5 +1,11 @@ -import React, { useRef, useState, useCallback } from "react"; -import PropTypes from "prop-types"; +import React, { + FC, + useRef, + useState, + useCallback, + Ref, + CSSProperties, +} from "react"; import copy from "copy-to-clipboard"; @@ -10,12 +16,11 @@ 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 { APIAlertGroupT } from "Models/APITypes"; import { FormatAlertsQ } from "Stores/AlertStore"; import { AlertStore } from "Stores/AlertStore"; import { SilenceFormStore, - SilenceTabNames, AlertmanagerClustersToOption, } from "Stores/SilenceFormStore"; import { QueryOperators, StaticLabels, FormatQuery } from "Common/Query"; @@ -29,8 +34,12 @@ const PopperModifiers = [ { name: "offset", options: { offset: "-5px, 0px" } }, ]; -const onSilenceClick = (alertStore, silenceFormStore, group) => { - let clusters = {}; +const onSilenceClick = ( + alertStore: AlertStore, + silenceFormStore: SilenceFormStore, + group: APIAlertGroupT +) => { + let clusters: { [cluster: string]: string[] } = {}; Object.entries(alertStore.data.clustersWithoutReadOnly).forEach( ([cluster, members]) => { members.forEach((member) => { @@ -47,11 +56,19 @@ const onSilenceClick = (alertStore, silenceFormStore, group) => { alertStore.settings.values.silenceForm.strip.labels, AlertmanagerClustersToOption(clusters) ); - silenceFormStore.tab.setTab(SilenceTabNames.Editor); + silenceFormStore.tab.setTab("editor"); silenceFormStore.toggle.show(); }; -const MenuContent = ({ +const MenuContent: FC<{ + popperPlacement?: string; + popperRef?: Ref; + popperStyle?: CSSProperties; + group: APIAlertGroupT; + afterClick: () => void; + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; +}> = ({ popperPlacement, popperRef, popperStyle, @@ -110,21 +127,14 @@ const MenuContent = ({ ); }; -MenuContent.propTypes = { - popperPlacement: PropTypes.string, - popperRef: PropTypes.func, - popperStyle: PropTypes.object, - group: APIGroup.isRequired, - afterClick: PropTypes.func.isRequired, -}; -const GroupMenu = ({ - group, - alertStore, - silenceFormStore, - themed, - setIsMenuOpen, -}) => { +const GroupMenu: FC<{ + group: APIAlertGroupT; + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; + themed: boolean; + setIsMenuOpen: (isOpen: boolean) => void; +}> = ({ group, alertStore, silenceFormStore, themed, setIsMenuOpen }) => { const [isHidden, setIsHidden] = useState(true); const toggle = useCallback(() => { @@ -176,12 +186,5 @@ const GroupMenu = ({ ); }; -GroupMenu.propTypes = { - group: APIGroup.isRequired, - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - themed: PropTypes.bool.isRequired, - setIsMenuOpen: PropTypes.func.isRequired, -}; export { GroupMenu, MenuContent }; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/index.tsx similarity index 84% rename from ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/index.js rename to ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/index.tsx index d2d88355f..b8db35adc 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/index.tsx @@ -1,9 +1,8 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC, MouseEvent } from "react"; import { useObserver } from "mobx-react-lite"; -import { APIGroup } from "Models/API"; +import { APIAlertGroupT } from "Models/APITypes"; import { AlertStore } from "Stores/AlertStore"; import { SilenceFormStore } from "Stores/SilenceFormStore"; import { FilteringLabel } from "Components/Labels/FilteringLabel"; @@ -13,7 +12,16 @@ import { AlertAck } from "Components/AlertAck"; import { ToggleIcon } from "Components/ToggleIcon"; import { GroupMenu } from "./GroupMenu"; -const GroupHeader = ({ +const GroupHeader: FC<{ + isCollapsed: boolean; + setIsCollapsed: (isCollapsed: boolean) => void; + group: APIAlertGroupT; + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; + themedCounters: boolean; + setIsMenuOpen: (isOpen: boolean) => void; + gridLabelValue: string; +}> = ({ isCollapsed, setIsCollapsed, group, @@ -23,7 +31,7 @@ const GroupHeader = ({ setIsMenuOpen, gridLabelValue, }) => { - const onCollapseClick = (event) => { + const onCollapseClick = (event: MouseEvent) => { // left click => toggle current group // left click + alt => toggle all groups setIsCollapsed(!isCollapsed); @@ -107,15 +115,5 @@ const GroupHeader = ({ )); }; -GroupHeader.propTypes = { - isCollapsed: PropTypes.bool.isRequired, - setIsCollapsed: PropTypes.func.isRequired, - group: APIGroup.isRequired, - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - themedCounters: PropTypes.bool.isRequired, - setIsMenuOpen: PropTypes.func.isRequired, - gridLabelValue: PropTypes.string.isRequired, -}; export { GroupHeader }; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silences.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silences.tsx similarity index 60% rename from ui/src/Components/Grid/AlertGrid/AlertGroup/Silences.js rename to ui/src/Components/Grid/AlertGrid/AlertGroup/Silences.tsx index 61ed31175..b2053296b 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silences.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silences.tsx @@ -1,19 +1,25 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC } from "react"; +import { APISilenceT } from "Models/APITypes"; +import { AlertStore } from "Stores/AlertStore"; +import { SilenceFormStore } from "Stores/SilenceFormStore"; import { ManagedSilence } from "Components/ManagedSilence"; -const FallbackSilenceDesciption = ({ silenceID }) => { + +const FallbackSilenceDesciption: FC<{ + silenceID: string; +}> = ({ silenceID }) => { return (
Silenced by {silenceID}
); }; -FallbackSilenceDesciption.propTypes = { - silenceID: PropTypes.string.isRequired, -}; -const GetSilenceFromStore = (alertStore, cluster, silenceID) => { +const GetSilenceFromStore = ( + alertStore: AlertStore, + cluster: string, + silenceID: string +): APISilenceT | null => { const amSilences = alertStore.data.silences[cluster]; if (!amSilences) return null; @@ -24,13 +30,13 @@ const GetSilenceFromStore = (alertStore, cluster, silenceID) => { return silence; }; -const RenderSilence = ( - alertStore, - silenceFormStore, - afterUpdate, - cluster, - silenceID -) => { +const RenderSilence: FC<{ + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; + afterUpdate: () => void; + cluster: string; + silenceID: string; +}> = ({ alertStore, silenceFormStore, afterUpdate, cluster, silenceID }) => { const silence = GetSilenceFromStore(alertStore, cluster, silenceID); if (silence === null) { diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.tsx similarity index 86% rename from ui/src/Components/Grid/AlertGrid/AlertGroup/index.js rename to ui/src/Components/Grid/AlertGrid/AlertGroup/index.tsx index 7b81eeda9..b83ef5f79 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.tsx @@ -1,13 +1,13 @@ -import React, { useEffect, useCallback, useState } from "react"; -import PropTypes from "prop-types"; +import React, { FC, useEffect, useCallback, useState, ReactNode } from "react"; import { useObserver } from "mobx-react-lite"; +import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; 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 { APIAlertT, APIAlertGroupT, AlertStateT } from "Models/APITypes"; import { Settings } from "Stores/Settings"; import { AlertStore } from "Stores/AlertStore"; import { SilenceFormStore } from "Stores/SilenceFormStore"; @@ -18,7 +18,11 @@ import { Alert } from "./Alert"; import { GroupFooter } from "./GroupFooter"; import { DefaultDetailsCollapseValue } from "./DetailsToggle"; -const LoadButton = ({ icon, action, tooltip }) => { +const LoadButton: FC<{ + icon: IconDefinition; + action: () => void; + tooltip: ReactNode; +}> = ({ icon, action, tooltip }) => { return (
)); }; -AlertGroup.propTypes = { - afterUpdate: PropTypes.func.isRequired, - group: APIGroup.isRequired, - showAlertmanagers: PropTypes.bool.isRequired, - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - settingsStore: PropTypes.instanceOf(Settings).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - groupWidth: PropTypes.number.isRequired, - gridLabelValue: PropTypes.string.isRequired, - initialAlertsToRender: PropTypes.number, -}; export { AlertGroup }; diff --git a/ui/src/Components/Grid/AlertGrid/Grid.js b/ui/src/Components/Grid/AlertGrid/Grid.tsx similarity index 91% rename from ui/src/Components/Grid/AlertGrid/Grid.js rename to ui/src/Components/Grid/AlertGrid/Grid.tsx index f30273ec5..a25eb911c 100644 --- a/ui/src/Components/Grid/AlertGrid/Grid.js +++ b/ui/src/Components/Grid/AlertGrid/Grid.tsx @@ -1,10 +1,18 @@ -import React, { useEffect, useState, useCallback } from "react"; -import PropTypes from "prop-types"; +import React, { + FC, + useEffect, + useState, + useCallback, + MouseEvent, + Ref, +} from "react"; import { useObserver } from "mobx-react-lite"; import debounce from "lodash.debounce"; +import { SizeDetail } from "bricks.js"; + import TransitionGroup from "react-transition-group/TransitionGroup"; import { CSSTransition } from "react-transition-group"; @@ -16,14 +24,22 @@ import { faAngleDoubleDown } from "@fortawesome/free-solid-svg-icons/faAngleDoub import { AlertStore } from "Stores/AlertStore"; import { Settings } from "Stores/Settings"; import { SilenceFormStore } from "Stores/SilenceFormStore"; -import { APIGrid } from "Models/API"; +import { APIGridT } from "Models/APITypes"; import { useGrid } from "Hooks/useGrid"; import { ThemeContext } from "Components/Theme"; import { DefaultDetailsCollapseValue } from "./AlertGroup/DetailsToggle"; import { AlertGroup } from "./AlertGroup"; import { Swimlane } from "./Swimlane"; -const Grid = ({ +const Grid: FC<{ + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; + settingsStore: Settings; + gridSizesConfig: SizeDetail[]; + groupWidth: number; + grid: APIGridT; + outerPadding: number; +}> = ({ alertStore, settingsStore, silenceFormStore, @@ -46,7 +62,7 @@ const Grid = ({ debouncedRepack(); }, [debouncedRepack, isExpanded]); - const onCollapseClick = (event) => { + const onCollapseClick = (event: MouseEvent) => { // left click => toggle current grid // left click + alt => toggle all grids @@ -118,7 +134,7 @@ const Grid = ({
} key={settingsStore.gridConfig.config.groupWidth} style={{ paddingLeft: outerPadding + "px", @@ -182,14 +198,5 @@ const Grid = ({ )); }; -Grid.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - settingsStore: PropTypes.instanceOf(Settings).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - gridSizesConfig: PropTypes.array.isRequired, - groupWidth: PropTypes.number.isRequired, - grid: APIGrid.isRequired, - outerPadding: PropTypes.number.isRequired, -}; export { Grid }; diff --git a/ui/src/Components/Grid/AlertGrid/GridSize.js b/ui/src/Components/Grid/AlertGrid/GridSize.ts similarity index 57% rename from ui/src/Components/Grid/AlertGrid/GridSize.js rename to ui/src/Components/Grid/AlertGrid/GridSize.ts index fd8e93a02..827d2364d 100644 --- a/ui/src/Components/Grid/AlertGrid/GridSize.js +++ b/ui/src/Components/Grid/AlertGrid/GridSize.ts @@ -1,6 +1,8 @@ +import { SizeDetail } from "bricks.js"; + // grid sizes, defines how many columns are used depending on the screen width // this is config as expected by https://github.com/callmecavs/bricks.js#sizes -const GridSizesConfig = (canvasWidth, baseWidth) => { +const GridSizesConfig = (baseWidth: number): SizeDetail[] => { const generatedSizes = []; for (let i = 2; i < 20; i++) { generatedSizes.push({ @@ -12,14 +14,19 @@ const GridSizesConfig = (canvasWidth, baseWidth) => { return [...[{ columns: 1, gutter: 0 }], ...generatedSizes]; }; -const GetColumnsCount = (canvasWidth, baseWidth) => - [{ mq: "0px", columns: 1 }, ...GridSizesConfig(canvasWidth, baseWidth)] +const GetColumnsCount = (canvasWidth: number, baseWidth: number): number => + [{ mq: "0px", columns: 1 }, ...GridSizesConfig(baseWidth)] .filter((gs) => gs.mq !== undefined) - .filter((gs) => canvasWidth >= Number.parseInt(gs.mq)) + .filter((gs) => canvasWidth >= Number.parseInt(gs.mq as string)) .map((gs) => gs.columns) - .pop(); + .pop() as number; -const GetGridElementWidth = (innerWidth, outerWidth, outerPadding, baseWidth) => +const GetGridElementWidth = ( + innerWidth: number, + outerWidth: number, + outerPadding: number, + baseWidth: number +): number => Math.floor( (innerWidth - outerPadding) / GetColumnsCount(outerWidth, baseWidth) ); diff --git a/ui/src/Components/Grid/AlertGrid/Swimlane.js b/ui/src/Components/Grid/AlertGrid/Swimlane.tsx similarity index 85% rename from ui/src/Components/Grid/AlertGrid/Swimlane.js rename to ui/src/Components/Grid/AlertGrid/Swimlane.tsx index 22225807f..21948a2a3 100644 --- a/ui/src/Components/Grid/AlertGrid/Swimlane.js +++ b/ui/src/Components/Grid/AlertGrid/Swimlane.tsx @@ -1,17 +1,21 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC, MouseEvent } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faTh } from "@fortawesome/free-solid-svg-icons/faTh"; import { AlertStore } from "Stores/AlertStore"; -import { APIGrid } from "Models/API"; +import { APIGridT } from "Models/APITypes"; import { FilteringLabel } from "Components/Labels/FilteringLabel"; import { FilteringCounterBadge } from "Components/Labels/FilteringCounterBadge"; import { TooltipWrapper } from "Components/TooltipWrapper"; import { ToggleIcon } from "Components/ToggleIcon"; -const Swimlane = ({ alertStore, grid, isExpanded, onToggle }) => { +const Swimlane: FC<{ + alertStore: AlertStore; + grid: APIGridT; + isExpanded: boolean; + onToggle: (event: MouseEvent) => void; +}> = ({ alertStore, grid, isExpanded, onToggle }) => { return (
@@ -63,11 +67,5 @@ const Swimlane = ({ alertStore, grid, isExpanded, onToggle }) => {
); }; -Swimlane.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - grid: APIGrid.isRequired, - isExpanded: PropTypes.bool.isRequired, - onToggle: PropTypes.func.isRequired, -}; export { Swimlane }; diff --git a/ui/src/Components/Grid/AlertGrid/index.test.js b/ui/src/Components/Grid/AlertGrid/index.test.js index d2d85f1b1..234f478ed 100644 --- a/ui/src/Components/Grid/AlertGrid/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/index.test.js @@ -97,7 +97,7 @@ const ShallowGrid = () => { alertStore={alertStore} silenceFormStore={silenceFormStore} settingsStore={settingsStore} - gridSizesConfig={GridSizesConfig(1024, 420)} + gridSizesConfig={GridSizesConfig(420)} groupWidth={420} grid={MockGrid()} outerPadding={0} @@ -111,7 +111,7 @@ const MountedGrid = () => { alertStore={alertStore} silenceFormStore={silenceFormStore} settingsStore={settingsStore} - gridSizesConfig={GridSizesConfig(1024, 420)} + gridSizesConfig={GridSizesConfig(420)} groupWidth={420} grid={MockGrid()} outerPadding={0} diff --git a/ui/src/Components/Grid/AlertGrid/index.js b/ui/src/Components/Grid/AlertGrid/index.tsx similarity index 74% rename from ui/src/Components/Grid/AlertGrid/index.js rename to ui/src/Components/Grid/AlertGrid/index.tsx index f5eb67c63..b50fb2569 100644 --- a/ui/src/Components/Grid/AlertGrid/index.js +++ b/ui/src/Components/Grid/AlertGrid/index.tsx @@ -1,5 +1,4 @@ -import React, { useEffect, useState } from "react"; -import PropTypes from "prop-types"; +import React, { FC, Ref, useEffect, useState } from "react"; import { autorun } from "mobx"; import { useObserver } from "mobx-react-lite"; @@ -13,12 +12,16 @@ import { useWindowSize } from "Hooks/useWindowSize"; import { Grid } from "./Grid"; import { GridSizesConfig, GetGridElementWidth } from "./GridSize"; -const AlertGrid = ({ alertStore, settingsStore, silenceFormStore }) => { +const AlertGrid: FC<{ + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; + settingsStore: Settings; +}> = ({ alertStore, settingsStore, silenceFormStore }) => { const { width: windowWidth } = useWindowSize(); const { ref, width: bodyWidth } = useDimensions(); const [gridSizesConfig, setGridSizesConfig] = useState( - GridSizesConfig(windowWidth, settingsStore.gridConfig.config.groupWidth) + GridSizesConfig(settingsStore.gridConfig.config.groupWidth) ); const [groupWidth, setGroupWidth] = useState( GetGridElementWidth( @@ -33,10 +36,7 @@ const AlertGrid = ({ alertStore, settingsStore, silenceFormStore }) => { () => autorun(() => { setGridSizesConfig( - GridSizesConfig( - windowWidth, - settingsStore.gridConfig.config.groupWidth - ) + GridSizesConfig(settingsStore.gridConfig.config.groupWidth) ); setGroupWidth( GetGridElementWidth( @@ -52,7 +52,7 @@ const AlertGrid = ({ alertStore, settingsStore, silenceFormStore }) => { return useObserver(() => ( -
+
} /> {alertStore.data.grids.map((grid) => ( { )); }; -AlertGrid.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - settingsStore: PropTypes.instanceOf(Settings).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, -}; export { AlertGrid }; diff --git a/ui/src/Components/Grid/EmptyGrid/index.js b/ui/src/Components/Grid/EmptyGrid/index.tsx similarity index 85% rename from ui/src/Components/Grid/EmptyGrid/index.js rename to ui/src/Components/Grid/EmptyGrid/index.tsx index 446164f4e..447330e3b 100644 --- a/ui/src/Components/Grid/EmptyGrid/index.js +++ b/ui/src/Components/Grid/EmptyGrid/index.tsx @@ -1,11 +1,11 @@ -import React from "react"; +import React, { FC } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faMugHot } from "@fortawesome/free-solid-svg-icons/faMugHot"; import { CenteredMessage } from "Components/CenteredMessage"; -const EmptyGrid = () => ( +const EmptyGrid: FC = () => ( ( +const FatalError: FC<{ message: string }> = ({ message }) => (
(
); -FatalError.propTypes = { - message: PropTypes.string.isRequired, -}; export { FatalError }; diff --git a/ui/src/Components/Grid/ReloadNeeded/index.js b/ui/src/Components/Grid/ReloadNeeded/index.tsx similarity index 84% rename from ui/src/Components/Grid/ReloadNeeded/index.js rename to ui/src/Components/Grid/ReloadNeeded/index.tsx index 45e631179..183206415 100644 --- a/ui/src/Components/Grid/ReloadNeeded/index.js +++ b/ui/src/Components/Grid/ReloadNeeded/index.tsx @@ -1,5 +1,4 @@ -import React, { useEffect } from "react"; -import PropTypes from "prop-types"; +import React, { FC, useEffect } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle"; @@ -7,7 +6,9 @@ import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner"; import { CenteredMessage } from "Components/CenteredMessage"; -const ReloadNeeded = ({ reloadAfter }) => { +const ReloadNeeded: FC<{ + reloadAfter: number; +}> = ({ reloadAfter }) => { useEffect(() => { const timer = setTimeout(() => window.location.reload(), reloadAfter); return () => clearTimeout(timer); @@ -29,8 +30,5 @@ const ReloadNeeded = ({ reloadAfter }) => {
); }; -ReloadNeeded.propTypes = { - reloadAfter: PropTypes.number.isRequired, -}; export { ReloadNeeded }; diff --git a/ui/src/Components/Grid/UpgradeNeeded/index.js b/ui/src/Components/Grid/UpgradeNeeded/index.tsx similarity index 79% rename from ui/src/Components/Grid/UpgradeNeeded/index.js rename to ui/src/Components/Grid/UpgradeNeeded/index.tsx index 725ce352f..2a0aa4078 100644 --- a/ui/src/Components/Grid/UpgradeNeeded/index.js +++ b/ui/src/Components/Grid/UpgradeNeeded/index.tsx @@ -1,5 +1,4 @@ -import React, { useEffect } from "react"; -import PropTypes from "prop-types"; +import React, { FC, useEffect } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faRocket } from "@fortawesome/free-solid-svg-icons/faRocket"; @@ -9,7 +8,10 @@ import { CenteredMessage } from "Components/CenteredMessage"; import "csshake/scss/csshake-slow.scss"; -const UpgradeNeeded = ({ newVersion, reloadAfter }) => { +const UpgradeNeeded: FC<{ + newVersion: string; + reloadAfter: number; +}> = ({ newVersion, reloadAfter }) => { useEffect(() => { const timer = setTimeout(() => window.location.reload(), reloadAfter); return () => clearTimeout(timer); @@ -32,9 +34,5 @@ const UpgradeNeeded = ({ newVersion, reloadAfter }) => { ); }; -UpgradeNeeded.propTypes = { - newVersion: PropTypes.string.isRequired, - reloadAfter: PropTypes.number.isRequired, -}; export { UpgradeNeeded }; diff --git a/ui/src/Components/Grid/index.js b/ui/src/Components/Grid/index.tsx similarity index 83% rename from ui/src/Components/Grid/index.js rename to ui/src/Components/Grid/index.tsx index 4918bf0a5..b6f525ac9 100644 --- a/ui/src/Components/Grid/index.js +++ b/ui/src/Components/Grid/index.tsx @@ -1,5 +1,4 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC } from "react"; import { useObserver } from "mobx-react-lite"; @@ -13,7 +12,11 @@ import { UpgradeNeeded } from "./UpgradeNeeded"; import { ReloadNeeded } from "./ReloadNeeded"; import { EmptyGrid } from "./EmptyGrid"; -const Grid = ({ alertStore, settingsStore, silenceFormStore }) => { +const Grid: FC<{ + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; + settingsStore: Settings; +}> = ({ alertStore, settingsStore, silenceFormStore }) => { return useObserver(() => alertStore.info.upgradeNeeded ? ( @@ -50,10 +53,5 @@ const Grid = ({ alertStore, settingsStore, silenceFormStore }) => { ) ); }; -Grid.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - settingsStore: PropTypes.instanceOf(Settings).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, -}; export { Grid }; diff --git a/ui/src/Components/Labels/FilteringCounterBadge/index.tsx b/ui/src/Components/Labels/FilteringCounterBadge/index.tsx index 2e6eaac1d..35decbadd 100644 --- a/ui/src/Components/Labels/FilteringCounterBadge/index.tsx +++ b/ui/src/Components/Labels/FilteringCounterBadge/index.tsx @@ -19,9 +19,9 @@ const FilteringCounterBadge: FC<{ value: string; counter: number; themed: boolean; - alwaysVisible: boolean; - defaultColor: "light" | "primary"; - isAppend: boolean; + alwaysVisible?: boolean; + defaultColor?: "light" | "primary"; + isAppend?: boolean; }> = observer( ({ alertStore, @@ -29,7 +29,7 @@ const FilteringCounterBadge: FC<{ value, counter, themed, - alwaysVisible, + alwaysVisible = false, defaultColor = "light", isAppend = true, }) => { diff --git a/ui/src/Components/Labels/Utils.js b/ui/src/Components/Labels/Utils.ts similarity index 69% rename from ui/src/Components/Labels/Utils.js rename to ui/src/Components/Labels/Utils.ts index 698b929f0..107e37c1a 100644 --- a/ui/src/Components/Labels/Utils.js +++ b/ui/src/Components/Labels/Utils.ts @@ -5,13 +5,28 @@ import { StateLabelClassMap, } from "Common/Colors"; import { StaticLabels } from "Common/Query"; +import { AlertStateT } from "Models/APITypes"; +import { AlertStore } from "Stores/AlertStore"; -const isBackgroundDark = (brightness) => brightness <= 125; +const isBackgroundDark = (brightness: number) => brightness <= 125; -const GetClassAndStyle = (alertStore, name, value, extraClass, baseClass) => { +export interface ClassAndStyleT { + style: { [key: string]: string | number }; + className: string; + baseClassNames: string[]; + colorClassNames: string[]; +} + +const GetClassAndStyle = ( + alertStore: AlertStore, + name: string, + value: string, + extraClass?: string, + baseClass?: "badge" | "btn" +): ClassAndStyleT => { const elementType = baseClass || "badge"; - const data = { + const data: ClassAndStyleT = { style: {}, className: "", baseClassNames: ["components-label", elementType], @@ -22,8 +37,8 @@ const GetClassAndStyle = (alertStore, name, value, extraClass, baseClass) => { data.colorClassNames.push(AlertNameLabelClassMap[elementType]); } else if (name === StaticLabels.State) { data.colorClassNames.push( - StateLabelClassMap[value] - ? `${elementType}-${StateLabelClassMap[value]}` + StateLabelClassMap[value as AlertStateT] + ? `${elementType}-${StateLabelClassMap[value as AlertStateT]}` : DefaultLabelClassMap[elementType] ); } else if (alertStore.settings.values.staticColorLabels.includes(name)) { diff --git a/ui/src/Components/ManagedSilence/DeleteSilence.js b/ui/src/Components/ManagedSilence/DeleteSilence.tsx similarity index 78% rename from ui/src/Components/ManagedSilence/DeleteSilence.js rename to ui/src/Components/ManagedSilence/DeleteSilence.tsx index 5b758af06..d23e5b2ad 100644 --- a/ui/src/Components/ManagedSilence/DeleteSilence.js +++ b/ui/src/Components/ManagedSilence/DeleteSilence.tsx @@ -1,5 +1,4 @@ -import React, { useEffect, useState } from "react"; -import PropTypes from "prop-types"; +import React, { FC, useEffect, useState, ReactNode } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faTrash } from "@fortawesome/free-solid-svg-icons/faTrash"; @@ -7,7 +6,7 @@ import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclama import { faCheckCircle } from "@fortawesome/free-solid-svg-icons/faCheckCircle"; import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch"; -import { APISilence } from "Models/API"; +import { APISilenceT } from "Models/APITypes"; import { AlertStore } from "Stores/AlertStore"; import { SilenceFormStore } from "Stores/SilenceFormStore"; import { FormatQuery, QueryOperators, StaticLabels } from "Common/Query"; @@ -15,7 +14,7 @@ import { useFetchDelete } from "Hooks/useFetchDelete"; import { Modal } from "Components/Modal"; import { PaginatedAlertList } from "Components/PaginatedAlertList"; -const ProgressMessage = () => ( +const ProgressMessage: FC = () => (
(
); -const ErrorMessage = ({ message }) => ( +const ErrorMessage: FC<{ + message: ReactNode; +}> = ({ message }) => (
(

{message}

); -ErrorMessage.propTypes = { - message: PropTypes.node.isRequired, -}; -const SuccessMessage = () => ( +const SuccessMessage: FC = () => (
(
); -const DeleteResult = ({ alertStore, cluster, silence }) => { +const DeleteResult: FC<{ + alertStore: AlertStore; + cluster: string; + silence: APISilenceT; +}> = ({ alertStore, cluster, silence }) => { const [currentTime, setCurrentTime] = useState(Math.floor(Date.now())); const am = alertStore.data.readWriteAlertmanagers @@ -97,19 +99,14 @@ const DeleteResult = ({ alertStore, cluster, silence }) => { ); }; -DeleteResult.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - cluster: PropTypes.string.isRequired, - silence: APISilence.isRequired, -}; -const DeleteSilenceModalContent = ({ - alertStore, - silenceFormStore, - cluster, - silence, - onHide, -}) => { +const DeleteSilenceModalContent: FC<{ + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; + cluster: string; + silence: APISilenceT; + onHide: () => void; +}> = ({ alertStore, silenceFormStore, cluster, silence, onHide }) => { const [confirm, setConfirm] = useState(false); useEffect(() => { @@ -149,7 +146,7 @@ const DeleteSilenceModalContent = ({
); }; -SilenceDetails.propTypes = { - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - cluster: PropTypes.string.isRequired, - silence: APISilence.isRequired, - onEditSilence: PropTypes.func.isRequired, - isUpper: PropTypes.bool, -}; -SilenceDetails.defaultProps = { - isUpper: false, -}; export { SilenceDetails }; diff --git a/ui/src/Components/ManagedSilence/SilenceProgress.js b/ui/src/Components/ManagedSilence/SilenceProgress.tsx similarity index 84% rename from ui/src/Components/ManagedSilence/SilenceProgress.js rename to ui/src/Components/ManagedSilence/SilenceProgress.tsx index 4aea0af90..b7b8a43c5 100644 --- a/ui/src/Components/ManagedSilence/SilenceProgress.js +++ b/ui/src/Components/ManagedSilence/SilenceProgress.tsx @@ -1,16 +1,16 @@ -import React, { useEffect, useState } from "react"; +import React, { FC, useEffect, useState } from "react"; import { useObserver } from "mobx-react-lite"; import parseISO from "date-fns/parseISO"; import getUnixTime from "date-fns/getUnixTime"; -import { APISilence } from "Models/API"; +import { APISilenceT } from "Models/APITypes"; import { DateFromNow } from "Components/DateFromNow"; import "./SilenceProgress.scss"; -const calculatePercent = (startsAt, endsAt) => { +const calculatePercent = (startsAt: string, endsAt: string) => { const durationDone = getUnixTime(new Date()) - getUnixTime(parseISO(startsAt)); const durationTotal = @@ -18,7 +18,9 @@ const calculatePercent = (startsAt, endsAt) => { return Math.floor((durationDone / durationTotal) * 100); }; -const SilenceProgress = ({ silence }) => { +const SilenceProgress: FC<{ + silence: APISilenceT; +}> = ({ silence }) => { const [progress, setProgress] = useState( calculatePercent(silence.startsAt, silence.endsAt) ); @@ -50,16 +52,13 @@ const SilenceProgress = ({ silence }) => { role="progressbar" style={{ width: progress + "%" }} aria-valuenow={progress} - aria-valuemin="0" - aria-valuemax="100" + aria-valuemin={0} + aria-valuemax={100} />
) ); }; -SilenceProgress.propTypes = { - silence: APISilence.isRequired, -}; export { SilenceProgress }; diff --git a/ui/src/Components/ManagedSilence/index.js b/ui/src/Components/ManagedSilence/index.tsx similarity index 64% rename from ui/src/Components/ManagedSilence/index.js rename to ui/src/Components/ManagedSilence/index.tsx index 7c9a389fe..81aab48cc 100644 --- a/ui/src/Components/ManagedSilence/index.js +++ b/ui/src/Components/ManagedSilence/index.tsx @@ -1,27 +1,39 @@ -import React, { useEffect, useState } from "react"; -import PropTypes from "prop-types"; +import React, { FC, useEffect, useState } from "react"; -import { APISilence } from "Models/API"; +import { APISilenceT, APIAlertmanagerUpstreamT } from "Models/APITypes"; import { AlertStore } from "Stores/AlertStore"; -import { SilenceFormStore, SilenceTabNames } from "Stores/SilenceFormStore"; +import { SilenceFormStore } from "Stores/SilenceFormStore"; import { SilenceComment } from "./SilenceComment"; import { SilenceDetails } from "./SilenceDetails"; -const GetAlertmanager = (alertStore, cluster) => +const GetAlertmanager = ( + alertStore: AlertStore, + cluster: string +): APIAlertmanagerUpstreamT => alertStore.data.readWriteAlertmanagers .filter((u) => u.cluster === cluster) .slice(0, 1)[0]; -const ManagedSilence = ({ +const ManagedSilence: FC<{ + cluster: string; + alertCount: number; + alertCountAlwaysVisible: boolean; + silence: APISilenceT; + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; + isOpen?: boolean; + onDidUpdate?: () => void; + isNested?: boolean; +}> = ({ cluster, alertCount, alertCountAlwaysVisible, silence, alertStore, silenceFormStore, - isOpen, + isOpen = false, onDidUpdate, - isNested, + isNested = false, }) => { useEffect(() => { if (onDidUpdate) onDidUpdate(); @@ -34,7 +46,7 @@ const ManagedSilence = ({ silenceFormStore.data.fillFormFromSilence(alertmanager, silence); silenceFormStore.data.resetProgress(); - silenceFormStore.tab.setTab(SilenceTabNames.Editor); + silenceFormStore.tab.setTab("editor"); silenceFormStore.toggle.show(); }; @@ -66,21 +78,5 @@ const ManagedSilence = ({
); }; -ManagedSilence.propTypes = { - cluster: PropTypes.string.isRequired, - alertCount: PropTypes.number.isRequired, - alertCountAlwaysVisible: PropTypes.bool.isRequired, - silence: APISilence.isRequired, - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - onDidUpdate: PropTypes.func, - onDeleteModalClose: PropTypes.func, - isOpen: PropTypes.bool, - isNested: PropTypes.bool, -}; -ManagedSilence.defaultProps = { - isOpen: false, - isNested: false, -}; export { ManagedSilence, GetAlertmanager }; diff --git a/ui/src/Components/Modal/index.tsx b/ui/src/Components/Modal/index.tsx index 330fe4ea4..e160f966a 100644 --- a/ui/src/Components/Modal/index.tsx +++ b/ui/src/Components/Modal/index.tsx @@ -54,6 +54,7 @@ const Modal: FC<{ isOpen: boolean; isUpper?: boolean; toggleOpen: () => void; + onExited?: () => void; }> = ({ size = "lg", isOpen, diff --git a/ui/src/Components/NavBar/FetchIndicator/index.js b/ui/src/Components/NavBar/FetchIndicator/index.tsx similarity index 69% rename from ui/src/Components/NavBar/FetchIndicator/index.js rename to ui/src/Components/NavBar/FetchIndicator/index.tsx index 65c33a831..5fde4e743 100644 --- a/ui/src/Components/NavBar/FetchIndicator/index.js +++ b/ui/src/Components/NavBar/FetchIndicator/index.tsx @@ -1,15 +1,20 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC } from "react"; import { useObserver } from "mobx-react-lite"; +import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; 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 { AlertStore, AlertStoreStatuses } from "Stores/AlertStore"; -const FetchIcon = ({ icon, color, visible, spin }) => ( +const FetchIcon: FC<{ + icon: IconDefinition; + color?: string; + visible?: boolean; + spin?: boolean; +}> = ({ icon, color = "muted", visible = true, spin = false }) => ( ( spin={spin} /> ); -FetchIcon.propTypes = { - icon: FontAwesomeIcon.propTypes.icon.isRequired, - color: PropTypes.string, - visible: PropTypes.bool, - spin: PropTypes.bool, -}; -FetchIcon.defaultProps = { - color: "muted", - visible: true, - spin: false, -}; -const FetchIndicator = ({ alertStore }) => { +const FetchIndicator: FC<{ + alertStore: AlertStore; +}> = ({ alertStore }) => { return useObserver(() => alertStore.status.paused ? ( @@ -49,8 +45,5 @@ const FetchIndicator = ({ alertStore }) => { ) ); }; -FetchIndicator.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, -}; export { FetchIndicator }; diff --git a/ui/src/Components/NavBar/FilterInput/Constants.js b/ui/src/Components/NavBar/FilterInput/Constants.ts similarity index 100% rename from ui/src/Components/NavBar/FilterInput/Constants.js rename to ui/src/Components/NavBar/FilterInput/Constants.ts diff --git a/ui/src/Components/NavBar/FilterInput/History.js b/ui/src/Components/NavBar/FilterInput/History.tsx similarity index 86% rename from ui/src/Components/NavBar/FilterInput/History.js rename to ui/src/Components/NavBar/FilterInput/History.tsx index 419c020d7..9b8e81c20 100644 --- a/ui/src/Components/NavBar/FilterInput/History.js +++ b/ui/src/Components/NavBar/FilterInput/History.tsx @@ -1,11 +1,20 @@ -import React, { useEffect, useRef, useState, useCallback } from "react"; -import PropTypes from "prop-types"; +import React, { + FC, + Ref, + CSSProperties, + useEffect, + useRef, + useState, + useCallback, + ReactNode, +} from "react"; import { useObserver } from "mobx-react-lite"; import { localStored } from "mobx-stored"; import { Manager, Reference, Popper } from "react-popper"; +import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faHistory } from "@fortawesome/free-solid-svg-icons/faHistory"; import { faCaretDown } from "@fortawesome/free-solid-svg-icons/faCaretDown"; @@ -13,7 +22,7 @@ import { faSave } from "@fortawesome/free-regular-svg-icons/faSave"; import { faUndoAlt } from "@fortawesome/free-solid-svg-icons/faUndoAlt"; import { faTrash } from "@fortawesome/free-solid-svg-icons/faTrash"; -import { AlertStore } from "Stores/AlertStore"; +import { AlertStore, FilterT } from "Stores/AlertStore"; import { Settings } from "Stores/Settings"; import { IsMobile } from "Common/Device"; import { CommonPopperModifiers } from "Common/Popper"; @@ -21,9 +30,16 @@ import { DropdownSlide } from "Components/Animations/DropdownSlide"; import { HistoryLabel } from "Components/Labels/HistoryLabel"; import { useOnClickOutside } from "Hooks/useOnClickOutside"; +interface ReduceFilterT { + raw: string; + name: string; + matcher: string; + value: string; +} + // takes a filter object out of alertStore.history.values and creates a new // object with only those keys that will be stored in history -function ReduceFilter(filter) { +function ReduceFilter(filter: FilterT): ReduceFilterT { return { raw: filter.raw, name: filter.name, @@ -32,7 +48,13 @@ function ReduceFilter(filter) { }; } -const ActionButton = ({ color, icon, title, action, afterClick }) => ( +const ActionButton: FC<{ + color: string; + icon: IconDefinition; + title: ReactNode; + action: () => void; + afterClick: () => void; +}> = ({ color, icon, title, action, afterClick }) => ( ); -ActionButton.propTypes = { - color: PropTypes.string.isRequired, - title: PropTypes.node.isRequired, - icon: FontAwesomeIcon.propTypes.icon.isRequired, - action: PropTypes.func.isRequired, - afterClick: PropTypes.func.isRequired, -}; -const HistoryMenu = ({ +const HistoryMenu: FC<{ + popperPlacement?: string; + popperRef?: Ref; + popperStyle?: CSSProperties; + filters: ReduceFilterT[][]; + alertStore: AlertStore; + settingsStore: Settings; + afterClick: () => void; + onClear: () => void; +}> = ({ popperPlacement, popperRef, popperStyle, @@ -132,20 +156,17 @@ const HistoryMenu = ({ ); }; -HistoryMenu.propTypes = { - popperPlacement: PropTypes.string, - popperRef: PropTypes.func, - popperStyle: PropTypes.object, - filters: PropTypes.array.isRequired, - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - settingsStore: PropTypes.instanceOf(Settings).isRequired, - afterClick: PropTypes.func.isRequired, - onClear: PropTypes.func.isRequired, -}; -const History = ({ alertStore, settingsStore }) => { +interface historyStorageT { + filters: ReduceFilterT[][]; +} + +const History: FC<{ + alertStore: AlertStore; + settingsStore: Settings; +}> = ({ alertStore, settingsStore }) => { // this will be dumped to local storage via mobx-stored - const history = localStored( + const history: historyStorageT = localStored( "history.filters", { filters: [], @@ -190,7 +211,7 @@ const History = ({ alertStore, settingsStore }) => { } }); - const ref = useRef(null); + const ref = useRef(null as null | HTMLElement); useOnClickOutside(ref, hide, isVisible); return useObserver(() => ( @@ -245,9 +266,5 @@ const History = ({ alertStore, settingsStore }) => { )); }; -History.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - settingsStore: PropTypes.instanceOf(Settings).isRequired, -}; export { History, HistoryMenu, ReduceFilter }; diff --git a/ui/src/Components/NavBar/FilterInput/index.js b/ui/src/Components/NavBar/FilterInput/index.tsx similarity index 78% rename from ui/src/Components/NavBar/FilterInput/index.js rename to ui/src/Components/NavBar/FilterInput/index.tsx index 3cb0c72a7..dab401a63 100644 --- a/ui/src/Components/NavBar/FilterInput/index.js +++ b/ui/src/Components/NavBar/FilterInput/index.tsx @@ -1,5 +1,4 @@ -import React, { useEffect, useState, useRef, useCallback } from "react"; -import PropTypes from "prop-types"; +import React, { FC, useEffect, useState, useRef, useCallback } from "react"; import { useObserver } from "mobx-react-lite"; @@ -19,9 +18,12 @@ import { FilterInputLabel } from "Components/Labels/FilterInputLabel"; import { AutosuggestTheme } from "./Constants"; import { History } from "./History"; -const FilterInput = ({ alertStore, settingsStore }) => { - const autosuggestRef = useRef(); - const inputRef = useRef(); +const FilterInput: FC<{ + alertStore: AlertStore; + settingsStore: Settings; +}> = ({ alertStore, settingsStore }) => { + const autosuggestRef = useRef(null as null | Autosuggest); + const inputRef = useRef(null as null | HTMLElement); const formRef = useRef(null); const [suggestions, setSuggestions] = useState([]); @@ -31,14 +33,14 @@ const FilterInput = ({ alertStore, settingsStore }) => { const onSuggestionsClearRequested = useCallback(() => setSuggestions([]), []); const onSuggestionSelected = useCallback( - (event, { suggestion }) => { + (_, { suggestion }) => { setValue(""); alertStore.filters.addFilter(suggestion); }, [alertStore.filters] ); - const onChange = useCallback((event, { newValue }) => setValue(newValue), []); + const onChange = useCallback((_, { newValue }) => setValue(newValue), []); const onSubmit = useCallback( (event) => { @@ -52,9 +54,11 @@ const FilterInput = ({ alertStore, settingsStore }) => { ); useEffect(() => { - inputRef.current = autosuggestRef.current.input.parentElement; + inputRef.current = ((autosuggestRef.current as Autosuggest) + .input as HTMLInputElement).parentElement; if (!IsMobile()) { - autosuggestRef.current.input.focus(); + ((autosuggestRef.current as Autosuggest) + .input as HTMLInputElement).focus(); } }, []); @@ -90,16 +94,20 @@ const FilterInput = ({ alertStore, settingsStore }) => { } }, [response, error, isLoading, onSuggestionsClearRequested]); - const onInputClick = (event) => { + const onInputClick = (className: string) => { if ( - typeof event.target.className === "string" && - event.target.className.split(" ").includes("form-control") + typeof className === "string" && + className.split(" ").includes("form-control") ) { - autosuggestRef.current.input.focus(); + ((autosuggestRef.current as Autosuggest) + .input as HTMLInputElement).focus(); } }; - const renderSuggestion = (suggestion, { query, isHighlighted }) => { + const renderSuggestion = ( + suggestion: string, + { query }: { query: string } + ) => { return ( { ); }; - const renderInputComponent = (inputProps) => { - const { value } = inputProps; + const renderInputComponent: FC<{ value: string }> = ({ + value, + ...inputProps + }) => { return ( ); @@ -140,7 +151,9 @@ const FilterInput = ({ alertStore, settingsStore }) => {
+ onInputClick((event.target as HTMLDivElement).className) + } onFocus={() => setIsFocused(true)} onBlur={onBlur} > @@ -158,7 +171,7 @@ const FilterInput = ({ alertStore, settingsStore }) => { onSuggestionsClearRequested={onSuggestionsClearRequested} onSuggestionSelected={onSuggestionSelected} shouldRenderSuggestions={(value) => - value && value.trim().length > 1 + value ? value.trim().length > 1 : false } getSuggestionValue={(suggestion) => suggestion} renderSuggestion={renderSuggestion} @@ -181,9 +194,5 @@ const FilterInput = ({ alertStore, settingsStore }) => { )); }; -FilterInput.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - settingsStore: PropTypes.instanceOf(Settings).isRequired, -}; export { FilterInput }; diff --git a/ui/src/Components/NavBar/index.js b/ui/src/Components/NavBar/index.tsx similarity index 87% rename from ui/src/Components/NavBar/index.js rename to ui/src/Components/NavBar/index.tsx index c12cadff2..fabfd6b47 100644 --- a/ui/src/Components/NavBar/index.js +++ b/ui/src/Components/NavBar/index.tsx @@ -1,5 +1,4 @@ -import React, { useState, useRef, useEffect, useCallback } from "react"; -import PropTypes from "prop-types"; +import React, { FC, useState, useRef, useEffect, useCallback } from "react"; import { reaction } from "mobx"; import { useObserver } from "mobx-react-lite"; @@ -24,8 +23,13 @@ import { FilterInput } from "./FilterInput"; const DesktopIdleTimeout = 1000 * 60 * 3; const MobileIdleTimeout = 1000 * 12; -const NavBar = ({ alertStore, settingsStore, silenceFormStore, fixedTop }) => { - const idleTimer = useRef(null); +const NavBar: FC<{ + alertStore: AlertStore; + settingsStore: Settings; + silenceFormStore: SilenceFormStore; + fixedTop?: boolean; +}> = ({ alertStore, settingsStore, silenceFormStore, fixedTop = true }) => { + const idleTimer = useRef(null as null | IdleTimer); const [isIdle, setIsIdle] = useState(false); const [containerClass, setContainerClass] = useState("visible"); @@ -51,16 +55,16 @@ const NavBar = ({ alertStore, settingsStore, silenceFormStore, fixedTop }) => { }, []); useEffect(() => { - let timer; + let timer: number; if (isIdle) { - timer = setTimeout( + timer = window.setTimeout( () => updateBodyPaddingTop(true), context.animations.duration ); } else { updateBodyPaddingTop(false); } - return () => clearTimeout(timer); + return () => window.clearTimeout(timer); }, [height, updateBodyPaddingTop, isIdle, context.animations.duration]); useEffect( @@ -134,14 +138,5 @@ const NavBar = ({ alertStore, settingsStore, silenceFormStore, fixedTop }) => { )); }; -NavBar.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - settingsStore: PropTypes.instanceOf(Settings).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - fixedTop: PropTypes.bool, -}; -NavBar.defaultProps = { - fixedTop: true, -}; export { NavBar, MobileIdleTimeout, DesktopIdleTimeout }; diff --git a/ui/src/Components/SilenceModal/AlertManagerInput/index.js b/ui/src/Components/SilenceModal/AlertManagerInput/index.tsx similarity index 85% rename from ui/src/Components/SilenceModal/AlertManagerInput/index.js rename to ui/src/Components/SilenceModal/AlertManagerInput/index.tsx index e6f3a5bca..d0e987323 100644 --- a/ui/src/Components/SilenceModal/AlertManagerInput/index.js +++ b/ui/src/Components/SilenceModal/AlertManagerInput/index.tsx @@ -1,5 +1,4 @@ -import React, { useEffect } from "react"; -import PropTypes from "prop-types"; +import React, { FC, useEffect } from "react"; import { autorun } from "mobx"; import { useObserver } from "mobx-react-lite"; @@ -11,10 +10,14 @@ import { SilenceFormStore, AlertmanagerClustersToOption, } from "Stores/SilenceFormStore"; +import { MultiValueOptionT } from "Common/Select"; import { ThemeContext } from "Components/Theme"; import { ValidationError } from "Components/ValidationError"; -const AlertManagerInput = ({ alertStore, silenceFormStore }) => { +const AlertManagerInput: FC<{ + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; +}> = ({ alertStore, silenceFormStore }) => { useEffect(() => { if (silenceFormStore.data.alertmanagers.length === 0) { silenceFormStore.data.setAlertmanagers( @@ -68,15 +71,13 @@ const AlertManagerInput = ({ alertStore, silenceFormStore }) => { } isMulti onChange={(newValue) => { - silenceFormStore.data.setAlertmanagers(newValue || []); + silenceFormStore.data.setAlertmanagers( + (newValue as MultiValueOptionT[]) || ([] as MultiValueOptionT[]) + ); }} isDisabled={silenceFormStore.data.silenceID !== null} /> )); }; -AlertManagerInput.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, -}; export { AlertManagerInput }; diff --git a/ui/src/Components/SilenceModal/AuthorInput.js b/ui/src/Components/SilenceModal/AuthorInput.tsx similarity index 57% rename from ui/src/Components/SilenceModal/AuthorInput.js rename to ui/src/Components/SilenceModal/AuthorInput.tsx index a6dd99532..7bf9b97bf 100644 --- a/ui/src/Components/SilenceModal/AuthorInput.js +++ b/ui/src/Components/SilenceModal/AuthorInput.tsx @@ -1,20 +1,20 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC, ChangeEvent } from "react"; +import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faUser } from "@fortawesome/free-solid-svg-icons/faUser"; import { AlertStore } from "Stores/AlertStore"; -const IconInput = ({ - type, - autoComplete, - icon, - placeholder, - value, - onChange, - ...extra -}) => ( +const IconInput: FC<{ + type: string; + autoComplete: string; + icon: IconDefinition; + placeholder: string; + value: string; + onChange?: (event: ChangeEvent) => void; + readOnly?: boolean; +}> = ({ type, autoComplete, icon, placeholder, value, onChange, ...extra }) => (
@@ -33,16 +33,10 @@ const IconInput = ({ />
); -IconInput.propTypes = { - type: PropTypes.string.isRequired, - autoComplete: PropTypes.string.isRequired, - icon: FontAwesomeIcon.propTypes.icon.isRequired, - placeholder: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, - onChange: PropTypes.func, -}; -const AuthenticatedAuthorInput = ({ alertStore }) => ( +const AuthenticatedAuthorInput: FC<{ + alertStore: AlertStore; +}> = ({ alertStore }) => ( ( readOnly={true} /> ); -AuthenticatedAuthorInput.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, -}; export { IconInput, AuthenticatedAuthorInput }; diff --git a/ui/src/Components/SilenceModal/Browser/index.test.js b/ui/src/Components/SilenceModal/Browser/index.test.js index 4e6f7ae44..f4bf0dd6d 100644 --- a/ui/src/Components/SilenceModal/Browser/index.test.js +++ b/ui/src/Components/SilenceModal/Browser/index.test.js @@ -64,8 +64,6 @@ afterEach(() => { global.window.innerWidth = 1024; }); -const MockOnDeleteModalClose = jest.fn(); - const MockSilenceList = (count) => { let silences = []; for (var index = 1; index <= count; index++) { @@ -86,7 +84,6 @@ const MountedBrowser = () => { alertStore={alertStore} silenceFormStore={silenceFormStore} settingsStore={settingsStore} - onDeleteModalClose={MockOnDeleteModalClose} />, { wrappingComponent: ThemeContext.Provider, diff --git a/ui/src/Components/SilenceModal/Browser/index.js b/ui/src/Components/SilenceModal/Browser/index.tsx similarity index 87% rename from ui/src/Components/SilenceModal/Browser/index.js rename to ui/src/Components/SilenceModal/Browser/index.tsx index cd51731de..797f36b8c 100644 --- a/ui/src/Components/SilenceModal/Browser/index.js +++ b/ui/src/Components/SilenceModal/Browser/index.tsx @@ -1,5 +1,4 @@ -import React, { useState, useEffect } from "react"; -import PropTypes from "prop-types"; +import React, { FC, useState, useEffect, ReactNode } from "react"; import { useObserver } from "mobx-react-lite"; @@ -11,17 +10,20 @@ import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclama import { faSortAmountDownAlt } from "@fortawesome/free-solid-svg-icons/faSortAmountDownAlt"; import { faSortAmountUp } from "@fortawesome/free-solid-svg-icons/faSortAmountUp"; +import { APIManagedSilenceT } from "Models/APITypes"; import { AlertStore, FormatBackendURI } from "Stores/AlertStore"; import { SilenceFormStore } from "Stores/SilenceFormStore"; import { Settings } from "Stores/Settings"; -import { useFetchGet } from "Hooks/useFetchGet"; +import { useFetchGet, FetchGetOptionsT } from "Hooks/useFetchGet"; import { useDebounce } from "Hooks/useDebounce"; import { IsMobile } from "Common/Device"; import { ManagedSilence } from "Components/ManagedSilence"; import { PageSelect } from "Components/Pagination"; import { ThemeContext } from "Components/Theme"; -const FetchError = ({ message }) => ( +const FetchError: FC<{ + message: ReactNode; +}> = ({ message }) => (

@@ -29,11 +31,10 @@ const FetchError = ({ message }) => (

{message}

); -FetchError.propTypes = { - message: PropTypes.node.isRequired, -}; -const Placeholder = ({ content }) => { +const Placeholder: FC<{ + content: ReactNode; +}> = ({ content }) => { const context = React.useContext(ThemeContext); return ( @@ -49,16 +50,12 @@ const Placeholder = ({ content }) => { ); }; -Placeholder.propTypes = { - content: PropTypes.node.isRequired, -}; -const Browser = ({ - alertStore, - silenceFormStore, - settingsStore, - onDeleteModalClose, -}) => { +const Browser: FC<{ + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; + settingsStore: Settings; +}> = ({ alertStore, silenceFormStore, settingsStore }) => { const maxPerPage = IsMobile() ? 4 : 6; const [sortReverse, setSortReverse] = useState(false); const [showExpired, setShowExpired] = useState(false); @@ -79,7 +76,7 @@ const Browser = ({ showExpired ? "1" : "0" }&searchTerm=${debouncedSearchTerm}` ), - { deps: [currentTime] } + { deps: [currentTime] } as FetchGetOptionsT ); useEffect(() => { @@ -148,7 +145,7 @@ const Browser = ({ ) : ( - {response + {(response as APIManagedSilenceT[]) .slice((activePage - 1) * maxPerPage, activePage * maxPerPage) .map((silence) => ( )); }; -Browser.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - settingsStore: PropTypes.instanceOf(Settings).isRequired, - onDeleteModalClose: PropTypes.func.isRequired, -}; export { Browser }; diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/Duration.js b/ui/src/Components/SilenceModal/DateTimeSelect/Duration.tsx similarity index 73% rename from ui/src/Components/SilenceModal/DateTimeSelect/Duration.js rename to ui/src/Components/SilenceModal/DateTimeSelect/Duration.tsx index 8f2b4943a..505372699 100644 --- a/ui/src/Components/SilenceModal/DateTimeSelect/Duration.js +++ b/ui/src/Components/SilenceModal/DateTimeSelect/Duration.tsx @@ -1,5 +1,4 @@ -import React, { useEffect, useRef } from "react"; -import PropTypes from "prop-types"; +import React, { FC, useEffect, useRef } from "react"; import { observer } from "mobx-react-lite"; @@ -7,13 +6,18 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faAngleUp } from "@fortawesome/free-solid-svg-icons/faAngleUp"; import { faAngleDown } from "@fortawesome/free-solid-svg-icons/faAngleDown"; -const Duration = observer(({ value, label, onInc, onDec }) => { - const rootRef = useRef(null); +const Duration: FC<{ + value: number; + label: string; + onInc: () => void; + onDec: () => void; +}> = observer(({ value, label, onInc, onDec }) => { + const rootRef = useRef(null as null | HTMLDivElement); useEffect(() => { - const cancelWheel = (event) => event.preventDefault(); + const cancelWheel = (event: any) => event.preventDefault(); - const elem = rootRef.current; + const elem = rootRef.current as HTMLDivElement; elem.addEventListener("wheel", cancelWheel, { passive: false }); @@ -22,8 +26,8 @@ const Duration = observer(({ value, label, onInc, onDec }) => { }; }, []); - const onWheel = (event) => { - if (event.deltaY < 0) { + const onWheel = (deltaY: number) => { + if (deltaY < 0) { onInc(); } else { onDec(); @@ -31,7 +35,11 @@ const Duration = observer(({ value, label, onInc, onDec }) => { }; return ( -
+
onWheel(event.deltaY)} + className="components-duration" + > @@ -71,11 +79,5 @@ const Duration = observer(({ value, label, onInc, onDec }) => { ); }); -Duration.propTypes = { - value: PropTypes.number.isRequired, - label: PropTypes.string.isRequired, - onInc: PropTypes.func.isRequired, - onDec: PropTypes.func.isRequired, -}; export { Duration }; diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.js b/ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.tsx similarity index 76% rename from ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.js rename to ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.tsx index 79d167f20..1f2adb015 100644 --- a/ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.js +++ b/ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.tsx @@ -1,13 +1,18 @@ -import React, { useEffect, useRef } from "react"; -import PropTypes from "prop-types"; +import React, { FC, useEffect, useRef, MouseEvent, WheelEvent } from "react"; import { observer } from "mobx-react-lite"; +import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faAngleUp } from "@fortawesome/free-solid-svg-icons/faAngleUp"; import { faAngleDown } from "@fortawesome/free-solid-svg-icons/faAngleDown"; -const IconTd = ({ icon, onClick, onWheel, className }) => ( +const IconTd: FC<{ + icon: IconDefinition; + onClick: (event: MouseEvent) => void; + onWheel: (event: WheelEvent) => void; + className: string; +}> = ({ icon, onClick, onWheel, className }) => ( ); -IconTd.propTypes = { - icon: FontAwesomeIcon.propTypes.icon.isRequired, - onClick: PropTypes.func.isRequired, - onWheel: PropTypes.func.isRequired, - className: PropTypes.string.isRequired, -}; -const HourMinute = observer( +const HourMinute: FC<{ + dateValue: Date; + onHourInc: () => void; + onHourDec: () => void; + onMinuteInc: () => void; + onMinuteDec: () => void; +}> = observer( ({ dateValue, onHourInc, onHourDec, onMinuteInc, onMinuteDec }) => { - const rootRef = useRef(null); + const rootRef = useRef(null as null | HTMLDivElement); useEffect(() => { - const cancelWheel = (event) => event.preventDefault(); + const cancelWheel = (event: any) => event.preventDefault(); - const elem = rootRef.current; + const elem = rootRef.current as HTMLDivElement; elem.addEventListener("wheel", cancelWheel, { passive: false }); @@ -41,7 +46,7 @@ const HourMinute = observer( }; }, []); - const onHourWheel = (event) => { + const onHourWheel = (event: WheelEvent) => { if (event.deltaY < 0) { onHourInc(); } else { @@ -49,7 +54,7 @@ const HourMinute = observer( } }; - const onMinuteWheel = (event) => { + const onMinuteWheel = (event: WheelEvent) => { if (event.deltaY < 0) { onMinuteInc(); } else { @@ -114,12 +119,5 @@ const HourMinute = observer( ); } ); -HourMinute.propTypes = { - dateValue: PropTypes.instanceOf(Date).isRequired, - onHourInc: PropTypes.func.isRequired, - onHourDec: PropTypes.func.isRequired, - onMinuteInc: PropTypes.func.isRequired, - onMinuteDec: PropTypes.func.isRequired, -}; export { HourMinute }; diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/index.js b/ui/src/Components/SilenceModal/DateTimeSelect/index.tsx similarity index 80% rename from ui/src/Components/SilenceModal/DateTimeSelect/index.js rename to ui/src/Components/SilenceModal/DateTimeSelect/index.tsx index 92466857c..80c7fed8e 100644 --- a/ui/src/Components/SilenceModal/DateTimeSelect/index.js +++ b/ui/src/Components/SilenceModal/DateTimeSelect/index.tsx @@ -1,5 +1,4 @@ -import React, { useEffect, useState, useCallback } from "react"; -import PropTypes from "prop-types"; +import React, { FC, useEffect, useState, useCallback, ReactNode } from "react"; import { useObserver } from "mobx-react-lite"; @@ -15,13 +14,17 @@ import { SilenceFormStore } from "Stores/SilenceFormStore"; import { Duration } from "./Duration"; import { HourMinute } from "./HourMinute"; -const nowZeroSeconds = () => { +const nowZeroSeconds = (): Date => { const now = new Date(); now.setSeconds(0); return now; }; -const OffsetBadge = ({ startDate, endDate, prefixLabel }) => { +const OffsetBadge: FC<{ + startDate: Date; + endDate: Date; + prefixLabel: string; +}> = ({ startDate, endDate, prefixLabel }) => { const days = differenceInDays(endDate, startDate); const hours = differenceInHours(endDate, startDate) % 24; const minutes = differenceInMinutes(endDate, startDate) % 60; @@ -35,13 +38,12 @@ const OffsetBadge = ({ startDate, endDate, prefixLabel }) => { ); }; -OffsetBadge.propTypes = { - startDate: PropTypes.instanceOf(Date).isRequired, - endDate: PropTypes.instanceOf(Date).isRequired, - prefixLabel: PropTypes.string.isRequired, -}; -const Tab = ({ title, active, onClick }) => ( +const Tab: FC<{ + title: ReactNode; + active: boolean; + onClick: () => void; +}> = ({ title, active, onClick }) => (
  • (
  • ); -Tab.propTypes = { - title: PropTypes.node.isRequired, - active: PropTypes.bool, - onClick: PropTypes.func.isRequired, -}; -const TabNames = Object.freeze({ - Start: "start", - End: "end", - Duration: "duration", -}); - -const TabContentStart = ({ silenceFormStore }) => { +const TabContentStart: FC<{ + silenceFormStore: SilenceFormStore; +}> = ({ silenceFormStore }) => { return useObserver(() => (
    @@ -74,7 +67,7 @@ const TabContentStart = ({ silenceFormStore }) => { before: nowZeroSeconds(), }} todayButton="Today" - onDayClick={(val, ...mod) => { + onDayClick={(val) => { const startsAt = new Date(val); startsAt.setHours(silenceFormStore.data.startsAt.getHours()); startsAt.setMinutes(silenceFormStore.data.startsAt.getMinutes()); @@ -103,7 +96,9 @@ const TabContentStart = ({ silenceFormStore }) => { )); }; -const TabContentEnd = ({ silenceFormStore }) => { +const TabContentEnd: FC<{ silenceFormStore: SilenceFormStore }> = ({ + silenceFormStore, +}) => { return useObserver(() => (
    @@ -144,7 +139,7 @@ const TabContentEnd = ({ silenceFormStore }) => { }; // calculate value for duration increase button using a goal step -const CalculateChangeValueUp = (currentValue, step) => { +const CalculateChangeValueUp = (currentValue: number, step: number): number => { // if current value is less than step (but >0) then use 1 if (currentValue > 0 && currentValue < step) { return 1; @@ -154,7 +149,10 @@ const CalculateChangeValueUp = (currentValue, step) => { }; // calculate value for duration decrease button using a goal step -const CalculateChangeValueDown = (currentValue, step) => { +const CalculateChangeValueDown = ( + currentValue: number, + step: number +): number => { // if current value is less than step (but >0) then use 1 if (currentValue > 0 && currentValue < step) { return 1; @@ -163,7 +161,9 @@ const CalculateChangeValueDown = (currentValue, step) => { return currentValue % step || step; }; -const TabContentDuration = ({ silenceFormStore }) => { +const TabContentDuration: FC<{ + silenceFormStore: SilenceFormStore; +}> = ({ silenceFormStore }) => { return useObserver(() => (
    {
    )); }; -TabContentDuration.propTypes = { - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, -}; -const DateTimeSelect = ({ silenceFormStore, openTab }) => { +const DateTimeSelect: FC<{ + silenceFormStore: SilenceFormStore; + openTab?: "start" | "end" | "duration"; +}> = ({ silenceFormStore, openTab = "duration" }) => { const [currentTab, setCurrentTab] = useState(openTab); const [timeNow, setTimeNow] = useState(nowZeroSeconds()); @@ -231,8 +231,8 @@ const DateTimeSelect = ({ silenceFormStore, openTab }) => { /> } - active={currentTab === TabNames.Start} - onClick={() => setCurrentTab(TabNames.Start)} + active={currentTab === "start"} + onClick={() => setCurrentTab("start")} /> { /> } - active={currentTab === TabNames.End} - onClick={() => setCurrentTab(TabNames.End)} + active={currentTab === "end"} + onClick={() => setCurrentTab("end")} /> { /> } - active={currentTab === TabNames.Duration} - onClick={() => setCurrentTab(TabNames.Duration)} + active={currentTab === "duration"} + onClick={() => setCurrentTab("duration")} />
    - {currentTab === TabNames.Duration ? ( + {currentTab === "duration" ? ( ) : null} - {currentTab === TabNames.Start ? ( + {currentTab === "start" ? ( ) : null} - {currentTab === TabNames.End ? ( + {currentTab === "end" ? ( ) : null}
    )); }; -DateTimeSelect.propTypes = { - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - openTab: PropTypes.oneOf(Object.values(TabNames)), -}; -DateTimeSelect.defaultProps = { - openTab: TabNames.Duration, -}; -export { - DateTimeSelect, - TabContentStart, - TabContentEnd, - TabContentDuration, - TabNames, -}; +export { DateTimeSelect, TabContentStart, TabContentEnd, TabContentDuration }; diff --git a/ui/src/Components/SilenceModal/Matchers.js b/ui/src/Components/SilenceModal/Matchers.ts similarity index 63% rename from ui/src/Components/SilenceModal/Matchers.js rename to ui/src/Components/SilenceModal/Matchers.ts index 8b927655a..5ae2eb3a5 100644 --- a/ui/src/Components/SilenceModal/Matchers.js +++ b/ui/src/Components/SilenceModal/Matchers.ts @@ -1,6 +1,8 @@ import { FormatQuery, QueryOperators, StaticLabels } from "Common/Query"; +import { MultiValueOptionT } from "Common/Select"; +import { MatcherT } from "Stores/SilenceFormStore"; -const MatcherToFilter = (matcher) => { +const MatcherToFilter = (matcher: MatcherT): string => { const operator = matcher.isRegex ? QueryOperators.Regex : QueryOperators.Equal; @@ -15,8 +17,10 @@ const MatcherToFilter = (matcher) => { ); }; -const AlertManagersToFilter = (alertmanagers) => { - let amNames = [].concat(...alertmanagers.map((am) => am.value)); +const AlertManagersToFilter = (alertmanagers: MultiValueOptionT[]): string => { + let amNames: string[] = ([] as string[]).concat( + ...alertmanagers.map((am) => am.value) + ); return FormatQuery( StaticLabels.AlertManager, QueryOperators.Regex, diff --git a/ui/src/Components/SilenceModal/PayloadPreview/index.js b/ui/src/Components/SilenceModal/PayloadPreview/index.tsx similarity index 65% rename from ui/src/Components/SilenceModal/PayloadPreview/index.js rename to ui/src/Components/SilenceModal/PayloadPreview/index.tsx index 3efe7bf42..77575997c 100644 --- a/ui/src/Components/SilenceModal/PayloadPreview/index.js +++ b/ui/src/Components/SilenceModal/PayloadPreview/index.tsx @@ -1,5 +1,4 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC } from "react"; import { useObserver } from "mobx-react-lite"; @@ -8,7 +7,9 @@ import * as theme from "react-json-pretty/dist/monikai"; import { SilenceFormStore } from "Stores/SilenceFormStore"; -const PayloadPreview = ({ silenceFormStore }) => { +const PayloadPreview: FC<{ + silenceFormStore: SilenceFormStore; +}> = ({ silenceFormStore }) => { return useObserver(() => ( { /> )); }; -PayloadPreview.propTypes = { - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, -}; export { PayloadPreview }; diff --git a/ui/src/Components/SilenceModal/SilenceForm.js b/ui/src/Components/SilenceModal/SilenceForm.tsx similarity index 90% rename from ui/src/Components/SilenceModal/SilenceForm.js rename to ui/src/Components/SilenceModal/SilenceForm.tsx index ac8e7358e..e945ef501 100644 --- a/ui/src/Components/SilenceModal/SilenceForm.js +++ b/ui/src/Components/SilenceModal/SilenceForm.tsx @@ -1,5 +1,4 @@ -import React, { useEffect, useState } from "react"; -import PropTypes from "prop-types"; +import React, { FC, useEffect, useState, MouseEvent, FormEvent } from "react"; import { useObserver } from "mobx-react-lite"; @@ -22,6 +21,7 @@ import { SilenceFormStage, NewEmptyMatcher, NewClusterRequest, + ClusterRequestT, } from "Stores/SilenceFormStore"; import { Settings } from "Stores/Settings"; import { StringToOption } from "Common/Select"; @@ -35,7 +35,9 @@ import { DateTimeSelect } from "./DateTimeSelect"; import { PayloadPreview } from "./PayloadPreview"; import { IconInput, AuthenticatedAuthorInput } from "./AuthorInput"; -const ShareButton = ({ silenceFormStore }) => { +const ShareButton: FC<{ + silenceFormStore: SilenceFormStore; +}> = ({ silenceFormStore }) => { const [clickCount, setClickCount] = useState(0); const baseURL = [ @@ -81,12 +83,12 @@ const ShareButton = ({ silenceFormStore }) => { )); }; -const SilenceForm = ({ - alertStore, - silenceFormStore, - settingsStore, - previewOpen, -}) => { +const SilenceForm: FC<{ + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; + settingsStore: Settings; + previewOpen: boolean; +}> = ({ alertStore, silenceFormStore, settingsStore, previewOpen }) => { const [showPreview, setShowPreview] = useState(previewOpen); useEffect(() => { @@ -146,23 +148,23 @@ const SilenceForm = ({ } }, []); // eslint-disable-line react-hooks/exhaustive-deps - const addMore = (event) => { + const addMore = (event: MouseEvent) => { event.preventDefault(); silenceFormStore.data.addEmptyMatcher(); }; - const onAuthorChange = (event) => { - silenceFormStore.data.author = event.target.value; + const onAuthorChange = (author: string) => { + silenceFormStore.data.author = author; }; - const onCommentChange = (event) => { - silenceFormStore.data.comment = event.target.value; + const onCommentChange = (comment: string) => { + silenceFormStore.data.comment = comment; }; - const handleSubmit = (event) => { + const handleSubmit = (event: FormEvent) => { event.preventDefault(); - let rbc = {}; + let rbc: { [label: string]: ClusterRequestT } = {}; silenceFormStore.data.alertmanagers.forEach((am) => { rbc[am.label] = NewClusterRequest(am.label, am.value); }); @@ -217,7 +219,7 @@ const SilenceForm = ({ placeholder="Author" icon={faUser} value={silenceFormStore.data.author} - onChange={onAuthorChange} + onChange={(event) => onAuthorChange(event.target.value)} /> )} @@ -227,7 +229,7 @@ const SilenceForm = ({ placeholder="Comment" icon={faCommentDots} value={silenceFormStore.data.comment} - onChange={onCommentChange} + onChange={(event) => onCommentChange(event.target.value)} />
    )); }; -SilenceForm.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - settingsStore: PropTypes.instanceOf(Settings).isRequired, - previewOpen: PropTypes.bool.isRequired, -}; export { SilenceForm }; diff --git a/ui/src/Components/SilenceModal/SilenceMatch/LabelNameInput.js b/ui/src/Components/SilenceModal/SilenceMatch/LabelNameInput.tsx similarity index 54% rename from ui/src/Components/SilenceModal/SilenceMatch/LabelNameInput.js rename to ui/src/Components/SilenceModal/SilenceMatch/LabelNameInput.tsx index d9aba389d..9d3bd37b3 100644 --- a/ui/src/Components/SilenceModal/SilenceMatch/LabelNameInput.js +++ b/ui/src/Components/SilenceModal/SilenceMatch/LabelNameInput.tsx @@ -1,16 +1,18 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC } from "react"; import Creatable from "react-select/creatable"; -import { SilenceFormMatcher } from "Models/SilenceForm"; import { FormatBackendURI } from "Stores/AlertStore"; +import { MatcherWithIDT } from "Stores/SilenceFormStore"; import { useFetchGet } from "Hooks/useFetchGet"; import { ValidationError } from "Components/ValidationError"; import { ThemeContext } from "Components/Theme"; -import { NewLabelName } from "Common/Select"; +import { NewLabelName, OptionT, StringToOption } from "Common/Select"; -const LabelNameInput = ({ matcher, isValid }) => { +const LabelNameInput: FC<{ + matcher: MatcherWithIDT; + isValid: boolean; +}> = ({ matcher, isValid }) => { const { response } = useFetchGet(FormatBackendURI(`labelNames.json`)); const context = React.useContext(ThemeContext); @@ -21,28 +23,17 @@ const LabelNameInput = ({ matcher, isValid }) => { classNamePrefix="react-select" instanceId={`silence-input-label-name-${matcher.id}`} formatCreateLabel={NewLabelName} - defaultValue={ - matcher.name ? { label: matcher.name, value: matcher.name } : null - } + defaultValue={matcher.name ? StringToOption(matcher.name) : null} options={ - response - ? response.map((value) => ({ - label: value, - value: value, - })) - : [] + response ? response.map((value: string) => StringToOption(value)) : [] } placeholder={isValid ? "Label name" : } - onChange={({ value }) => { - matcher.name = value; + onChange={(option) => { + matcher.name = (option as OptionT).value; }} hideSelectedOptions /> ); }; -LabelNameInput.propTypes = { - matcher: SilenceFormMatcher.isRequired, - isValid: PropTypes.bool.isRequired, -}; export { LabelNameInput }; diff --git a/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.js b/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.tsx similarity index 52% rename from ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.js rename to ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.tsx index f512ddf8d..8e84df08d 100644 --- a/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.js +++ b/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.tsx @@ -1,23 +1,28 @@ -import React, { useEffect } from "react"; -import PropTypes from "prop-types"; +import React, { FC, useEffect, ReactNode, ComponentType } from "react"; import { observer } from "mobx-react-lite"; -import { components } from "react-select"; +import { + components, + PlaceholderProps, + ValueContainerProps, +} from "react-select"; import Creatable from "react-select/creatable"; import { FormatBackendURI } from "Stores/AlertStore"; -import { SilenceFormStore } from "Stores/SilenceFormStore"; -import { SilenceFormMatcher } from "Models/SilenceForm"; +import { SilenceFormStore, MatcherWithIDT } from "Stores/SilenceFormStore"; import { useFetchGet } from "Hooks/useFetchGet"; import { hashObject } from "Common/Hash"; -import { NewLabelValue } from "Common/Select"; +import { NewLabelValue, OptionT, StringToOption } from "Common/Select"; import { ValidationError } from "Components/ValidationError"; import { ThemeContext } from "Components/Theme"; import { MatchCounter } from "./MatchCounter"; -const GenerateHashFromMatchers = (silenceFormStore, matcher) => +const GenerateHashFromMatchers = ( + silenceFormStore: SilenceFormStore, + matcher: MatcherWithIDT +): string => hashObject({ alertmanagers: silenceFormStore.data.alertmanagers, matcher: { @@ -27,31 +32,42 @@ const GenerateHashFromMatchers = (silenceFormStore, matcher) => }, }); -const Placeholder = (props) => { +const Placeholder: FC<{}> = (props) => { return (
    - + )} />
    ); }; -const ValueContainer = ({ children, ...props }) => ( - - {props.selectProps.matcher.values.length > 0 ? ( +const ValueContainer: FC<{ + selectProps: { + silenceFormStore: SilenceFormStore; + matcher: MatcherWithIDT; + }; + props: ValueContainerProps; + children: ReactNode; +}> = ({ children, selectProps, ...props }) => ( + + {selectProps.matcher.values.length > 0 ? ( ) : null} {children} ); -const LabelValueInput = observer(({ silenceFormStore, matcher, isValid }) => { +const LabelValueInput: FC<{ + silenceFormStore: SilenceFormStore; + matcher: MatcherWithIDT; + isValid: boolean; +}> = observer(({ silenceFormStore, matcher, isValid }) => { const { response, get, cancelGet } = useFetchGet( FormatBackendURI(`labelValues.json?name=${matcher.name}`), { autorun: false } @@ -74,36 +90,28 @@ const LabelValueInput = observer(({ silenceFormStore, matcher, isValid }) => { formatCreateLabel={NewLabelValue} defaultValue={matcher.values} options={ - response - ? response.map((value) => ({ - label: value, - value: value, - })) - : [] + response ? response.map((value: string) => StringToOption(value)) : [] } placeholder={isValid ? "Label value" : } onChange={(newValue) => { - const value = newValue || []; - matcher.values = value; + matcher.values = (newValue || []) as OptionT[]; // force regex if we have multiple values - if (value.length > 1 && matcher.isRegex === false) { + if (matcher.values.length > 1 && matcher.isRegex === false) { matcher.isRegex = true; - } else if (value.length === 1 && matcher.isRegex === true) { + } else if (matcher.values.length === 1 && matcher.isRegex === true) { matcher.isRegex = false; } }} hideSelectedOptions isMulti - components={{ ValueContainer, Placeholder }} + components={{ + ValueContainer: ValueContainer as ComponentType, + Placeholder: Placeholder, + }} silenceFormStore={silenceFormStore} matcher={matcher} /> ); }); -LabelValueInput.propTypes = { - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - matcher: SilenceFormMatcher.isRequired, - isValid: PropTypes.bool.isRequired, -}; export { LabelValueInput }; diff --git a/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.js b/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.tsx similarity index 81% rename from ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.js rename to ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.tsx index 1da99abb1..96e9d8a29 100644 --- a/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.js +++ b/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.tsx @@ -1,5 +1,4 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC } from "react"; import { useObserver } from "mobx-react-lite"; @@ -8,13 +7,15 @@ import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclama import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner"; import { FormatBackendURI, FormatAlertsQ } from "Stores/AlertStore"; -import { SilenceFormStore } from "Stores/SilenceFormStore"; -import { SilenceFormMatcher } from "Models/SilenceForm"; +import { SilenceFormStore, MatcherWithIDT } from "Stores/SilenceFormStore"; import { TooltipWrapper } from "Components/TooltipWrapper"; import { useFetchGet } from "Hooks/useFetchGet"; import { MatcherToFilter, AlertManagersToFilter } from "../Matchers"; -const MatchCounter = ({ silenceFormStore, matcher }) => { +const MatchCounter: FC<{ + silenceFormStore: SilenceFormStore; + matcher: MatcherWithIDT; +}> = ({ silenceFormStore, matcher }) => { const filters = [MatcherToFilter(matcher)]; if (silenceFormStore.data.alertmanagers.length) { filters.push(AlertManagersToFilter(silenceFormStore.data.alertmanagers)); @@ -52,9 +53,5 @@ const MatchCounter = ({ silenceFormStore, matcher }) => { ) ); }; -MatchCounter.propTypes = { - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - matcher: SilenceFormMatcher.isRequired, -}; export { MatchCounter }; diff --git a/ui/src/Components/SilenceModal/SilenceMatch/index.js b/ui/src/Components/SilenceModal/SilenceMatch/index.tsx similarity index 79% rename from ui/src/Components/SilenceModal/SilenceMatch/index.js rename to ui/src/Components/SilenceModal/SilenceMatch/index.tsx index c40dc89e9..4253aecd6 100644 --- a/ui/src/Components/SilenceModal/SilenceMatch/index.js +++ b/ui/src/Components/SilenceModal/SilenceMatch/index.tsx @@ -1,24 +1,22 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC } from "react"; import { useObserver } from "mobx-react-lite"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faTrash } from "@fortawesome/free-solid-svg-icons/faTrash"; -import { SilenceFormStore } from "Stores/SilenceFormStore"; +import { SilenceFormStore, MatcherWithIDT } from "Stores/SilenceFormStore"; import { TooltipWrapper } from "Components/TooltipWrapper"; -import { SilenceFormMatcher } from "Models/SilenceForm"; import { LabelNameInput } from "./LabelNameInput"; import { LabelValueInput } from "./LabelValueInput"; -const SilenceMatch = ({ - silenceFormStore, - matcher, - showDelete, - onDelete, - isValid, -}) => { +const SilenceMatch: FC<{ + silenceFormStore: SilenceFormStore; + matcher: MatcherWithIDT; + showDelete: boolean; + onDelete: () => void; + isValid: boolean; +}> = ({ silenceFormStore, matcher, showDelete, onDelete, isValid }) => { return useObserver(() => (
    @@ -68,12 +66,5 @@ const SilenceMatch = ({
    )); }; -SilenceMatch.propTypes = { - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - matcher: SilenceFormMatcher.isRequired, - showDelete: PropTypes.bool.isRequired, - onDelete: PropTypes.func.isRequired, - isValid: PropTypes.bool.isRequired, -}; export { SilenceMatch }; diff --git a/ui/src/Components/SilenceModal/SilenceModalContent.test.js b/ui/src/Components/SilenceModal/SilenceModalContent.test.js index c964cc017..4f82ed8d7 100644 --- a/ui/src/Components/SilenceModal/SilenceModalContent.test.js +++ b/ui/src/Components/SilenceModal/SilenceModalContent.test.js @@ -56,7 +56,6 @@ const MountedSilenceModalContent = () => { settingsStore={settingsStore} silenceFormStore={silenceFormStore} onHide={MockOnHide} - onDeleteModalClose={jest.fn()} /> ); }; diff --git a/ui/src/Components/SilenceModal/SilenceModalContent.js b/ui/src/Components/SilenceModal/SilenceModalContent.tsx similarity index 82% rename from ui/src/Components/SilenceModal/SilenceModalContent.js rename to ui/src/Components/SilenceModal/SilenceModalContent.tsx index 64690fe5b..5a41867f2 100644 --- a/ui/src/Components/SilenceModal/SilenceModalContent.js +++ b/ui/src/Components/SilenceModal/SilenceModalContent.tsx @@ -1,5 +1,4 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC } from "react"; import { useObserver } from "mobx-react-lite"; @@ -20,7 +19,7 @@ import { SilencePreview } from "./SilencePreview"; import { SilenceSubmitController } from "./SilenceSubmit/SilenceSubmitController"; import { Browser } from "./Browser"; -const ReadOnlyPlaceholder = () => ( +const ReadOnlyPlaceholder: FC = () => (

    @@ -29,13 +28,18 @@ const ReadOnlyPlaceholder = () => (

    ); -const SilenceModalContent = ({ +const SilenceModalContent: FC<{ + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; + settingsStore: Settings; + onHide: () => void; + previewOpen?: boolean; +}> = ({ alertStore, silenceFormStore, settingsStore, onHide, - previewOpen, - onDeleteModalClose, + previewOpen = false, }) => { return useObserver(() => ( @@ -53,12 +57,12 @@ const SilenceModalContent = ({ : "Silence submitted" } active={silenceFormStore.tab.current === SilenceTabNames.Editor} - onClick={() => silenceFormStore.tab.setTab(SilenceTabNames.Editor)} + onClick={() => silenceFormStore.tab.setTab("editor")} /> silenceFormStore.tab.setTab(SilenceTabNames.Browser)} + onClick={() => silenceFormStore.tab.setTab("browser")} />
    )); }; -SilenceModalContent.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - settingsStore: PropTypes.instanceOf(Settings).isRequired, - onHide: PropTypes.func.isRequired, - previewOpen: PropTypes.bool, - onDeleteModalClose: PropTypes.func.isRequired, -}; -SilenceModalContent.defaultProps = { - previewOpen: false, -}; export { SilenceModalContent }; diff --git a/ui/src/Components/SilenceModal/SilencePreview/index.js b/ui/src/Components/SilenceModal/SilencePreview/index.tsx similarity index 83% rename from ui/src/Components/SilenceModal/SilencePreview/index.js rename to ui/src/Components/SilenceModal/SilencePreview/index.tsx index a91000eaa..3c3060024 100644 --- a/ui/src/Components/SilenceModal/SilencePreview/index.js +++ b/ui/src/Components/SilenceModal/SilencePreview/index.tsx @@ -1,5 +1,4 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faArrowLeft } from "@fortawesome/free-solid-svg-icons/faArrowLeft"; @@ -10,7 +9,10 @@ import { SilenceFormStore } from "Stores/SilenceFormStore"; import { PaginatedAlertList } from "Components/PaginatedAlertList"; import { MatcherToFilter, AlertManagersToFilter } from "../Matchers"; -const SilencePreview = ({ alertStore, silenceFormStore }) => { +const SilencePreview: FC<{ + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; +}> = ({ alertStore, silenceFormStore }) => { const filters = [ ...silenceFormStore.data.matchers.map((m) => MatcherToFilter(m)), AlertManagersToFilter(silenceFormStore.data.alertmanagers), @@ -46,9 +48,5 @@ const SilencePreview = ({ alertStore, silenceFormStore }) => { ); }; -SilencePreview.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, -}; export { SilencePreview }; diff --git a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitController.js b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitController.tsx similarity index 91% rename from ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitController.js rename to ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitController.tsx index 7fd26bde7..3ddb86327 100644 --- a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitController.js +++ b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitController.tsx @@ -1,5 +1,4 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC } from "react"; import { useObserver } from "mobx-react-lite"; @@ -12,7 +11,10 @@ import { AlertStore } from "Stores/AlertStore"; import { SilenceFormStore } from "Stores/SilenceFormStore"; import { SilenceSubmitProgress } from "./SilenceSubmitProgress"; -const SingleClusterStatus = ({ silenceFormStore, alertStore }) => { +const SingleClusterStatus: FC<{ + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; +}> = ({ silenceFormStore, alertStore }) => { const clusterRequest = Object.values( silenceFormStore.data.requestsByCluster )[0]; @@ -64,7 +66,10 @@ const SingleClusterStatus = ({ silenceFormStore, alertStore }) => { )); }; -const MultiClusterStatus = ({ silenceFormStore, alertStore }) => { +const MultiClusterStatus: FC<{ + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; +}> = ({ silenceFormStore, alertStore }) => { return useObserver(() => (
    (
    @@ -129,7 +134,10 @@ const MultiClusterStatus = ({ silenceFormStore, alertStore }) => { )); }; -const SilenceSubmitController = ({ silenceFormStore, alertStore }) => { +const SilenceSubmitController: FC<{ + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; +}> = ({ silenceFormStore, alertStore }) => { return useObserver(() => ( {Object.keys(silenceFormStore.data.requestsByCluster).length === 1 ? ( @@ -156,9 +164,5 @@ const SilenceSubmitController = ({ silenceFormStore, alertStore }) => { )); }; -SilenceSubmitController.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, -}; export { SilenceSubmitController, MultiClusterStatus, SingleClusterStatus }; diff --git a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.tsx similarity index 63% rename from ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js rename to ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.tsx index 988420f13..eed6a6609 100644 --- a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js +++ b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.tsx @@ -1,27 +1,30 @@ -import React, { useEffect, useState } from "react"; -import PropTypes from "prop-types"; +import React, { FC, useEffect, useState } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch"; -import { APISilenceMatcher } from "Models/API"; +import { AlertmanagerSilencePayloadT } from "Models/APITypes"; import { AlertStore } from "Stores/AlertStore"; import { SilenceFormStore } from "Stores/SilenceFormStore"; -import { useFetchAny } from "Hooks/useFetchAny"; +import { useFetchAny, UpstreamT } from "Hooks/useFetchAny"; -const SilenceSubmitProgress = ({ - alertStore, - silenceFormStore, - cluster, - members, - payload, -}) => { - const [upstreams, setUpstreams] = useState([]); +interface PostResponseT { + silenceID: string; +} + +const SilenceSubmitProgress: FC<{ + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; + cluster: string; + members: string[]; + payload: AlertmanagerSilencePayloadT; +}> = ({ alertStore, silenceFormStore, cluster, members, payload }) => { + const [upstreams, setUpstreams] = useState([] as UpstreamT[]); const { response, error, inProgress, responseURI } = useFetchAny(upstreams); - const [publicURIs, setPublicURIs] = useState({}); + const [publicURIs, setPublicURIs] = useState({} as { [key: string]: string }); useEffect(() => { - let uris = {}; + let uris: { [uri: string]: string } = {}; let membersToTry = []; for (const member of members) { if (alertStore.data.isReadOnlyAlertmanager(member)) { @@ -60,29 +63,16 @@ const SilenceSubmitProgress = ({ silenceFormStore.data.requestsByCluster[cluster].error = error; } else if (!inProgress && response !== null) { silenceFormStore.data.requestsByCluster[cluster].isDone = true; - silenceFormStore.data.requestsByCluster[cluster].silenceID = - response.silenceID; silenceFormStore.data.requestsByCluster[ cluster - ].silenceLink = `${publicURIs[responseURI]}/#/silences/${response.silenceID}`; + ].silenceID = (response as PostResponseT).silenceID; + silenceFormStore.data.requestsByCluster[cluster].silenceLink = `${ + publicURIs[responseURI as string] + }/#/silences/${(response as PostResponseT).silenceID}`; } }, [cluster, error, inProgress, publicURIs, response, responseURI]); // eslint-disable-line react-hooks/exhaustive-deps return ; }; -SilenceSubmitProgress.propTypes = { - cluster: PropTypes.string.isRequired, - members: PropTypes.arrayOf(PropTypes.string).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, - id: PropTypes.string, - }).isRequired, - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, -}; export { SilenceSubmitProgress }; diff --git a/ui/src/Components/SilenceModal/index.stories.js b/ui/src/Components/SilenceModal/index.stories.js index f39176f55..cdaba3865 100644 --- a/ui/src/Components/SilenceModal/index.stories.js +++ b/ui/src/Components/SilenceModal/index.stories.js @@ -176,7 +176,6 @@ storiesOf("SilenceModal", module) settingsStore={settingsStore} onHide={() => {}} previewOpen={true} - onDeleteModalClose={() => {}} /> @@ -186,7 +185,6 @@ storiesOf("SilenceModal", module) settingsStore={settingsStore} onHide={() => {}} previewOpen={true} - onDeleteModalClose={() => {}} /> @@ -267,7 +265,6 @@ storiesOf("SilenceModal", module) silenceFormStore={silenceFormStore} settingsStore={settingsStore} onHide={() => {}} - onDeleteModalClose={() => {}} /> ); @@ -316,7 +313,6 @@ storiesOf("SilenceModal", module) silenceFormStore={silenceFormStore} settingsStore={settingsStore} onHide={() => {}} - onDeleteModalClose={() => {}} /> ); diff --git a/ui/src/Components/SilenceModal/index.test.js b/ui/src/Components/SilenceModal/index.test.js index c864a3fbb..7ad40d0fb 100644 --- a/ui/src/Components/SilenceModal/index.test.js +++ b/ui/src/Components/SilenceModal/index.test.js @@ -175,21 +175,4 @@ describe("", () => { tree.unmount(); expect(document.body.className.split(" ")).not.toContain("modal-open"); }); - - it("'modal-open' class is preserved on body node after remountModal is called", () => { - let callbacks = []; - jest.spyOn(React, "useCallback").mockImplementation((fn) => { - callbacks.push(fn); - return fn; - }); - - silenceFormStore.toggle.visible = true; - MountedSilenceModal(); - - expect(callbacks.length).toBeGreaterThan(0); - act(() => { - callbacks.forEach((f) => f()); - }); - expect(document.body.className.split(" ")).toContain("modal-open"); - }); }); diff --git a/ui/src/Components/SilenceModal/index.js b/ui/src/Components/SilenceModal/index.tsx similarity index 76% rename from ui/src/Components/SilenceModal/index.js rename to ui/src/Components/SilenceModal/index.tsx index 631347097..f5cf0c08d 100644 --- a/ui/src/Components/SilenceModal/index.js +++ b/ui/src/Components/SilenceModal/index.tsx @@ -1,5 +1,4 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { FC } from "react"; import { useObserver } from "mobx-react-lite"; @@ -20,13 +19,11 @@ const SilenceModalContent = React.lazy(() => })) ); -const SilenceModal = ({ alertStore, silenceFormStore, settingsStore }) => { - // uses React.useCallback instead of useCallback for tests - const onDeleteModalClose = React.useCallback(() => { - const event = new CustomEvent("remountModal"); - window.dispatchEvent(event); - }, []); - +const SilenceModal: FC<{ + alertStore: AlertStore; + silenceFormStore: SilenceFormStore; + settingsStore: Settings; +}> = ({ alertStore, silenceFormStore, settingsStore }) => { return useObserver(() => (
  • { silenceFormStore={silenceFormStore} settingsStore={settingsStore} onHide={silenceFormStore.toggle.hide} - onDeleteModalClose={onDeleteModalClose} /> )); }; -SilenceModal.propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, - settingsStore: PropTypes.instanceOf(Settings).isRequired, -}; export { SilenceModal }; diff --git a/ui/src/Hooks/useFetchAny.ts b/ui/src/Hooks/useFetchAny.ts index 29680d5b9..a41aba36d 100644 --- a/ui/src/Hooks/useFetchAny.ts +++ b/ui/src/Hooks/useFetchAny.ts @@ -10,7 +10,7 @@ export interface UpstreamT { } interface ResponseState { - response: string | null; + response: string | { [key: string]: any } | null; error: string | null; responseURI: string | null; inProgress: boolean; diff --git a/ui/src/Hooks/useFetchDelete.ts b/ui/src/Hooks/useFetchDelete.ts index 9b084b645..951c43fba 100644 --- a/ui/src/Hooks/useFetchDelete.ts +++ b/ui/src/Hooks/useFetchDelete.ts @@ -4,7 +4,11 @@ import merge from "lodash.merge"; import { CommonOptions } from "Common/Fetch"; -const useFetchDelete = (uri: string, options: RequestInit, deps = []) => { +const useFetchDelete = ( + uri: string, + options: RequestInit, + deps: any[] = [] +) => { const [response, setResponse] = useState(null as string | null); const [error, setError] = useState(null as string | null); const [isDeleting, setIsDeleting] = useState(true); diff --git a/ui/src/Hooks/useFetchGet.ts b/ui/src/Hooks/useFetchGet.ts index fa3cb7ac6..7d0c0e7a0 100644 --- a/ui/src/Hooks/useFetchGet.ts +++ b/ui/src/Hooks/useFetchGet.ts @@ -6,9 +6,17 @@ import promiseRetry from "promise-retry"; import { CommonOptions, FetchRetryConfig } from "Common/Fetch"; +type FetchFunctionT = (request: RequestInfo) => Promise; + +export interface FetchGetOptionsT { + autorun?: boolean; + deps?: any[]; + fetcher?: null | FetchFunctionT; +} + const useFetchGet = ( uri: string, - { autorun = true, deps = [], fetcher = null } = {} + { autorun = true, deps = [], fetcher = null }: FetchGetOptionsT = {} ) => { const [response, setResponse] = useState(null as any); const [error, setError] = useState(null as string | null); @@ -28,7 +36,7 @@ const useFetchGet = ( setIsLoading(true); setRetryCount(0); setError(null); - const res = await promiseRetry( + const res: Response = await promiseRetry( (retry: Function, number: number) => (fetcher || fetch)( uri, @@ -42,7 +50,7 @@ const useFetchGet = ( mode: number <= FetchRetryConfig.retries ? "cors" : "no-cors", } ) as RequestInit - ).catch((err) => { + ).catch((err: Error) => { if (!isCanceled.current) { setIsRetrying(true); setRetryCount(number); diff --git a/ui/src/Hooks/useGrid.ts b/ui/src/Hooks/useGrid.ts index 1554209d3..d108872bb 100644 --- a/ui/src/Hooks/useGrid.ts +++ b/ui/src/Hooks/useGrid.ts @@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from "react"; import Bricks, { SizeDetail, Instance } from "bricks.js"; const useGrid = (sizes: SizeDetail[]) => { - const ref = useRef(null as Node | null); + const ref = useRef(null as HTMLElement | null); const grid = useRef(null as Instance | null); const [repack, setRepack] = useState(() => () => {}); diff --git a/ui/src/Models/API.js b/ui/src/Models/API.js deleted file mode 100644 index 53ca90f1e..000000000 --- a/ui/src/Models/API.js +++ /dev/null @@ -1,101 +0,0 @@ -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({ - fingerprint: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - cluster: PropTypes.string.isRequired, - state: AlertState.isRequired, - startsAt: PropTypes.string.isRequired, - source: PropTypes.string.isRequired, - silencedBy: PropTypes.arrayOf(PropTypes.string).isRequired, - inhibitedBy: PropTypes.arrayOf(PropTypes.string).isRequired, -}); - -const APIAlert = PropTypes.exact({ - id: PropTypes.string.isRequired, - annotations: PropTypes.arrayOf(Annotation).isRequired, - labels: PropTypes.object.isRequired, - startsAt: PropTypes.string.isRequired, - state: AlertState.isRequired, - alertmanager: PropTypes.arrayOf(APIAlertAlertmanagerState).isRequired, - receiver: PropTypes.string.isRequired, -}); - -const APIStateCount = PropTypes.exact({ - active: PropTypes.number.isRequired, - suppressed: PropTypes.number.isRequired, - unprocessed: PropTypes.number.isRequired, -}); - -const APIGroup = PropTypes.exact({ - receiver: PropTypes.string.isRequired, - labels: PropTypes.object.isRequired, - alerts: PropTypes.arrayOf(APIAlert), - id: PropTypes.string.isRequired, - alertmanagerCount: PropTypes.objectOf(PropTypes.number).isRequired, - stateCount: APIStateCount.isRequired, - shared: PropTypes.exact({ - annotations: PropTypes.arrayOf(Annotation).isRequired, - labels: PropTypes.object.isRequired, - silences: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string)) - .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, - ticketID: PropTypes.string.isRequired, - ticketURL: PropTypes.string.isRequired, -}); - -const APIAlertmanagerUpstream = PropTypes.exact({ - name: PropTypes.string.isRequired, - cluster: PropTypes.string.isRequired, - uri: PropTypes.string.isRequired, - publicURI: PropTypes.string.isRequired, - readonly: PropTypes.bool.isRequired, - headers: PropTypes.object.isRequired, - corsCredentials: PropTypes.oneOf(["omit", "same-origin", "include"]) - .isRequired, - error: PropTypes.string.isRequired, - version: PropTypes.string.isRequired, - clusterMembers: PropTypes.arrayOf(PropTypes.string).isRequired, -}); - -const APIGrid = PropTypes.exact({ - labelName: PropTypes.string.isRequired, - labelValue: PropTypes.string.isRequired, - alertGroups: PropTypes.arrayOf(APIGroup).isRequired, - stateCount: APIStateCount.isRequired, -}); - -export { - APIAlert, - APIGroup, - APISilence, - APISilenceMatcher, - APIAlertAlertmanagerState, - APIAlertmanagerUpstream, - APIGrid, -}; diff --git a/ui/src/Models/APITypes.ts b/ui/src/Models/APITypes.ts index f339a2406..dde3cc79a 100644 --- a/ui/src/Models/APITypes.ts +++ b/ui/src/Models/APITypes.ts @@ -77,6 +77,13 @@ export interface APISilenceT { ticketURL: string; } +export interface APIManagedSilenceT { + alertCount: number; + cluster: string; + isExpired: boolean; + silence: APISilenceT; +} + export interface APIGridT { labelName: string; labelValue: string; diff --git a/ui/src/Models/SilenceForm.js b/ui/src/Models/SilenceForm.js deleted file mode 100644 index 85d9a243d..000000000 --- a/ui/src/Models/SilenceForm.js +++ /dev/null @@ -1,15 +0,0 @@ -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, - isRegex: PropTypes.bool.isRequired, -}); - -export { SilenceFormMatcher, SilenceFormSuggestion }; diff --git a/ui/src/Stores/SilenceFormStore.ts b/ui/src/Stores/SilenceFormStore.ts index 7f9b3c603..1c96332b6 100644 --- a/ui/src/Stores/SilenceFormStore.ts +++ b/ui/src/Stores/SilenceFormStore.ts @@ -16,12 +16,7 @@ import { APIAlertmanagerUpstreamT, AlertmanagerSilencePayloadT, } from "Models/APITypes"; -import { StringToOption, OptionT } from "Common/Select"; - -export interface MultiValueOptionT { - label: string; - value: string[]; -} +import { StringToOption, OptionT, MultiValueOptionT } from "Common/Select"; export interface MatcherT { name: string; @@ -80,7 +75,7 @@ const SilenceTabNames = Object.freeze({ const MatchersFromGroup = ( group: APIAlertGroupT, stripLabels: string[], - alerts: APIAlertT[], + alerts?: APIAlertT[], onlyActive?: boolean ): MatcherWithIDT[] => { let matchers: MatcherWithIDT[] = []; @@ -149,12 +144,24 @@ const MatchersFromGroup = ( return matchers; }; -const NewClusterRequest = (cluster: string, members: string[]) => ({ +export interface ClusterRequestT { + cluster: string; + members: string[]; + isDone: boolean; + silenceID: undefined | string; + silenceLink: undefined | string; + error: null | string; +} + +const NewClusterRequest = ( + cluster: string, + members: string[] +): ClusterRequestT => ({ cluster: cluster, members: members, isDone: false, - silenceID: null, - silenceLink: null, + silenceID: undefined, + silenceLink: undefined, error: null, }); @@ -201,10 +208,6 @@ const UnpackRegexMatcherValues = (isRegex: boolean, value: string) => { } }; -interface ClusterRequestT { - foo: boolean; - // FIXME -} class SilenceFormStore { toggle = observable( { @@ -367,7 +370,7 @@ class SilenceFormStore { group: APIAlertGroupT, stripLabels: string[], alertmanagers: MultiValueOptionT[], - alerts: APIAlertT[] + alerts?: APIAlertT[] ) { this.alertmanagers = alertmanagers; diff --git a/ui/src/react-app-env.d.ts b/ui/src/react-app-env.d.ts index bc1a38d83..9d458eeb0 100644 --- a/ui/src/react-app-env.d.ts +++ b/ui/src/react-app-env.d.ts @@ -5,3 +5,7 @@ declare module "react-media-hook" { } declare module "favico.js"; + +declare module "react-json-pretty/dist/monikai"; + +declare module "react-linkify";