fix(ui): remove old alerts

This commit is contained in:
sylenien 2023-01-09 14:52:45 +01:00 committed by Delirium
parent f2333e10e1
commit ef090aa696
26 changed files with 68 additions and 543 deletions

View file

@ -43,7 +43,7 @@ const Section = ({ index, title, description, content }) => (
</div>
);
const AlertForm = (props) => {
function AlertForm(props) {
const {
slackChannels,
msTeamsChannels,
@ -53,28 +53,28 @@ const AlertForm = (props) => {
} = props;
const { alertsStore } = useStore()
const {
instance,
triggerOptions,
loading,
} = alertsStore
const instance = alertsStore.instance
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 });
const write = ({ target: { value, name } }) => alertsStore.edit({ [name]: value });
const writeOption = (e, { name, value }) => alertsStore.edit({ [name]: value.value });
const onChangeCheck = ({ target: { checked, name } }) => alertsStore.edit({ [name]: checked });
useEffect(() => {
alertsStore.fetchTriggerOptions();
void alertsStore.fetchTriggerOptions();
}, []);
const writeQueryOption = (e, { name, value }) => {
const { query } = instance;
props.edit({ query: { ...query, [name]: value } });
alertsStore.edit({ query: { ...query, [name]: value } });
};
const writeQuery = ({ target: { value, name } }) => {
const { query } = instance;
props.edit({ query: { ...query, [name]: value } });
alertsStore.edit({ query: { ...query, [name]: value } });
};
const metric =
@ -112,7 +112,7 @@ const AlertForm = (props) => {
primary
name="detectionMethod"
className="my-3"
onSelect={(e, { name, value }) => props.edit({ [name]: value })}
onSelect={(e, { name, value }) => alertsStore.edit({ [name]: value })}
value={{ value: instance.detectionMethod }}
list={[
{ name: 'Threshold', value: 'threshold' },
@ -294,7 +294,7 @@ const AlertForm = (props) => {
selected={instance.slackInput}
options={slackChannels}
placeholder="Select Channel"
onChange={(selected) => props.edit({ slackInput: selected })}
onChange={(selected) => alertsStore.edit({ slackInput: selected })}
/>
</div>
</div>
@ -308,7 +308,7 @@ const AlertForm = (props) => {
selected={instance.msteamsInput}
options={msTeamsChannels}
placeholder="Select Channel"
onChange={(selected) => props.edit({ msteamsInput: selected })}
onChange={(selected) => alertsStore.edit({ msteamsInput: selected })}
/>
</div>
</div>
@ -323,7 +323,7 @@ const AlertForm = (props) => {
validate={validateEmail}
selected={instance.emailInput}
placeholder="Type and press Enter key"
onChange={(selected) => props.edit({ emailInput: selected })}
onChange={(selected) => alertsStore.edit({ emailInput: selected })}
/>
</div>
</div>
@ -337,7 +337,7 @@ const AlertForm = (props) => {
selected={instance.webhookInput}
options={webhooks}
placeholder="Select Webhook"
onChange={(selected) => props.edit({ webhookInput: selected })}
onChange={(selected) => alertsStore.edit({ webhookInput: selected })}
/>
</div>
)}

View file

@ -5,10 +5,15 @@ 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 { SLACK, WEBHOOK } from 'App/constants/schedule';
import { SLACK, TEAMS, WEBHOOK } from 'App/constants/schedule';
import { confirm } from 'UI';
interface Select {
label: string;
value: string | number
}
interface Props {
showModal?: boolean;
metricId?: number;
@ -25,14 +30,23 @@ function AlertFormModal(props: Props) {
props.fetchWebhooks();
}, []);
const slackChannels = webhooks
.filter((hook) => hook.type === SLACK)
.map(({ webhookId, name }) => ({ value: webhookId, text: name }))
.toJS();
const hooks = webhooks
.filter((hook) => hook.type === WEBHOOK)
.map(({ webhookId, name }) => ({ value: webhookId, text: name }))
.toJS();
const slackChannels: Select[] = []
const hooks: Select[] = []
const msTeamsChannels: Select[] = []
webhooks.forEach((hook) => {
const option = { value: hook.webhookId, label: hook.name }
if (hook.type === SLACK) {
slackChannels.push(option)
}
if (hook.type === WEBHOOK) {
hooks.push(option)
}
if (hook.type === TEAMS) {
msTeamsChannels.push(option)
}
})
const saveAlert = (instance) => {
const wasUpdating = instance.exists();
@ -71,7 +85,7 @@ function AlertFormModal(props: Props) {
<SlideModal
title={
<div className="flex items-center">
<span className="mr-3">{'Create Alert'}</span>
<span className="m-3">{'Create Alert'}</span>
</div>
}
isDisplayed={showModal}
@ -83,6 +97,7 @@ function AlertFormModal(props: Props) {
metricId={metricId}
edit={alertsStore.edit}
slackChannels={slackChannels}
msTeamsChannels={msTeamsChannels}
webhooks={hooks}
onSubmit={saveAlert}
onClose={props.onClose}
@ -99,5 +114,5 @@ export default connect(
(state) => ({
webhooks: state.getIn(['webhooks', 'list']),
}),
{ fetchWebhooks, setShowAlerts }
{ fetchWebhooks }
)(observer(AlertFormModal));

View file

@ -1,57 +0,0 @@
import React from 'react'
import cn from 'classnames';
import stl from './alertItem.module.css';
import AlertTypeLabel from './AlertTypeLabel';
const AlertItem = props => {
const { alert, onEdit, active } = props;
const getThreshold = threshold => {
if (threshold === 15) return '15 Minutes';
if (threshold === 30) return '30 Minutes';
if (threshold === 60) return '1 Hour';
if (threshold === 120) return '2 Hours';
if (threshold === 240) return '4 Hours';
if (threshold === 1440) return '1 Day';
}
const getNotifyChannel = alert => {
let str = '';
if (alert.msteams)
str = 'MS Teams'
if (alert.slack)
str = 'Slack';
if (alert.email)
str += (str === '' ? '' : ' and ')+ 'Email';
if (alert.webhool)
str += (str === '' ? '' : ' and ')+ 'Webhook';
if (str === '')
return 'OpenReplay';
return str;
}
const isThreshold = alert.detectionMethod === 'threshold';
return (
<div
className={cn(stl.wrapper, 'p-4 py-6 relative group cursor-pointer', { [stl.active]: active })}
onClick={onEdit}
id="alert-item"
>
<AlertTypeLabel type={alert.detectionMethod} />
<div className="capitalize font-medium">{alert.name}</div>
<div className="mt-2 text-sm color-gray-medium">
{alert.detectionMethod === 'threshold' && (
<div>When <span className="italic font-medium">{alert.metric.text}</span> is {alert.condition.text} <span className="italic font-medium">{alert.query.right} {alert.metric.unit}</span> over the past <span className="italic font-medium">{getThreshold(alert.currentPeriod)}</span>, notify me on <span>{getNotifyChannel(alert)}</span>.</div>
)}
{alert.detectionMethod === 'change' && (
<div>When the <span className="italic font-medium">{alert.options.change}</span> of <span className="italic font-medium">{alert.metric.text}</span> is {alert.condition.text} <span className="italic font-medium">{alert.query.right} {alert.metric.unit}</span> over the past <span className="italic font-medium">{getThreshold(alert.currentPeriod)}</span> compared to the previous <span className="italic font-medium">{getThreshold(alert.previousPeriod)}</span>, notify me on {getNotifyChannel(alert)}.</div>
)}
</div>
</div>
)
}
export default AlertItem

View file

@ -1,13 +0,0 @@
import React from 'react'
import cn from 'classnames'
import stl from './alertTypeLabel.module.css'
function AlertTypeLabel({ filterKey, type = '' }) {
return (
<div className={ cn("rounded-full px-2 text-xs mb-2 fit-content uppercase color-gray-darkest", stl.wrapper, { [stl.alert] : filterKey === 'alert', }) }>
{ type }
</div>
)
}
export default AlertTypeLabel

View file

@ -1,109 +0,0 @@
import React, { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import AlertsList from './AlertsList';
import { SlideModal, IconButton } from 'UI';
import { init, edit, save, remove } from 'Duck/alerts';
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 { confirm } from 'UI';
const Alerts = (props) => {
const { webhooks, setShowAlerts } = props;
const [showForm, setShowForm] = useState(false);
useEffect(() => {
props.fetchWebhooks();
}, []);
const slackChannels = webhooks
.filter((hook) => hook.type === SLACK)
.map(({ webhookId, name }) => ({ value: webhookId, label: name }))
.toJS();
const hooks = webhooks
.filter((hook) => hook.type === WEBHOOK)
.map(({ webhookId, name }) => ({ value: webhookId, label: name }))
.toJS();
const saveAlert = (instance) => {
const wasUpdating = instance.exists();
props.save(instance).then(() => {
if (!wasUpdating) {
toast.success('New alert saved');
toggleForm(null, false);
} else {
toast.success('Alert updated');
}
});
};
const onDelete = async (instance) => {
if (
await confirm({
header: 'Confirm',
confirmButton: 'Yes, delete',
confirmation: `Are you sure you want to permanently delete this alert?`,
})
) {
props.remove(instance.alertId).then(() => {
toggleForm(null, false);
});
}
};
const toggleForm = (instance, state) => {
if (instance) {
props.init(instance);
}
return setShowForm(state ? state : !showForm);
};
return (
<div>
<SlideModal
title={
<div className="flex items-center">
<span className="mr-3">{'Alerts'}</span>
<IconButton circle size="small" icon="plus" outline id="add-button" onClick={() => toggleForm({}, true)} />
</div>
}
isDisplayed={true}
onClose={() => {
toggleForm({}, false);
setShowAlerts(false);
}}
size="small"
content={
<AlertsList
onEdit={(alert) => {
toggleForm(alert, true);
}}
onClickCreate={() => toggleForm({}, true)}
/>
}
detailContent={
showForm && (
<AlertForm
edit={props.edit}
slackChannels={slackChannels}
webhooks={hooks}
onSubmit={saveAlert}
onClose={() => toggleForm({}, false)}
onDelete={onDelete}
/>
)
}
/>
</div>
);
};
export default connect(
(state) => ({
webhooks: state.getIn(['webhooks', 'list']),
instance: state.getIn(['alerts', 'instance']),
}),
{ init, edit, save, remove, fetchWebhooks, setShowAlerts }
)(Alerts);

View file

@ -1,58 +0,0 @@
import React, { useEffect, useState } from 'react';
import { Loader, NoContent, Input, Button } from 'UI';
import AlertItem from './AlertItem';
import { fetchList } from 'Duck/alerts';
import { connect } from 'react-redux';
import { getRE } from 'App/utils';
const AlertsList = (props) => {
const { loading, list, instance, onEdit } = props;
const [query, setQuery] = useState('');
useEffect(() => {
props.fetchList();
}, []);
const filterRE = getRE(query, 'i');
const _filteredList = list.filter(({ name, query: { left } }) => filterRE.test(name) || filterRE.test(left));
return (
<div>
<div className="mb-3 w-full px-3">
<Input name="searchQuery" placeholder="Search by Name or Metric" onChange={({ target: { value } }) => setQuery(value)} />
</div>
<Loader loading={loading}>
<NoContent
title="No alerts have been setup yet."
subtext={
<div className="flex flex-col items-center">
<div>Alerts helps your team stay up to date with the activity on your app.</div>
<Button variant="primary" className="mt-4" icon="plus" onClick={props.onClickCreate}>
Create
</Button>
</div>
}
size="small"
show={list.size === 0}
>
<div className="bg-white">
{_filteredList.map((a) => (
<div className="border-b" key={a.key}>
<AlertItem active={instance.alertId === a.alertId} alert={a} onEdit={() => onEdit(a.toData())} />
</div>
))}
</div>
</NoContent>
</Loader>
</div>
);
};
export default connect(
(state) => ({
list: state.getIn(['alerts', 'list']).sort((a, b) => b.createdAt - a.createdAt),
instance: state.getIn(['alerts', 'instance']),
loading: state.getIn(['alerts', 'loading']),
}),
{ fetchList }
)(AlertsList);

View file

@ -1,33 +0,0 @@
import React from 'react';
import { Button } from 'UI';
import stl from './listItem.module.css';
import cn from 'classnames';
import AlertTypeLabel from '../../AlertTypeLabel';
const ListItem = ({ alert, onClear, loading, onNavigate }) => {
return (
<div className={cn(stl.wrapper, 'group', { [stl.viewed] : alert.viewed })}>
<div className="flex justify-between items-center">
<div className="text-sm">{alert.createdAt && alert.createdAt.toFormat('LLL dd, yyyy, hh:mm a')}</div>
<div className={ cn("invisible", { 'group-hover:visible' : !alert.viewed})} >
<Button variant="text" loading={loading}>
<span className={ cn("text-sm color-gray-medium", { 'invisible' : loading })} onClick={onClear}>{'IGNORE'}</span>
</Button>
</div>
</div>
<AlertTypeLabel
type={alert.options.sourceMeta}
filterKey={alert.filterKey}
/>
<div>
<h2 className="mb-2 flex items-center">
{alert.title}
</h2>
<div className="mb-2 text-sm text-justify break-all">{alert.description}</div>
</div>
</div>
)
}
export default ListItem

View file

@ -1 +0,0 @@
export { default } from './ListItem';

View file

@ -1,7 +0,0 @@
.wrapper {
padding: 15px;
}
.viewed {
background-color: $gray-lightest;
}

View file

@ -1,9 +0,0 @@
.wrapper {
&:hover {
background-color: $active-blue;
}
&.active {
background-color: $active-blue;
}
}

View file

@ -1,11 +0,0 @@
.wrapper {
background-color: white;
color: $gray-dark;
border: solid thin $gray-light;
}
.alert {
background: #C3E9EA;
color: #32888C;
border: none;
}

View file

@ -1,130 +0,0 @@
import { storiesOf } from '@storybook/react';
import { List } from 'immutable';
import Alert from 'Types/alert';
import Notification from 'Types/notification';
import Alerts from '.';
import Notifications from './Notifications';
import AlertsList from './AlertsList';
import AlertForm from './AlertForm';
const list = [
{
"alertId": 2,
"projectId": 1,
"name": "new alert",
"description": null,
"active": true,
"threshold": 240,
"detectionMethod": "threshold",
"query": {
"left": "avgPageLoad",
"right": 1.0,
"operator": ">="
},
"createdAt": 1591893324078,
"options": {
"message": [
{
"type": "slack",
"value": "51"
},
],
"LastNotification": 1592929583000,
"renotifyInterval": 120
}
},
{
"alertId": 14,
"projectId": 1,
"name": "alert 19.06",
"description": null,
"active": true,
"threshold": 30,
"detectionMethod": "threshold",
"query": {
"left": "avgPageLoad",
"right": 3000.0,
"operator": ">="
},
"createdAt": 1592579750935,
"options": {
"message": [
{
"type": "slack",
"value": "51"
}
],
"renotifyInterval": 120
}
},
{
"alertId": 15,
"projectId": 1,
"name": "notify every 60min",
"description": null,
"active": true,
"threshold": 30,
"detectionMethod": "threshold",
"query": {
"left": "avgPageLoad",
"right": 1.0,
"operator": ">="
},
"createdAt": 1592848779604,
"options": {
"message": [
{
"type": "slack",
"value": "51"
},
],
"LastNotification": 1599135058000,
"renotifyInterval": 60
}
},
{
"alertId": 21,
"projectId": 1,
"name": "always notify",
"description": null,
"active": true,
"threshold": 30,
"detectionMethod": "threshold",
"query": {
"left": "avgPageLoad",
"right": 1.0,
"operator": ">="
},
"createdAt": 1592849011350,
"options": {
"message": [
{
"type": "slack",
"value": "51"
}
],
"LastNotification": 1599135058000,
"renotifyInterval": 10
}
}
]
const notifications = List([
{ title: 'test', type: 'change', createdAt: 1591893324078, description: 'Lorem ipusm'},
{ title: 'test', type: 'threshold', createdAt: 1591893324078, description: 'Lorem ipusm'},
{ title: 'test', type: 'threshold', createdAt: 1591893324078, description: 'Lorem ipusm'},
{ title: 'test', type: 'threshold', createdAt: 1591893324078, description: 'Lorem ipusm'},
])
storiesOf('Alerts', module)
.add('Alerts', () => (
<Alerts />
))
.add('List', () => (
<AlertsList list={List(list).map(Alert)} />
))
.add('AlertForm', () => (
<AlertForm />
))
.add('AlertNotifications', () => (
<Notifications announcements={notifications.map(Notification)} />
))

View file

@ -1 +0,0 @@
export { default } from './Alerts'

View file

@ -131,7 +131,7 @@ function AlertListItem(props: Props) {
{' is '}
<span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas' }}>
{alert.query.operator}
{numberWithCommas(alert.query.right)} {alert.metric.unit}
{numberWithCommas(alert.query.right)} {alert.metric?.unit}
</span>
{' over the past '}
<span className="font-semibold" style={{ fontFamily: 'Menlo, Monaco, Consolas' }}>{getThreshold(

View file

@ -87,7 +87,7 @@ function DashboardView(props: Props) {
/>
<AlertFormModal
showModal={showAlertModal}
onClose={() => dashboardStore.updateKey('showAlertModal', false)}
onClose={() => dashboardStore.toggleAlertModal(false)}
/>
</div>
</Loader>

View file

@ -19,7 +19,7 @@ function FunnelIssueDetails(props: Props) {
useEffect(() => {
setLoading(true);
const _filters = { ...filter, series: widget.data.stages ? widget.toJsonDrilldown().map((item: any) => {
const _filters = { ...filter, series: widget.data.stages ? widget.series.map((item: any) => {
return {
...item,
filter: {

View file

@ -49,7 +49,7 @@ function FunnelIssues() {
const depsString = JSON.stringify(widget.series);
useEffect(() => {
debounceRequest({ ...filter, series: widget.toJsonDrilldown(), page: metricStore.sessionsPage, limit: metricStore.sessionsPageSize });
debounceRequest({ ...filter, series: widget.series, page: metricStore.sessionsPage, limit: metricStore.sessionsPageSize });
}, [stages.length, drillDownPeriod, filter.filters, depsString, metricStore.sessionsPage]);
return useObserver(() => (

View file

@ -81,9 +81,10 @@ function WidgetSessions(props: Props) {
const customFilter = { ...filter, ...timeRange, filters: [ ...sessionStore.userFilter.filters, clickFilter]}
debounceClickMapSearch(customFilter)
} else {
console.log(widget)
debounceRequest(widget.metricId, {
...filter,
series: widget.toJsonDrilldown(),
series: widget.series,
page: metricStore.sessionsPage,
limit: metricStore.sessionsPageSize,
});

View file

@ -1,7 +1,5 @@
import React from 'react';
import { connect } from 'react-redux';
import WidgetIcon from './WidgetIcon';
import { init as initAlert } from 'Duck/alerts';
import { useStore } from 'App/mstore';
interface Props {
@ -9,20 +7,21 @@ interface Props {
initAlert: Function;
}
function AlertButton(props: Props) {
const { seriesId, initAlert } = props;
const { dashboardStore } = useStore();
const { seriesId } = props;
const { dashboardStore, alertsStore } = useStore();
const onClick = () => {
initAlert({ query: { left: seriesId }})
dashboardStore.updateKey('showAlertModal', true);
dashboardStore.toggleAlertModal(true);
alertsStore.init({ query: { left: seriesId }})
}
return (
<div onClick={onClick}>
<WidgetIcon
className="cursor-pointer"
icon="bell-plus"
tooltip="Set Alert"
onClick={onClick}
/>
</div>
);
}
export default connect(null, { initAlert })(AlertButton);
export default AlertButton;

View file

@ -3,15 +3,14 @@ import { Icon, Tooltip } from 'UI';
interface Props {
className: string;
onClick: () => void;
icon: string;
tooltip: string;
}
function WidgetIcon(props: Props) {
const { className, onClick, icon, tooltip } = props;
const { className, icon, tooltip } = props;
return (
<Tooltip title={tooltip}>
<div className={className} onClick={onClick}>
<div className={className}>
{/* @ts-ignore */}
<Icon name={icon} size="14" />
</div>

View file

@ -12,7 +12,6 @@ import { init as initSite } from 'Duck/site';
import { getInitials } from 'App/utils';
import ErrorGenPanel from 'App/dev/components';
import Alerts from '../Alerts/Alerts';
import { fetchListActive as fetchMetadata } from 'Duck/customField';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
@ -91,8 +90,6 @@ const Header = (props) => {
{<ErrorGenPanel />}
</div>
{showAlerts && <Alerts />}
</div>
);
};
@ -103,7 +100,6 @@ export default withRouter(
account: state.getIn(['user', 'account']),
siteId: state.getIn(['site', 'siteId']),
sites: state.getIn(['site', 'list']),
showAlerts: state.getIn(['dashboard', 'showAlerts']),
boardingCompletion: state.getIn(['dashboard', 'boardingCompletion']),
}),
{ onLogoutClick: logout, initSite, fetchMetadata }

View file

@ -1,63 +0,0 @@
import Alert from 'Types/alert';
import { Map } from 'immutable';
import crudDuckGenerator from './tools/crudDuck';
import { RequestTypes } from 'Duck/requestStateCreator';
import { reduceDucks } from 'Duck/tools';
const name = 'alert'
const idKey = 'alertId';
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: [],
alertsSearch: '',
});
const reducer = (state = initialState, action = {}) => {
switch (action.type) {
// case GENERATE_LINK.SUCCESS:
// return state.update(
// 'list',
// list => list
// .map(member => {
// if(member.id === action.id) {
// return Member({...member.toJS(), invitationLink: action.data.invitationLink })
// }
// return member
// })
// );
case CHANGE_SEARCH:
return state.set('alertsSearch', action.search);
case FETCH_TRIGGER_OPTIONS.SUCCESS:
return state.set('triggerOptions', action.data.map(({ name, value }) => ({ label: name, value })));
}
return state;
};
export function save(instance) {
return {
types: crudDuck.actionTypes.SAVE.toArray(),
call: client => client.post( instance[idKey] ? `/alerts/${ instance[idKey] }` : '/alerts', instance.toData()),
};
}
export function changeSearch(search) {
return {
type: CHANGE_SEARCH,
search,
};
}
export function fetchTriggerOptions() {
return {
types: FETCH_TRIGGER_OPTIONS.toArray(),
call: client => client.get('/alerts/triggers'),
};
}
// export default crudDuck.reducer;
export default reduceDucks(crudDuck, { initialState, reducer }).reducer;

View file

@ -7,7 +7,6 @@ import assignments from './assignments';
import filters from './filters';
import funnelFilters from './funnelFilters';
import templates from './templates';
import alerts from './alerts';
import dashboard from './dashboard';
import components from './components';
import sources from './sources';
@ -32,7 +31,6 @@ const rootReducer = combineReducers({
funnelFilters,
templates,
alerts,
dashboard,
components,
members,

View file

@ -6,7 +6,7 @@ export default class AlertsStore {
alerts: Alert[] = [];
triggerOptions: { label: string, value: string | number, unit?: string }[] = [];
alertsSearch = '';
// @ts-ignore
// @ts-ignore
instance: Alert = new Alert({}, false);
loading = false
@ -70,6 +70,8 @@ export default class AlertsStore {
}
edit = (diff: Partial<Alert>) => {
Object.assign(this.instance, diff)
const key = Object.keys(diff)[0]
// @ts-ignore
this.instance[key] = diff[key]
}
}
}

View file

@ -405,6 +405,10 @@ export default class DashboardStore {
});
}
toggleAlertModal(val: boolean) {
this.showAlertModal = val
}
fetchMetricChartData(
metric: Widget,
data: any,

View file

@ -1,5 +1,6 @@
import { notEmptyString, validateNumber } from 'App/validate';
import { alertMetrics as metrics, alertConditions as conditions } from 'App/constants';
import { makeAutoObservable } from 'mobx'
const metricsMap = {}
const conditionsMap = {}
@ -73,7 +74,7 @@ export default class Alert {
detectionMethod: IAlert["detectionMethod"]
detection_method: IAlert["detection_method"]
change: IAlert["change"]
query:IAlert["query"]
query: IAlert["query"]
options: IAlert["options"]
createdAt?: IAlert["createdAt"]
slack: IAlert["slack"]
@ -128,6 +129,8 @@ export default class Alert {
hasNotification: !!slack || !!email || !!webhook,
isExists,
})
makeAutoObservable(this)
}
validate() {