change(ui): refactor alerts?
This commit is contained in:
parent
0ad417d0dc
commit
bf1fb4f680
27 changed files with 475 additions and 287 deletions
|
|
@ -8,7 +8,6 @@ import { fetchUserInfo } from 'Duck/user';
|
|||
import withSiteIdUpdater from 'HOCs/withSiteIdUpdater';
|
||||
import Header from 'Components/Header/Header';
|
||||
import { fetchList as fetchSiteList } from 'Duck/site';
|
||||
import { fetchList as fetchAlerts } from 'Duck/alerts';
|
||||
import { withStore } from 'App/mstore';
|
||||
|
||||
import APIClient from './api_client';
|
||||
|
|
@ -114,7 +113,6 @@ const MULTIVIEW_INDEX_PATH = routes.multiviewIndex();
|
|||
fetchTenants,
|
||||
setSessionPath,
|
||||
fetchSiteList,
|
||||
fetchAlerts,
|
||||
}
|
||||
)
|
||||
class Router extends React.Component {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { Button, Form, Input, SegmentSelection, Checkbox, Icon } from 'UI';
|
||||
import { alertConditions as conditions } from 'App/constants';
|
||||
import { client, CLIENT_TABS } from 'App/routes';
|
||||
import { connect } from 'react-redux';
|
||||
import stl from './alertForm.module.css';
|
||||
import DropdownChips from './DropdownChips';
|
||||
import { validateEmail } from 'App/validate';
|
||||
import cn from 'classnames';
|
||||
import { fetchTriggerOptions } from 'Duck/alerts';
|
||||
import { useStore } from 'App/mstore'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import Select from 'Shared/Select';
|
||||
|
||||
const thresholdOptions = [
|
||||
|
|
@ -44,26 +43,28 @@ const Section = ({ index, title, description, content }) => (
|
|||
</div>
|
||||
);
|
||||
|
||||
const integrationsRoute = client(CLIENT_TABS.INTEGRATIONS);
|
||||
|
||||
const AlertForm = (props) => {
|
||||
const {
|
||||
instance,
|
||||
slackChannels,
|
||||
msTeamsChannels,
|
||||
webhooks,
|
||||
loading,
|
||||
onDelete,
|
||||
deleting,
|
||||
triggerOptions,
|
||||
style = { width: '580px', height: '100vh' },
|
||||
} = props;
|
||||
const { alertsStore } = useStore()
|
||||
const {
|
||||
instance,
|
||||
triggerOptions,
|
||||
loading,
|
||||
} = alertsStore
|
||||
const deleting = loading
|
||||
|
||||
const write = ({ target: { value, name } }) => props.edit({ [name]: value });
|
||||
const writeOption = (e, { name, value }) => props.edit({ [name]: value.value });
|
||||
const onChangeCheck = ({ target: { checked, name } }) => props.edit({ [name]: checked });
|
||||
|
||||
useEffect(() => {
|
||||
props.fetchTriggerOptions();
|
||||
alertsStore.fetchTriggerOptions();
|
||||
}, []);
|
||||
|
||||
const writeQueryOption = (e, { name, value }) => {
|
||||
|
|
@ -378,12 +379,4 @@ const AlertForm = (props) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default connect(
|
||||
(state) => ({
|
||||
instance: state.getIn(['alerts', 'instance']),
|
||||
triggerOptions: state.getIn(['alerts', 'triggerOptions']),
|
||||
loading: state.getIn(['alerts', 'saveRequest', 'loading']),
|
||||
deleting: state.getIn(['alerts', 'removeRequest', 'loading']),
|
||||
}),
|
||||
{ fetchTriggerOptions }
|
||||
)(AlertForm);
|
||||
export default observer(AlertForm);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { SlideModal, IconButton } from 'UI';
|
||||
import { init, edit, save, remove } from 'Duck/alerts';
|
||||
import { SlideModal } from 'UI';
|
||||
import { useStore } from 'App/mstore'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { fetchList as fetchWebhooks } from 'Duck/webhook';
|
||||
import AlertForm from '../AlertForm';
|
||||
import { connect } from 'react-redux';
|
||||
import { setShowAlerts } from 'Duck/dashboard';
|
||||
import { EMAIL, SLACK, WEBHOOK } from 'App/constants/schedule';
|
||||
import { SLACK, WEBHOOK } from 'App/constants/schedule';
|
||||
import { confirm } from 'UI';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -14,12 +15,9 @@ interface Props {
|
|||
onClose?: () => void;
|
||||
webhooks: any;
|
||||
fetchWebhooks: Function;
|
||||
save: Function;
|
||||
remove: Function;
|
||||
init: Function;
|
||||
edit: Function;
|
||||
}
|
||||
function AlertFormModal(props: Props) {
|
||||
const { alertsStore } = useStore()
|
||||
const { metricId = null, showModal = false, webhooks } = props;
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
|
||||
|
|
@ -38,7 +36,7 @@ function AlertFormModal(props: Props) {
|
|||
|
||||
const saveAlert = (instance) => {
|
||||
const wasUpdating = instance.exists();
|
||||
props.save(instance).then(() => {
|
||||
alertsStore.save(instance).then(() => {
|
||||
if (!wasUpdating) {
|
||||
toggleForm(null, false);
|
||||
}
|
||||
|
|
@ -56,7 +54,7 @@ function AlertFormModal(props: Props) {
|
|||
confirmation: `Are you sure you want to permanently delete this alert?`,
|
||||
})
|
||||
) {
|
||||
props.remove(instance.alertId).then(() => {
|
||||
alertsStore.remove(instance.alertId).then(() => {
|
||||
toggleForm(null, false);
|
||||
});
|
||||
}
|
||||
|
|
@ -64,7 +62,7 @@ function AlertFormModal(props: Props) {
|
|||
|
||||
const toggleForm = (instance, state) => {
|
||||
if (instance) {
|
||||
props.init(instance);
|
||||
alertsStore.init(instance);
|
||||
}
|
||||
return setShowForm(state ? state : !showForm);
|
||||
};
|
||||
|
|
@ -83,7 +81,7 @@ function AlertFormModal(props: Props) {
|
|||
showModal && (
|
||||
<AlertForm
|
||||
metricId={metricId}
|
||||
edit={props.edit}
|
||||
edit={alertsStore.edit}
|
||||
slackChannels={slackChannels}
|
||||
webhooks={hooks}
|
||||
onSubmit={saveAlert}
|
||||
|
|
@ -100,7 +98,6 @@ function AlertFormModal(props: Props) {
|
|||
export default connect(
|
||||
(state) => ({
|
||||
webhooks: state.getIn(['webhooks', 'list']),
|
||||
instance: state.getIn(['alerts', 'instance']),
|
||||
}),
|
||||
{ init, edit, save, remove, fetchWebhooks, setShowAlerts }
|
||||
)(AlertFormModal);
|
||||
{ fetchWebhooks, setShowAlerts }
|
||||
)(observer(AlertFormModal));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Loader, NoContent, Input, Button } from 'UI';
|
||||
import AlertItem from './AlertItem';
|
||||
import { fetchList, init } from 'Duck/alerts';
|
||||
import { fetchList } from 'Duck/alerts';
|
||||
import { connect } from 'react-redux';
|
||||
import { getRE } from 'App/utils';
|
||||
|
||||
|
|
@ -54,5 +54,5 @@ export default connect(
|
|||
instance: state.getIn(['alerts', 'instance']),
|
||||
loading: state.getIn(['alerts', 'loading']),
|
||||
}),
|
||||
{ fetchList, init }
|
||||
{ fetchList }
|
||||
)(AlertsList);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react'
|
||||
import stl from './Bar.module.css'
|
||||
|
||||
const Bar = ({ className = '', width = 0, avg, domain, color }) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex items-center">
|
||||
<div className={stl.bar} style={{ width: `${width > 0 ? width : 5 }%`, backgroundColor: color }}></div>
|
||||
<div className="ml-2">
|
||||
<span className="font-medium">{`${avg}`}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm leading-3 color-gray-medium">{domain}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Bar
|
||||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { NoContent } from 'UI';
|
||||
import { Styles } from '../../common';
|
||||
import { numberWithCommas } from 'App/utils';
|
||||
import Bar from 'App/components/Dashboard/Widgets/ErrorsPerDomain/Bar';
|
||||
import Bar from './Bar';
|
||||
import { NO_METRIC_DATA } from 'App/constants/messages'
|
||||
|
||||
interface Props {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
.bar {
|
||||
height: 5px;
|
||||
background-color: red;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
.bar {
|
||||
height: 10px;
|
||||
background-color: red;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react'
|
||||
import stl from './Bar.module.css'
|
||||
|
||||
const Bar = ({ className = '', width = 0, avg, domain, color }) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex items-center">
|
||||
<div className={stl.bar} style={{ width: `${width < 5 ? 5 : width }%`, backgroundColor: color }}></div>
|
||||
<div className="ml-2 shrink-0">
|
||||
<span className="font-medium">{avg}</span>
|
||||
<span> ms</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm leading-3">{domain}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Bar
|
||||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { NoContent } from 'UI';
|
||||
import { Styles } from '../../common';
|
||||
import { numberWithCommas } from 'App/utils';
|
||||
import Bar from 'App/components/Dashboard/Widgets/SlowestDomains/Bar';
|
||||
import Bar from './Bar';
|
||||
import { NO_METRIC_DATA } from 'App/constants/messages'
|
||||
|
||||
interface Props {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { numberWithCommas } from 'App/utils';
|
|||
import { DateTime } from 'luxon';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import cn from 'classnames';
|
||||
import Alert from 'Types/alert';
|
||||
|
||||
const getThreshold = (threshold: number) => {
|
||||
if (threshold === 15) return '15 Minutes';
|
||||
|
|
@ -75,7 +76,7 @@ const getNotifyChannel = (alert: Record<string, any>, webhooks: Array<any>) => {
|
|||
interface Props extends RouteComponentProps {
|
||||
alert: Alert;
|
||||
siteId: string;
|
||||
init: (alert?: Alert) => void;
|
||||
init: (alert: Alert) => void;
|
||||
demo?: boolean;
|
||||
webhooks: Array<any>;
|
||||
}
|
||||
|
|
@ -90,7 +91,7 @@ function AlertListItem(props: Props) {
|
|||
const onItemClick = () => {
|
||||
if (demo) return;
|
||||
const path = withSiteId(alertEdit(alert.alertId), siteId);
|
||||
init(alert);
|
||||
init(alert || {});
|
||||
history.push(path);
|
||||
};
|
||||
|
||||
|
|
@ -117,9 +118,9 @@ function AlertListItem(props: Props) {
|
|||
{demo
|
||||
? DateTime.fromMillis(+new Date()).toFormat('LLL dd, yyyy, hh:mm a')
|
||||
: checkForRecent(
|
||||
DateTime.fromMillis(alert.createdAt || +new Date()),
|
||||
'LLL dd, yyyy, hh:mm a'
|
||||
)}
|
||||
DateTime.fromMillis(alert.createdAt || +new Date()),
|
||||
'LLL dd, yyyy, hh:mm a'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="color-gray-medium px-2 pb-2">
|
||||
|
|
@ -133,11 +134,13 @@ function AlertListItem(props: Props) {
|
|||
{numberWithCommas(alert.query.right)} {alert.metric.unit}
|
||||
</span>
|
||||
{' over the past '}
|
||||
<span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas' }}>{getThreshold(alert.currentPeriod)}</span>
|
||||
<span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas' }}>{getThreshold(
|
||||
alert.currentPeriod)}</span>
|
||||
{alert.detectionMethod === 'change' ? (
|
||||
<>
|
||||
{' compared to the previous '}
|
||||
<span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas ' }}>{getThreshold(alert.previousPeriod)}</span>
|
||||
<span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas ' }}>{getThreshold(
|
||||
alert.previousPeriod)}</span>
|
||||
</>
|
||||
) : null}
|
||||
{', notify me on '}
|
||||
|
|
|
|||
|
|
@ -2,37 +2,36 @@ import React from 'react';
|
|||
import { NoContent, Pagination, Icon } from 'UI';
|
||||
import { filterList } from 'App/utils';
|
||||
import { sliceListPerPage } from 'App/utils';
|
||||
import { fetchList } from 'Duck/alerts';
|
||||
import { connect } from 'react-redux';
|
||||
import { fetchList as fetchWebhooks } from 'Duck/webhook';
|
||||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||
import AlertListItem from './AlertListItem'
|
||||
import { useStore } from 'App/mstore'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import Alert from 'Types/alert'
|
||||
|
||||
const pageSize = 10;
|
||||
|
||||
interface Props {
|
||||
fetchList: () => void;
|
||||
list: any;
|
||||
alertsSearch: any;
|
||||
siteId: string;
|
||||
webhooks: Array<any>;
|
||||
init: (instance?: Alert) => void
|
||||
fetchWebhooks: () => void;
|
||||
}
|
||||
|
||||
function AlertsList({ fetchList, list: alertsList, alertsSearch, siteId, init, fetchWebhooks, webhooks }: Props) {
|
||||
React.useEffect(() => { fetchList(); fetchWebhooks() }, []);
|
||||
function AlertsList({ siteId, fetchWebhooks, webhooks }: Props) {
|
||||
const { alertsStore } = useStore();
|
||||
const { alerts: alertsList, alertsSearch, fetchList, init } = alertsStore
|
||||
|
||||
const alertsArray = alertsList.toJS();
|
||||
React.useEffect(() => { fetchList(); fetchWebhooks() }, []);
|
||||
const alertsArray = alertsList
|
||||
const [page, setPage] = React.useState(1);
|
||||
|
||||
const filteredAlerts = filterList(alertsArray, alertsSearch, ['name'], (item, query) => query.test(item.query.left))
|
||||
const list = alertsSearch !== '' ? filteredAlerts : alertsArray;
|
||||
const lenth = list.length;
|
||||
|
||||
return (
|
||||
<NoContent
|
||||
show={lenth === 0}
|
||||
show={list.length === 0}
|
||||
title={
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<AnimatedSVG name={ICONS.NO_ALERTS} size={180} />
|
||||
|
|
@ -63,7 +62,7 @@ function AlertsList({ fetchList, list: alertsList, alertsSearch, siteId, init, f
|
|||
</div>
|
||||
<Pagination
|
||||
page={page}
|
||||
totalPages={Math.ceil(lenth / pageSize)}
|
||||
totalPages={Math.ceil(list.length / pageSize)}
|
||||
onPageChange={(page) => setPage(page)}
|
||||
limit={pageSize}
|
||||
debounceRequest={100}
|
||||
|
|
@ -75,12 +74,8 @@ function AlertsList({ fetchList, list: alertsList, alertsSearch, siteId, init, f
|
|||
|
||||
export default connect(
|
||||
(state) => ({
|
||||
// @ts-ignore
|
||||
list: state.getIn(['alerts', 'list']).sort((a, b) => b.createdAt - a.createdAt),
|
||||
// @ts-ignore
|
||||
alertsSearch: state.getIn(['alerts', 'alertsSearch']),
|
||||
// @ts-ignore
|
||||
webhooks: state.getIn(['webhooks', 'list']),
|
||||
}),
|
||||
{ fetchList, fetchWebhooks }
|
||||
)(AlertsList);
|
||||
{ fetchWebhooks }
|
||||
)(observer(AlertsList));
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Icon } from 'UI';
|
||||
import { debounce } from 'App/utils';
|
||||
import { changeSearch } from 'Duck/alerts';
|
||||
import { connect } from 'react-redux';
|
||||
import { useStore } from 'App/mstore'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
|
||||
let debounceUpdate: any = () => {};
|
||||
|
||||
interface Props {
|
||||
changeSearch: (value: string) => void;
|
||||
}
|
||||
|
||||
function AlertsSearch({ changeSearch }: Props) {
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
function AlertsSearch() {
|
||||
const { alertsStore } = useStore();
|
||||
const [inputValue, setInputValue] = useState(alertsStore.alertsSearch);
|
||||
|
||||
useEffect(() => {
|
||||
debounceUpdate = debounce((value: string) => changeSearch(value), 500);
|
||||
debounceUpdate = debounce((value: string) => alertsStore.changeSearch(value), 500);
|
||||
}, []);
|
||||
|
||||
const write = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
|
||||
|
|
@ -36,10 +33,4 @@ function AlertsSearch({ changeSearch }: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state) => ({
|
||||
// @ts-ignore
|
||||
alertsSearch: state.getIn(['alerts', 'alertsSearch']),
|
||||
}),
|
||||
{ changeSearch }
|
||||
)(AlertsSearch);
|
||||
export default observer(AlertsSearch);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Button, PageTitle, Icon, Link } from 'UI';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
import { connect } from 'react-redux';
|
||||
import { init } from 'Duck/alerts';
|
||||
import { withSiteId, alertCreate } from 'App/routes';
|
||||
|
||||
import AlertsList from './AlertsList';
|
||||
|
|
@ -10,10 +8,9 @@ import AlertsSearch from './AlertsSearch';
|
|||
|
||||
interface IAlertsView {
|
||||
siteId: string;
|
||||
init: (instance?: Alert) => any;
|
||||
}
|
||||
|
||||
function AlertsView({ siteId, init }: IAlertsView) {
|
||||
function AlertsView({ siteId }: IAlertsView) {
|
||||
return (
|
||||
<div style={{ maxWidth: '1300px', margin: 'auto'}} className="bg-white rounded py-4 border">
|
||||
<div className="flex items-center mb-4 justify-between px-6">
|
||||
|
|
@ -21,7 +18,7 @@ function AlertsView({ siteId, init }: IAlertsView) {
|
|||
<PageTitle title="Alerts" />
|
||||
</div>
|
||||
<div className="ml-auto flex items-center">
|
||||
<Link to={withSiteId(alertCreate(), siteId)}><Button variant="primary" onClick={null}>Create Alert</Button></Link>
|
||||
<Link to={withSiteId(alertCreate(), siteId)}><Button variant="primary">Create Alert</Button></Link>
|
||||
<div className="ml-4 w-1/4" style={{ minWidth: 300 }}>
|
||||
<AlertsSearch />
|
||||
</div>
|
||||
|
|
@ -31,12 +28,9 @@ function AlertsView({ siteId, init }: IAlertsView) {
|
|||
<Icon name="info-circle-fill" className="mr-2" size={16} />
|
||||
Alerts helps your team stay up to date with the activity on your app.
|
||||
</div>
|
||||
<AlertsList siteId={siteId} init={init} />
|
||||
<AlertsList siteId={siteId} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const Container = connect(null, { init })(AlertsView);
|
||||
|
||||
export default withPageTitle('Alerts - OpenReplay')(Container);
|
||||
export default withPageTitle('Alerts - OpenReplay')(AlertsView);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { Form, SegmentSelection, Icon } from 'UI';
|
||||
import { Form, SegmentSelection } from 'UI';
|
||||
import { connect } from 'react-redux';
|
||||
import { validateEmail } from 'App/validate';
|
||||
import { fetchTriggerOptions, init, edit, save, remove, fetchList } from 'Duck/alerts';
|
||||
import { confirm } from 'UI';
|
||||
import { toast } from 'react-toastify';
|
||||
import { SLACK, WEBHOOK, TEAMS } from 'App/constants/schedule';
|
||||
|
|
@ -10,7 +9,9 @@ import { fetchList as fetchWebhooks } from 'Duck/webhook';
|
|||
import Breadcrumb from 'Shared/Breadcrumb';
|
||||
import { withSiteId, alerts } from 'App/routes';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { useStore } from 'App/mstore'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import Alert from 'Types/alert'
|
||||
import cn from 'classnames';
|
||||
import WidgetName from '../WidgetName';
|
||||
import BottomButtons from './AlertForm/BottomButtons';
|
||||
|
|
@ -55,67 +56,63 @@ interface Select {
|
|||
|
||||
interface IProps extends RouteComponentProps {
|
||||
siteId: string;
|
||||
instance: Alert;
|
||||
slackChannels: any[];
|
||||
webhooks: any[];
|
||||
loading: boolean;
|
||||
deleting: boolean;
|
||||
triggerOptions: any[];
|
||||
list: any;
|
||||
fetchTriggerOptions: () => void;
|
||||
edit: (query: any) => void;
|
||||
init: (alert?: Alert) => any;
|
||||
save: (alert: Alert) => Promise<any>;
|
||||
remove: (alertId: string) => Promise<any>;
|
||||
onSubmit: (instance: Alert) => void;
|
||||
fetchWebhooks: () => void;
|
||||
fetchList: () => void;
|
||||
}
|
||||
|
||||
const NewAlert = (props: IProps) => {
|
||||
const { alertsStore } = useStore();
|
||||
const {
|
||||
instance,
|
||||
siteId,
|
||||
webhooks,
|
||||
loading,
|
||||
deleting,
|
||||
triggerOptions,
|
||||
fetchTriggerOptions,
|
||||
init,
|
||||
edit,
|
||||
save,
|
||||
remove,
|
||||
fetchWebhooks,
|
||||
fetchList,
|
||||
list,
|
||||
instance,
|
||||
alerts: list,
|
||||
triggerOptions,
|
||||
loading,
|
||||
} = alertsStore
|
||||
const deleting = loading
|
||||
|
||||
const {
|
||||
siteId,
|
||||
webhooks,
|
||||
fetchWebhooks,
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
init({});
|
||||
if (list.size === 0) fetchList();
|
||||
props.fetchTriggerOptions();
|
||||
if (list.length === 0) fetchList();
|
||||
fetchTriggerOptions();
|
||||
fetchWebhooks();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (list.size > 0) {
|
||||
if (list.length > 0) {
|
||||
const alertId = location.pathname.split('/').pop();
|
||||
const currentAlert = list
|
||||
.toJS()
|
||||
.find((alert: Alert) => alert.alertId === parseInt(alertId, 10));
|
||||
init(currentAlert);
|
||||
.find((alert: Alert) => alert.alertId === String(alertId));
|
||||
init(currentAlert || {});
|
||||
}
|
||||
}, [list]);
|
||||
|
||||
const write = ({ target: { value, name } }: React.ChangeEvent<HTMLInputElement>) =>
|
||||
props.edit({ [name]: value });
|
||||
edit({ [name]: value });
|
||||
|
||||
const writeOption = (
|
||||
_: React.ChangeEvent,
|
||||
{ name, value }: { name: string; value: Record<string, any> }
|
||||
) => props.edit({ [name]: value.value });
|
||||
) => edit({ [name]: value.value });
|
||||
|
||||
const onChangeCheck = ({ target: { checked, name } }: React.ChangeEvent<HTMLInputElement>) =>
|
||||
props.edit({ [name]: checked });
|
||||
const onChangeCheck = ({ target: { checked, name } }: React.ChangeEvent<HTMLInputElement>) => edit({ [name]: checked });
|
||||
|
||||
const onDelete = async (instance: Alert) => {
|
||||
if (
|
||||
|
|
@ -170,12 +167,12 @@ const NewAlert = (props: IProps) => {
|
|||
{ name, value }: { name: string; value: string }
|
||||
) => {
|
||||
const { query } = instance;
|
||||
props.edit({ query: { ...query, [name]: value } });
|
||||
edit({ query: { ...query, [name]: value } });
|
||||
};
|
||||
|
||||
const writeQuery = ({ target: { value, name } }: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { query } = instance;
|
||||
props.edit({ query: { ...query, [name]: value } });
|
||||
edit({ query: { ...query, [name]: value } });
|
||||
};
|
||||
|
||||
const metric =
|
||||
|
|
@ -222,7 +219,7 @@ const NewAlert = (props: IProps) => {
|
|||
outline
|
||||
name="detectionMethod"
|
||||
className="my-3 w-1/4"
|
||||
onSelect={(e: any, { name, value }: any) => props.edit({ [name]: value })}
|
||||
onSelect={(e: any, { name, value }: any) => edit({ [name]: value })}
|
||||
value={{ value: instance.detectionMethod }}
|
||||
list={[
|
||||
{ name: 'Threshold', value: 'threshold' },
|
||||
|
|
@ -294,20 +291,9 @@ const NewAlert = (props: IProps) => {
|
|||
export default withRouter(
|
||||
connect(
|
||||
(state) => ({
|
||||
// @ts-ignore
|
||||
instance: state.getIn(['alerts', 'instance']),
|
||||
//@ts-ignore
|
||||
list: state.getIn(['alerts', 'list']),
|
||||
// @ts-ignore
|
||||
triggerOptions: state.getIn(['alerts', 'triggerOptions']),
|
||||
// @ts-ignore
|
||||
loading: state.getIn(['alerts', 'saveRequest', 'loading']),
|
||||
// @ts-ignore
|
||||
deleting: state.getIn(['alerts', 'removeRequest', 'loading']),
|
||||
// @ts-ignore
|
||||
webhooks: state.getIn(['webhooks', 'list']),
|
||||
}),
|
||||
{ fetchTriggerOptions, init, edit, save, remove, fetchWebhooks, fetchList }
|
||||
// @ts-ignore
|
||||
)(NewAlert)
|
||||
{ fetchWebhooks }
|
||||
)(observer(NewAlert))
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
// TODO burn the immutable and make typing this possible
|
||||
type Alert = Record<string, any>
|
||||
|
|
@ -3,4 +3,4 @@ export default [
|
|||
{ value: '>=', label: 'above or equal to' },
|
||||
{ value: '<', label: 'below' },
|
||||
{ value: '<=', label: 'below or equal to' },
|
||||
];
|
||||
] as const;
|
||||
|
|
@ -18,4 +18,4 @@ export default [
|
|||
{ value: 'performance.crashes.count', label: 'performance.crashes.count', unit: '' },
|
||||
{ value: 'errors.javascript.count', label: 'errors.javascript.count', unit: '' },
|
||||
{ value: 'errors.backend.count', label: 'errors.backend.count', unit: '' },
|
||||
];
|
||||
] as const;
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
import Alert from 'Types/alert';
|
||||
import { Map } from 'immutable';
|
||||
import crudDuckGenerator from './tools/crudDuck';
|
||||
import withRequestState, { RequestTypes } from 'Duck/requestStateCreator';
|
||||
import { RequestTypes } from 'Duck/requestStateCreator';
|
||||
import { reduceDucks } from 'Duck/tools';
|
||||
|
||||
const name = 'alert'
|
||||
const idKey = 'alertId';
|
||||
const crudDuck = crudDuckGenerator(name, Alert, { idKey: idKey });
|
||||
const crudDuck = crudDuckGenerator(name, (d) => new Alert(d), { idKey: idKey });
|
||||
export const { fetchList, init, edit, remove } = crudDuck.actions;
|
||||
const FETCH_TRIGGER_OPTIONS = new RequestTypes(`${name}/FETCH_TRIGGER_OPTIONS`);
|
||||
const CHANGE_SEARCH = `${name}/CHANGE_SEARCH`
|
||||
|
||||
console.log(fetchList(), init(), edit(), remove())
|
||||
const initialState = Map({
|
||||
definedPercent: 0,
|
||||
triggerOptions: [],
|
||||
|
|
|
|||
75
frontend/app/mstore/alertsStore.ts
Normal file
75
frontend/app/mstore/alertsStore.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { makeAutoObservable } from 'mobx'
|
||||
import Alert, { IAlert } from 'Types/alert'
|
||||
import { alertsService } from 'App/services'
|
||||
|
||||
export default class AlertsStore {
|
||||
alerts: Alert[] = [];
|
||||
triggerOptions: { label: string, value: string | number, unit?: string }[] = [];
|
||||
alertsSearch = '';
|
||||
// @ts-ignore
|
||||
instance: Alert = new Alert({}, false);
|
||||
loading = false
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
changeSearch(value: string) {
|
||||
this.alertsSearch = value;
|
||||
}
|
||||
|
||||
async fetchList() {
|
||||
this.loading = true
|
||||
try {
|
||||
const list = await alertsService.fetchList();
|
||||
this.alerts = list.map(alert => new Alert(alert, true));
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
async save(inst: Alert) {
|
||||
this.loading = true
|
||||
try {
|
||||
await alertsService.save(inst ? inst : this.instance)
|
||||
this.instance.isExists = true
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
async remove(id: string) {
|
||||
this.loading = true
|
||||
try {
|
||||
await alertsService.remove(id)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
async fetchTriggerOptions() {
|
||||
this.loading = true
|
||||
try {
|
||||
const options = await alertsService.fetchTriggerOptions();
|
||||
this.triggerOptions = options.map(({ name, value }) => ({ label: name, value }))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
init(inst: Partial<IAlert> | Alert) {
|
||||
this.instance = inst instanceof Alert ? inst : new Alert(inst, false)
|
||||
}
|
||||
|
||||
edit(diff: Partial<Alert>) {
|
||||
Object.assign(this.instance, diff)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import {
|
|||
notesService,
|
||||
recordingsService,
|
||||
configService,
|
||||
alertsService,
|
||||
} from 'App/services';
|
||||
import SettingsStore from './settingsStore';
|
||||
import AuditStore from './auditStore';
|
||||
|
|
@ -27,6 +28,7 @@ import BugReportStore from './bugReportStore'
|
|||
import RecordingsStore from './recordingsStore'
|
||||
import AssistMultiviewStore from './assistMultiviewStore';
|
||||
import WeeklyReportStore from './weeklyReportConfigStore'
|
||||
import AlertStore from './alertsStore'
|
||||
|
||||
export class RootStore {
|
||||
dashboardStore: DashboardStore;
|
||||
|
|
@ -44,6 +46,7 @@ export class RootStore {
|
|||
recordingsStore: RecordingsStore;
|
||||
assistMultiviewStore: AssistMultiviewStore;
|
||||
weeklyReportStore: WeeklyReportStore
|
||||
alertsStore: AlertStore
|
||||
|
||||
constructor() {
|
||||
this.dashboardStore = new DashboardStore();
|
||||
|
|
@ -61,6 +64,7 @@ export class RootStore {
|
|||
this.recordingsStore = new RecordingsStore();
|
||||
this.assistMultiviewStore = new AssistMultiviewStore();
|
||||
this.weeklyReportStore = new WeeklyReportStore();
|
||||
this.alertsStore = new AlertStore();
|
||||
}
|
||||
|
||||
initClient() {
|
||||
|
|
@ -75,6 +79,7 @@ export class RootStore {
|
|||
notesService.initClient(client)
|
||||
recordingsService.initClient(client);
|
||||
configService.initClient(client);
|
||||
alertsService.initClient(client)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { makeAutoObservable, observable, action, runInAction } from "mobx"
|
||||
import { makeAutoObservable, runInAction } from "mobx"
|
||||
import Widget from "./widget"
|
||||
import { dashboardService } from "App/services"
|
||||
import { toast } from 'react-toastify';
|
||||
|
|
@ -6,7 +6,7 @@ import { DateTime } from 'luxon';
|
|||
|
||||
export default class Dashboard {
|
||||
public static get ID_KEY():string { return "dashboardId" }
|
||||
dashboardId: any = undefined
|
||||
dashboardId?: string = undefined
|
||||
name: string = "Untitled Dashboard"
|
||||
description: string = ""
|
||||
isPublic: boolean = true
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ export default class Widget {
|
|||
widgetId: any = undefined
|
||||
category?: string = undefined
|
||||
name: string = "Untitled Card"
|
||||
// metricType: string = "timeseries"
|
||||
metricType: string = "timeseries"
|
||||
metricOf: string = "sessionCount"
|
||||
metricValue: string = ""
|
||||
|
|
@ -37,8 +36,6 @@ export default class Widget {
|
|||
period: Record<string, any> = Period({ rangeName: LAST_24_HOURS }) // temp value in detail view
|
||||
hasChanged: boolean = false
|
||||
|
||||
sessionsLoading: boolean = false
|
||||
|
||||
position: number = 0
|
||||
data: any = {
|
||||
sessions: [],
|
||||
|
|
@ -51,7 +48,6 @@ export default class Widget {
|
|||
isLoading: boolean = false
|
||||
isValid: boolean = false
|
||||
dashboardId: any = undefined
|
||||
colSpan: number = 2
|
||||
predefinedKey: string = ''
|
||||
|
||||
constructor() {
|
||||
|
|
@ -103,10 +99,6 @@ export default class Widget {
|
|||
return this
|
||||
}
|
||||
|
||||
setPeriod(period: any) {
|
||||
this.period = new Period({ start: period.startDate, end: period.endDate, rangeName: period.rangeName })
|
||||
}
|
||||
|
||||
toWidget(): any {
|
||||
return {
|
||||
config: {
|
||||
|
|
@ -117,10 +109,6 @@ export default class Widget {
|
|||
}
|
||||
}
|
||||
|
||||
toJsonDrilldown() {
|
||||
return this.series.map((series: any) => series.toJson())
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
metricId: this.metricId,
|
||||
|
|
@ -171,18 +159,6 @@ export default class Widget {
|
|||
})
|
||||
}
|
||||
|
||||
fetchIssues(filter: any): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
metricService.fetchIssues(filter).then((response: any) => {
|
||||
const significantIssues = response.issues.significant ? response.issues.significant.map((issue: any) => new Funnelissue().fromJSON(issue)) : []
|
||||
const insignificantIssues = response.issues.insignificant ? response.issues.insignificant.map((issue: any) => new Funnelissue().fromJSON(issue)) : []
|
||||
resolve({
|
||||
issues: significantIssues.length > 0 ? significantIssues : insignificantIssues,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fetchIssue(funnelId: any, issueId: any, params: any): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
metricService.fetchIssue(funnelId, issueId, params).then((response: any) => {
|
||||
|
|
|
|||
49
frontend/app/services/AlertsService.ts
Normal file
49
frontend/app/services/AlertsService.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import APIClient from 'App/api_client';
|
||||
import Alert, { IAlert } from "Types/alert";
|
||||
|
||||
export default class AlertsService {
|
||||
private client: APIClient;
|
||||
|
||||
constructor(client?: APIClient) {
|
||||
this.client = client ? client : new APIClient();
|
||||
}
|
||||
|
||||
initClient(client?: APIClient) {
|
||||
this.client = client || new APIClient();
|
||||
}
|
||||
|
||||
save(instance: Alert): Promise<IAlert> {
|
||||
return this.client.post(instance['alertId'] ? `/alerts/${instance['alertId']}` : '/alerts', instance.toData())
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {})
|
||||
.catch(Promise.reject)
|
||||
}
|
||||
|
||||
fetchTriggerOptions(): Promise<{ name: string, value: string | number }[]> {
|
||||
return this.client.get('/alerts/triggers')
|
||||
.then(r => r.json())
|
||||
.then(j => j.data || [])
|
||||
.catch(Promise.reject)
|
||||
}
|
||||
|
||||
fetchList(): Promise<IAlert[]> {
|
||||
return this.client.get('/alerts')
|
||||
.then(r => r.json())
|
||||
.then(j => j.data || [])
|
||||
.catch(Promise.reject)
|
||||
}
|
||||
|
||||
fetch(id: string): Promise<IAlert> {
|
||||
return this.client.get(`/alerts/${id}`)
|
||||
.then(r => r.json())
|
||||
.then(j => j.data || {})
|
||||
.catch(Promise.reject)
|
||||
}
|
||||
|
||||
remove(id: string): Promise<IAlert> {
|
||||
return this.client.delete(`/alerts/${id}`)
|
||||
.then(r => r.json())
|
||||
.then(j => j.data || {})
|
||||
.catch(Promise.reject)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import ErrorService from "./ErrorService";
|
|||
import NotesService from "./NotesService";
|
||||
import RecordingsService from "./RecordingsService";
|
||||
import ConfigService from './ConfigService'
|
||||
|
||||
import AlertsService from './AlertsService'
|
||||
export const dashboardService = new DashboardService();
|
||||
export const metricService = new MetricService();
|
||||
export const sessionService = new SessionSerivce();
|
||||
|
|
@ -19,3 +19,4 @@ export const errorService = new ErrorService();
|
|||
export const notesService = new NotesService();
|
||||
export const recordingsService = new RecordingsService();
|
||||
export const configService = new ConfigService();
|
||||
export const alertsService = new AlertsService();
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
import Record from 'Types/Record';
|
||||
import { notEmptyString, validateName, validateNumber, validateEmail } from 'App/validate';
|
||||
import { List, Map } from 'immutable';
|
||||
import { alertMetrics as metrics, alertConditions as conditions } from 'App/constants';
|
||||
// import Filter from './filter';
|
||||
|
||||
const metricsMap = {}
|
||||
const conditionsMap = {}
|
||||
metrics.forEach(m => { metricsMap[m.value] = m });
|
||||
conditions.forEach(c => { conditionsMap[c.value] = c });
|
||||
|
||||
export default Record({
|
||||
alertId: '',
|
||||
projectId: undefined,
|
||||
name: 'Untitled Alert',
|
||||
description: '',
|
||||
active: true,
|
||||
currentPeriod: 15,
|
||||
previousPeriod: 15,
|
||||
detectionMethod: 'threshold',
|
||||
change: 'change',
|
||||
query: Map({ left: '', operator: '', right: ''}),
|
||||
options: Map({ currentPeriod: 15, previousPeriod: 15 }),
|
||||
createdAt: undefined,
|
||||
|
||||
slack: false,
|
||||
slackInput: [],
|
||||
webhook: false,
|
||||
webhookInput: [],
|
||||
email: false,
|
||||
emailInput: [],
|
||||
msteams: false,
|
||||
msteamsInput: [],
|
||||
hasNotification: false,
|
||||
metric: '',
|
||||
condition: '',
|
||||
}, {
|
||||
idKey: 'alertId',
|
||||
methods: {
|
||||
validate() {
|
||||
return notEmptyString(this.name) &&
|
||||
this.query.left && this.query.right && validateNumber(this.query.right) && this.query.right > 0 && this.query.operator &&
|
||||
(this.slack ? this.slackInput.length > 0 : true) &&
|
||||
(this.email ? this.emailInput.length > 0 : true) &&
|
||||
(this.msteams ? this.msteamsInput.length > 0 : true) &&
|
||||
(this.webhook ? this.webhookInput.length > 0 : true);
|
||||
},
|
||||
toData() {
|
||||
const js = this.toJS();
|
||||
|
||||
const options = { message: [] }
|
||||
if (js.slack && js.slackInput)
|
||||
options.message = options.message.concat(js.slackInput.map(i => ({ type: 'slack', value: i })))
|
||||
// options.message.push({ type: 'slack', value: js.slackInput })
|
||||
if (js.email && js.emailInput)
|
||||
options.message = options.message.concat(js.emailInput.map(i => ({ type: 'email', value: i })))
|
||||
// options.message.push({ type: 'email', value: js.emailInput })
|
||||
if (js.webhook && js.webhookInput)
|
||||
options.message = options.message.concat(js.webhookInput.map(i => ({ type: 'webhook', value: i })))
|
||||
// options.message.push({ type: 'webhook', value: js.webhookInput })
|
||||
if (js.msteams && js.msteamsInput)
|
||||
options.message = options.message.concat(js.msteamsInput.map(i => ({ type: 'msteams', value: i })))
|
||||
|
||||
options.previousPeriod = js.previousPeriod
|
||||
options.currentPeriod = js.currentPeriod
|
||||
|
||||
js.detection_method = js.detectionMethod;
|
||||
delete js.slack;
|
||||
delete js.webhook;
|
||||
delete js.email;
|
||||
delete js.slackInput;
|
||||
delete js.webhookInput;
|
||||
delete js.emailInput;
|
||||
delete js.msteams;
|
||||
delete js.msteamsInput;
|
||||
delete js.hasNotification;
|
||||
delete js.metric;
|
||||
delete js.condition;
|
||||
delete js.currentPeriod;
|
||||
delete js.previousPeriod;
|
||||
|
||||
return { ...js, options: options };
|
||||
},
|
||||
},
|
||||
fromJS: (item) => {
|
||||
const options = item.options || { currentPeriod: 15, previousPeriod: 15, message: [] };
|
||||
const query = item.query || { left: '', operator: '', right: ''};
|
||||
|
||||
const slack = List(options.message).filter(i => i.type === 'slack');
|
||||
const email = List(options.message).filter(i => i.type === 'email');
|
||||
const webhook = List(options.message).filter(i => i.type === 'webhook');
|
||||
const msteams = List(options.message).filter(i => i.type === 'msteams');
|
||||
|
||||
return {
|
||||
...item,
|
||||
metric: metricsMap[query.left],
|
||||
condition: item.query ? conditionsMap[item.query.operator] : {},
|
||||
detectionMethod: item.detectionMethod || item.detection_method,
|
||||
query: query,
|
||||
options: options,
|
||||
previousPeriod: options.previousPeriod,
|
||||
currentPeriod: options.currentPeriod,
|
||||
|
||||
slack: slack.size > 0,
|
||||
slackInput: slack.map(i => parseInt(i.value)).toJS(),
|
||||
|
||||
msteams: msteams.size > 0,
|
||||
msteamsInput: msteams.map(i => parseInt(i.value)).toJS(),
|
||||
|
||||
email: email.size > 0,
|
||||
emailInput: email.map(i => i.value).toJS(),
|
||||
|
||||
webhook: webhook.size > 0,
|
||||
webhookInput: webhook.map(i => parseInt(i.value)).toJS(),
|
||||
|
||||
hasNotification: !!slack || !!email || !!webhook
|
||||
}
|
||||
},
|
||||
});
|
||||
197
frontend/app/types/alert.ts
Normal file
197
frontend/app/types/alert.ts
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
import { notEmptyString, validateNumber } from 'App/validate';
|
||||
import { alertMetrics as metrics, alertConditions as conditions } from 'App/constants';
|
||||
|
||||
const metricsMap = {}
|
||||
const conditionsMap = {}
|
||||
// @ts-ignore
|
||||
metrics.forEach(m => { metricsMap[m.value] = m });
|
||||
// @ts-ignore
|
||||
conditions.forEach(c => { conditionsMap[c.value] = c });
|
||||
|
||||
export interface IAlert {
|
||||
alertId: string;
|
||||
projectId?: string;
|
||||
name: string;
|
||||
description: string;
|
||||
active: boolean;
|
||||
currentPeriod: number;
|
||||
previousPeriod: number;
|
||||
detectionMethod: string;
|
||||
detection_method?: string;
|
||||
change: string;
|
||||
query: { left: string, operator: string, right: string };
|
||||
options: { currentPeriod: number, previousPeriod: number, message: {type: string, value: string}[] };
|
||||
createdAt?: number;
|
||||
slack: boolean;
|
||||
slackInput: string[];
|
||||
webhook: boolean;
|
||||
webhookInput: string[];
|
||||
email: boolean;
|
||||
emailInput: string[];
|
||||
msteams: boolean;
|
||||
msteamsInput: string[];
|
||||
hasNotification: boolean;
|
||||
metric: { unit: any };
|
||||
condition: string;
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
alertId: '',
|
||||
projectId: undefined,
|
||||
name: 'Untitled Alert',
|
||||
description: '',
|
||||
active: true,
|
||||
currentPeriod: 15,
|
||||
previousPeriod: 15,
|
||||
detectionMethod: 'threshold',
|
||||
change: 'change',
|
||||
query: { left: '', operator: '', right: '' },
|
||||
options: { currentPeriod: 15, previousPeriod: 15 },
|
||||
createdAt: undefined,
|
||||
|
||||
slack: false,
|
||||
slackInput: [],
|
||||
webhook: false,
|
||||
webhookInput: [],
|
||||
email: false,
|
||||
emailInput: [],
|
||||
msteams: false,
|
||||
msteamsInput: [],
|
||||
hasNotification: false,
|
||||
metric: '',
|
||||
condition: '',
|
||||
} as unknown as IAlert
|
||||
|
||||
export default class Alert {
|
||||
alertId: IAlert["alertId"]
|
||||
projectId?: IAlert["projectId"]
|
||||
name: IAlert["name"]
|
||||
description: IAlert["description"]
|
||||
active: IAlert["active"]
|
||||
currentPeriod: IAlert["currentPeriod"]
|
||||
previousPeriod: IAlert["previousPeriod"]
|
||||
detectionMethod: IAlert["detectionMethod"]
|
||||
detection_method: IAlert["detection_method"]
|
||||
change: IAlert["change"]
|
||||
query:IAlert["query"]
|
||||
options: IAlert["options"]
|
||||
createdAt?: IAlert["createdAt"]
|
||||
slack: IAlert["slack"]
|
||||
slackInput: IAlert["slackInput"]
|
||||
webhook: IAlert["webhook"]
|
||||
webhookInput: IAlert["webhookInput"]
|
||||
email: IAlert["email"]
|
||||
emailInput: IAlert["emailInput"]
|
||||
msteams: IAlert["msteams"]
|
||||
msteamsInput: IAlert["msteamsInput"]
|
||||
hasNotification: IAlert["hasNotification"]
|
||||
metric: IAlert["metric"]
|
||||
condition: IAlert["condition"]
|
||||
isExists = false
|
||||
|
||||
constructor(item: Partial<IAlert> = defaults, isExists: boolean) {
|
||||
Object.assign(defaults, item)
|
||||
|
||||
const options = defaults.options || { currentPeriod: 15, previousPeriod: 15, message: [] };
|
||||
const query = defaults.query || { left: '', operator: '', right: ''};
|
||||
|
||||
const slack = options.message?.filter(i => i.type === 'slack') || [];
|
||||
const email = options.message?.filter(i => i.type === 'email') || [];
|
||||
const webhook = options.message?.filter(i => i.type === 'webhook') || [];
|
||||
const msteams = options.message?.filter(i => i.type === 'msteams') || [];
|
||||
|
||||
Object.assign(this, {
|
||||
...defaults,
|
||||
// @ts-ignore
|
||||
metric: metricsMap[query.left],
|
||||
alertId: String(defaults.alertId),
|
||||
// @ts-ignore TODO
|
||||
condition: defaults.query ? conditionsMap[defaults.query.operator] : {},
|
||||
detectionMethod: defaults.detectionMethod || defaults.detection_method,
|
||||
query: query,
|
||||
options: options,
|
||||
previousPeriod: options.previousPeriod,
|
||||
currentPeriod: options.currentPeriod,
|
||||
|
||||
slack: slack.length > 0,
|
||||
slackInput: slack.map(i => parseInt(i.value)),
|
||||
|
||||
msteams: msteams.length > 0,
|
||||
msteamsInput: msteams.map(i => parseInt(i.value)),
|
||||
|
||||
email: email.length > 0,
|
||||
emailInput: email.map(i => i.value),
|
||||
|
||||
webhook: webhook.length > 0,
|
||||
webhookInput: webhook.map(i => parseInt(i.value)),
|
||||
|
||||
hasNotification: !!slack || !!email || !!webhook,
|
||||
isExists,
|
||||
})
|
||||
}
|
||||
|
||||
validate() {
|
||||
return notEmptyString(this.name) &&
|
||||
this.query.left && this.query.right && validateNumber(this.query.right) && parseInt(this.query.right, 10) > 0 && this.query.operator &&
|
||||
(this.slack ? this.slackInput.length > 0 : true) &&
|
||||
(this.email ? this.emailInput.length > 0 : true) &&
|
||||
(this.msteams ? this.msteamsInput.length > 0 : true) &&
|
||||
(this.webhook ? this.webhookInput.length > 0 : true);
|
||||
}
|
||||
|
||||
|
||||
toData() {
|
||||
const js = { ...this };
|
||||
|
||||
const options = { message: [], previousPeriod: 0, currentPeriod: 0 }
|
||||
if (js.slack && js.slackInput)
|
||||
// @ts-ignore
|
||||
options.message = options.message.concat(js.slackInput.map(i => ({ type: 'slack', value: i })))
|
||||
if (js.email && js.emailInput)
|
||||
// @ts-ignore
|
||||
options.message = options.message.concat(js.emailInput.map(i => ({ type: 'email', value: i })))
|
||||
if (js.webhook && js.webhookInput)
|
||||
// @ts-ignore
|
||||
options.message = options.message.concat(js.webhookInput.map(i => ({ type: 'webhook', value: i })))
|
||||
if (js.msteams && js.msteamsInput)
|
||||
// @ts-ignore
|
||||
options.message = options.message.concat(js.msteamsInput.map(i => ({ type: 'msteams', value: i })))
|
||||
|
||||
options.previousPeriod = js.previousPeriod
|
||||
options.currentPeriod = js.currentPeriod
|
||||
|
||||
js.detection_method = js.detectionMethod;
|
||||
// @ts-ignore
|
||||
delete js.slack;
|
||||
// @ts-ignore
|
||||
delete js.webhook;
|
||||
// @ts-ignore
|
||||
delete js.email;
|
||||
// @ts-ignore
|
||||
delete js.slackInput;
|
||||
// @ts-ignore
|
||||
delete js.webhookInput;
|
||||
// @ts-ignore
|
||||
delete js.emailInput;
|
||||
// @ts-ignore
|
||||
delete js.msteams;
|
||||
// @ts-ignore
|
||||
delete js.msteamsInput;
|
||||
// @ts-ignore
|
||||
delete js.hasNotification;
|
||||
// @ts-ignore
|
||||
delete js.metric;
|
||||
// @ts-ignore
|
||||
delete js.condition;
|
||||
// @ts-ignore
|
||||
delete js.currentPeriod;
|
||||
// @ts-ignore
|
||||
delete js.previousPeriod;
|
||||
|
||||
return { ...js, options: options };
|
||||
}
|
||||
|
||||
exists() {
|
||||
return this.isExists
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue