fix(ui): remove old alerts
This commit is contained in:
parent
f2333e10e1
commit
ef090aa696
26 changed files with 68 additions and 543 deletions
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
@ -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
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './ListItem';
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
.wrapper {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.viewed {
|
||||
background-color: $gray-lightest;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
.wrapper {
|
||||
&:hover {
|
||||
background-color: $active-blue;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $active-blue;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
.wrapper {
|
||||
background-color: white;
|
||||
color: $gray-dark;
|
||||
border: solid thin $gray-light;
|
||||
}
|
||||
|
||||
.alert {
|
||||
background: #C3E9EA;
|
||||
color: #32888C;
|
||||
border: none;
|
||||
}
|
||||
|
|
@ -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)} />
|
||||
))
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './Alerts'
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ function DashboardView(props: Props) {
|
|||
/>
|
||||
<AlertFormModal
|
||||
showModal={showAlertModal}
|
||||
onClose={() => dashboardStore.updateKey('showAlertModal', false)}
|
||||
onClose={() => dashboardStore.toggleAlertModal(false)}
|
||||
/>
|
||||
</div>
|
||||
</Loader>
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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(() => (
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -405,6 +405,10 @@ export default class DashboardStore {
|
|||
});
|
||||
}
|
||||
|
||||
toggleAlertModal(val: boolean) {
|
||||
this.showAlertModal = val
|
||||
}
|
||||
|
||||
fetchMetricChartData(
|
||||
metric: Widget,
|
||||
data: any,
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue