+
@@ -56,7 +84,12 @@ function AlertListItem(props: Props) {
- {checkForRecent(DateTime.fromMillis(alert.createdAt), 'LLL dd, yyyy, hh:mm a')}
+ {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'
+ )}
@@ -66,7 +99,8 @@ function AlertListItem(props: Props) {
{alert.query.left}
{' is '}
- {alert.query.operator}{alert.query.right} {alert.metric.unit}
+ {alert.query.operator}
+ {alert.query.right} {alert.metric.unit}
{' over the past '}
{getThreshold(alert.currentPeriod)}
@@ -77,7 +111,7 @@ function AlertListItem(props: Props) {
>
) : null}
{', notify me on '}
- {getNotifyChannel(alert)}.
+ {getNotifyChannel(alert, webhooks)}.
{alert.description ? (
{alert.description}
diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx
index ce14773c6..2e544399e 100644
--- a/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx
+++ b/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx
@@ -4,6 +4,7 @@ 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 AlertListItem from './AlertListItem'
@@ -14,13 +15,13 @@ interface Props {
list: any;
alertsSearch: any;
siteId: string;
- onDelete: (instance: Alert) => void;
- onSave: (instance: Alert) => void;
+ webhooks: Array
;
init: (instance?: Alert) => void
+ fetchWebhooks: () => void;
}
-function AlertsList({ fetchList, list: alertsList, alertsSearch, siteId, init }: Props) {
- React.useEffect(() => { fetchList() }, []);
+function AlertsList({ fetchList, list: alertsList, alertsSearch, siteId, init, fetchWebhooks, webhooks }: Props) {
+ React.useEffect(() => { fetchList(); fetchWebhooks() }, []);
const alertsArray = alertsList.toJS();
const [page, setPage] = React.useState(1);
@@ -50,7 +51,7 @@ function AlertsList({ fetchList, list: alertsList, alertsSearch, siteId, init }:
{sliceListPerPage(list, page - 1, pageSize).map((alert: any) => (
-
+
))}
@@ -78,6 +79,8 @@ export default connect(
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 }
+ { fetchList, fetchWebhooks }
)(AlertsList);
diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx
index e20e5719e..65c3f2ef8 100644
--- a/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx
+++ b/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx
@@ -1,10 +1,9 @@
import React from 'react';
-import { Button, PageTitle, Icon } from 'UI';
+import { Button, PageTitle, Icon, Link } from 'UI';
import withPageTitle from 'HOCs/withPageTitle';
import { connect } from 'react-redux';
-import { init, edit, save, remove } from 'Duck/alerts';
-import { confirm } from 'UI';
-import { toast } from 'react-toastify';
+import { init } from 'Duck/alerts';
+import { withSiteId, alertCreate } from 'App/routes';
import AlertsList from './AlertsList';
import AlertsSearch from './AlertsSearch';
@@ -12,58 +11,30 @@ import AlertsSearch from './AlertsSearch';
interface IAlertsView {
siteId: string;
init: (instance?: Alert) => any;
- save: (instance: Alert) => Promise
;
- remove: (alertId: string) => Promise;
}
-function AlertsView({ siteId, remove, save, init }: IAlertsView) {
-
- const onDelete = async (instance: Alert) => {
- if (
- await confirm({
- header: 'Confirm',
- confirmButton: 'Yes, delete',
- confirmation: `Are you sure you want to permanently delete this alert?`,
- })
- ) {
- remove(instance.alertId).then(() => {
- // toggleForm(null, false);
- });
- }
- };
- const onSave = (instance: Alert) => {
- const wasUpdating = instance.exists();
- save(instance).then(() => {
- if (!wasUpdating) {
- toast.success('New alert saved');
- // toggleForm(null, false);
- } else {
- toast.success('Alert updated');
- }
- });
- };
-
+function AlertsView({ siteId, init }: IAlertsView) {
return (
- A dashboard is a custom visualization using your OpenReplay data.
+ Alerts helps your team stay up to date with the activity on your app.
-
+
);
}
// @ts-ignore
-const Container = connect(null, { init, edit, save, remove })(AlertsView);
+const Container = connect(null, { init })(AlertsView);
export default withPageTitle('Alerts - OpenReplay')(Container);
diff --git a/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx b/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx
index 9df9ec85e..72143cf90 100644
--- a/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx
+++ b/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx
@@ -1,92 +1,159 @@
import React, { useEffect } from 'react';
-import { Button, Form, Input, SegmentSelection, Checkbox, Icon } from 'UI';
-import { alertConditions as conditions } from 'App/constants';
+import { Form, SegmentSelection, Icon } from 'UI';
import { connect } from 'react-redux';
-// @ts-ignore
-import stl from './alertForm.module.css';
-import DropdownChips from './DropdownChips';
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 } from 'App/constants/schedule';
+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 cn from 'classnames';
-import { fetchTriggerOptions } from 'Duck/alerts';
-import Select from 'Shared/Select';
+import WidgetName from '../WidgetName';
+import BottomButtons from './AlertForm/BottomButtons';
+import NotifyHooks from './AlertForm/NotifyHooks';
+import AlertListItem from './AlertListItem';
+import Condition from './AlertForm/Condition';
-const thresholdOptions = [
- { label: '15 minutes', value: 15 },
- { label: '30 minutes', value: 30 },
- { label: '1 hour', value: 60 },
- { label: '2 hours', value: 120 },
- { label: '4 hours', value: 240 },
- { label: '1 day', value: 1440 },
-];
-const changeOptions = [
- { label: 'change', value: 'change' },
- { label: '% change', value: 'percent' },
-];
-
-const Circle = ({ text }: { text: string}) => (
-
+const Circle = ({ text }: { text: string }) => (
+
{text}
);
interface ISection {
- index: string
- title: string
- description?: string
- content: React.ReactNode
+ index: string;
+ title: string;
+ description?: string;
+ content: React.ReactNode;
}
const Section = ({ index, title, description, content }: ISection) => (
-
-
+
+
-
+
{title}
{description &&
{description}
}
-
{content}
+
{content}
);
-interface IProps {
- instance: Alert
- style: Record
- slackChannels: any[]
- webhooks: any[]
- loading: boolean
- deleting: boolean
- triggerOptions: any[]
- onDelete: (instance: Alert) => void
- onClose: () => void
- fetchTriggerOptions: () => void
- edit: (query: any) => void
- onSubmit: (instance: Alert) => void
+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;
+ remove: (alertId: string) => Promise;
+ onSubmit: (instance: Alert) => void;
+ fetchWebhooks: () => void;
+ fetchList: () => void;
}
const NewAlert = (props: IProps) => {
const {
instance,
- slackChannels,
+ siteId,
webhooks,
loading,
- onDelete,
deleting,
triggerOptions,
- style,
+ init,
+ edit,
+ save,
+ remove,
+ fetchWebhooks,
+ fetchList,
+ list,
} = props;
- const write = ({ target: { value, name } }: React.ChangeEvent) => props.edit({ [name]: value });
- const writeOption = (_: React.ChangeEvent, { name, value }: { name: string, value: Record}) => props.edit({ [name]: value.value });
- const onChangeCheck = ({ target: { checked, name } }: React.ChangeEvent) => props.edit({ [name]: checked });
+ const [expanded, setExpanded] = React.useState(false);
useEffect(() => {
+ if (list.size === 0) fetchList();
props.fetchTriggerOptions();
+ fetchWebhooks();
}, []);
- const writeQueryOption = (e: React.ChangeEvent, { name, value }: { name: string, value: string }) => {
+ useEffect(() => {
+ if (list.size > 0) {
+ const alertId = location.pathname.split('/').pop()
+ const currentAlert = list.toJS().find((alert: Alert) => alert.alertId === parseInt(alertId, 10));
+ init(currentAlert);
+ }
+ }, [list])
+
+
+ const write = ({ target: { value, name } }: React.ChangeEvent) =>
+ props.edit({ [name]: value });
+ const writeOption = (
+ _: React.ChangeEvent,
+ { name, value }: { name: string; value: Record }
+ ) => props.edit({ [name]: value.value });
+ const onChangeCheck = ({ target: { checked, name } }: React.ChangeEvent) =>
+ props.edit({ [name]: checked });
+
+ const onDelete = async (instance: Alert) => {
+ if (
+ await confirm({
+ header: 'Confirm',
+ confirmButton: 'Yes, delete',
+ confirmation: `Are you sure you want to permanently delete this alert?`,
+ })
+ ) {
+ remove(instance.alertId).then(() => {
+ props.history.push(withSiteId(alerts(), siteId))
+ });
+ }
+ };
+ const onSave = (instance: Alert) => {
+ const wasUpdating = instance.exists();
+ save(instance).then(() => {
+ if (!wasUpdating) {
+ toast.success('New alert saved');
+ props.history.push(withSiteId(alerts(), siteId))
+ } else {
+ toast.success('Alert updated');
+ }
+ });
+ };
+
+ const onClose = () => {
+ props.history.push(withSiteId(alerts(), siteId))
+ }
+
+ const slackChannels = webhooks
+ .filter((hook) => hook.type === SLACK)
+ .map(({ webhookId, name }) => ({ value: webhookId, label: name }))
+ // @ts-ignore
+ .toJS();
+ const hooks = webhooks
+ .filter((hook) => hook.type === WEBHOOK)
+ .map(({ webhookId, name }) => ({ value: webhookId, label: name }))
+ // @ts-ignore
+ .toJS();
+
+
+
+ const writeQueryOption = (
+ e: React.ChangeEvent,
+ { name, value }: { name: string; value: string }
+ ) => {
const { query } = instance;
props.edit({ query: { ...query, [name]: value } });
};
@@ -104,291 +171,137 @@ const NewAlert = (props: IProps) => {
const isThreshold = instance.detectionMethod === 'threshold';
return (
-
- }
- />
-
-
-
-
-
-
-
-
-
-
- {instance.slack && (
-
-
-
- props.edit({ slackInput: selected })}
- />
-
-
- )}
-
- {instance.email && (
-
-
-
- props.edit({ emailInput: selected })}
- />
-
-
- )}
-
- {instance.webhook && (
-
-
- props.edit({ webhookInput: selected })}
- />
-
- )}
-
- }
- />
-
-
-
-
+
+
+
+
+ >
+ ) : null}
+
+
+
+ {instance && (
+
null} webhooks={webhooks} />
+ )}
-
+ >
);
};
-export default connect(
+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 }
-)(NewAlert);
+ { fetchTriggerOptions, init, edit, save, remove, fetchWebhooks, fetchList }
+ // @ts-ignore
+)(NewAlert));
diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx
index df8e198d8..470a43cb0 100644
--- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx
+++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx
@@ -180,5 +180,5 @@ function DashboardView(props: Props) {
);
}
-
+// @ts-ignore
export default withPageTitle('Dashboards - OpenReplay')(withReport(withRouter(withModal(observer(DashboardView)))));
diff --git a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx
index 85cefe70d..47512629f 100644
--- a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx
+++ b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx
@@ -6,8 +6,8 @@ import MetricsSearch from '../MetricsSearch';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
-interface Props{
- siteId: number;
+interface Props {
+ siteId: string;
}
function MetricsView({ siteId }: Props) {
const { metricStore } = useStore();