diff --git a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx
index 48a46a3a0..93999281a 100644
--- a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx
+++ b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx
@@ -2,9 +2,7 @@ import React, { useEffect, useState } from 'react';
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 { SLACK, TEAMS, WEBHOOK } from 'App/constants/schedule';
import { confirm } from 'UI';
@@ -18,16 +16,14 @@ interface Props {
showModal?: boolean;
metricId?: number;
onClose?: () => void;
- webhooks: any;
- fetchWebhooks: Function;
}
function AlertFormModal(props: Props) {
- const { alertsStore } = useStore()
- const { metricId = null, showModal = false, webhooks } = props;
+ const { alertsStore, settingsStore } = useStore()
+ const { metricId = null, showModal = false } = props;
const [showForm, setShowForm] = useState(false);
-
+ const webhooks = settingsStore.webhooks
useEffect(() => {
- props.fetchWebhooks();
+ settingsStore.fetchWebhooks();
}, []);
@@ -110,9 +106,4 @@ function AlertFormModal(props: Props) {
);
}
-export default connect(
- (state) => ({
- webhooks: state.getIn(['webhooks', 'list']),
- }),
- { fetchWebhooks }
-)(observer(AlertFormModal));
+export default observer(AlertFormModal);
diff --git a/frontend/app/components/Client/Webhooks/WebhookForm.js b/frontend/app/components/Client/Webhooks/WebhookForm.js
index b64a63af8..c918dcb93 100644
--- a/frontend/app/components/Client/Webhooks/WebhookForm.js
+++ b/frontend/app/components/Client/Webhooks/WebhookForm.js
@@ -1,94 +1,74 @@
import React from 'react';
-import { connect } from 'react-redux';
-import { edit, save } from 'Duck/webhook';
import { Form, Button, Input } from 'UI';
import styles from './webhookForm.module.css';
+import { useStore } from 'App/mstore'
+import { observer } from 'mobx-react-lite'
-@connect(
- (state) => ({
- webhook: state.getIn(['webhooks', 'instance']),
- loading: state.getIn(['webhooks', 'saveRequest', 'loading']),
- }),
- {
- edit,
- save,
- }
-)
-class WebhookForm extends React.PureComponent {
- setFocus = () => this.focusElement.focus();
- onChangeSelect = (event, { name, value }) => this.props.edit({ [name]: value });
- write = ({ target: { value, name } }) => this.props.edit({ [name]: value });
+function WebhookForm(props) {
+ const { settingsStore } = useStore()
+ const { webhookInst: webhook, hooksLoading: loading, saveWebhook, editWebhook } = settingsStore
+ const write = ({ target: { value, name } }) => editWebhook({ [name]: value });
- save = () => {
- this.props.save(this.props.webhook).then(() => {
- this.props.onClose();
+ const save = () => {
+ saveWebhook(webhook).then(() => {
+ props.onClose();
});
};
- render() {
- const { webhook, loading } = this.props;
- return (
-
-
{webhook.exists() ? 'Update' : 'Add'} Webhook
-
-
- {
- this.focusElement = ref;
- }}
- name="name"
- value={webhook.name}
- onChange={this.write}
- placeholder="Name"
- />
-
-
-
- {
- this.focusElement = ref;
- }}
- name="endpoint"
- value={webhook.endpoint}
- onChange={this.write}
- placeholder="Endpoint"
- />
-
+ return (
+
+
{webhook.exists() ? 'Update' : 'Add'} Webhook
+
+
+
+
-
-
- {
- this.focusElement = ref;
- }}
- name="authHeader"
- value={webhook.authHeader}
- onChange={this.write}
- placeholder="Auth Header"
- />
-
+
+
+
+
-
-
-
- {webhook.exists() && }
-
- {webhook.exists() &&
}
+
+
+
+
+
+
+
+
+ {webhook.exists() && }
-
-
- );
- }
+ {webhook.exists() &&
+
}
+
+
+
+ );
}
-export default WebhookForm;
+export default observer(WebhookForm);
diff --git a/frontend/app/components/Client/Webhooks/Webhooks.js b/frontend/app/components/Client/Webhooks/Webhooks.js
index a87ac2298..0e1a0214c 100644
--- a/frontend/app/components/Client/Webhooks/Webhooks.js
+++ b/frontend/app/components/Client/Webhooks/Webhooks.js
@@ -1,9 +1,7 @@
import React, { useEffect } from 'react';
-import { connect } from 'react-redux';
import cn from 'classnames';
import withPageTitle from 'HOCs/withPageTitle';
import { Button, Loader, NoContent, Icon } from 'UI';
-import { init, fetchList, remove } from 'Duck/webhook';
import WebhookForm from './WebhookForm';
import ListItem from './ListItem';
import styles from './webhooks.module.css';
@@ -11,79 +9,71 @@ import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import { confirm } from 'UI';
import { toast } from 'react-toastify';
import { useModal } from 'App/components/Modal';
+import { useStore } from 'App/mstore';
+import { observer } from 'mobx-react-lite'
-function Webhooks(props) {
- const { webhooks, loading } = props;
- const { showModal, hideModal } = useModal();
+function Webhooks() {
+ const { settingsStore } = useStore()
+ const { webhooks, hooksLoading: loading } = settingsStore;
+ const { showModal, hideModal } = useModal();
- const noSlackWebhooks = webhooks.filter((hook) => hook.type === 'webhook');
- useEffect(() => {
- props.fetchList();
- }, []);
+ const noSlackWebhooks = webhooks.filter((hook) => hook.type === 'webhook');
+ useEffect(() => {
+ void settingsStore.fetchWebhooks();
+ }, []);
- const init = (v) => {
- props.init(v);
- showModal(
);
- };
+ const init = (v) => {
+ settingsStore.initWebhook(v);
+ showModal(
);
+ };
- const removeWebhook = async (id) => {
- if (
- await confirm({
- header: 'Confirm',
- confirmButton: 'Yes, delete',
- confirmation: `Are you sure you want to remove this webhook?`,
- })
- ) {
- props.remove(id).then(() => {
- toast.success('Webhook removed successfully');
- });
- hideModal();
- }
- };
+ const removeWebhook = async (id) => {
+ if (
+ await confirm({
+ header: 'Confirm',
+ confirmButton: 'Yes, delete',
+ confirmation: `Are you sure you want to remove this webhook?`,
+ })
+ ) {
+ settingsStore.removeWebhook(id).then(() => {
+ toast.success('Webhook removed successfully');
+ });
+ hideModal();
+ }
+ };
- return (
-
-
-
{'Webhooks'}
- {/*
-
-
-
- Leverage webhooks to push OpenReplay data to other systems.
-
-
-
-
-
- None added yet
+ return (
+
+
+
{'Webhooks'}
+ init()}>Add Webhook
- }
- size="small"
- show={noSlackWebhooks.size === 0}
- >
-
- {noSlackWebhooks.map((webhook) => (
- init(webhook)} />
- ))}
-
-
-
-
- );
+
+
+
+ Leverage webhooks to push OpenReplay data to other systems.
+
+
+
+
+
+ None added yet
+
+ }
+ size="small"
+ show={noSlackWebhooks.length === 0}
+ >
+
+ {noSlackWebhooks.map((webhook) => (
+ init(webhook)} />
+ ))}
+
+
+
+
+ );
}
-export default connect(
- (state) => ({
- webhooks: state.getIn(['webhooks', 'list']),
- loading: state.getIn(['webhooks', 'loading']),
- }),
- {
- init,
- fetchList,
- remove,
- }
-)(withPageTitle('Webhooks - OpenReplay Preferences')(Webhooks));
+export default withPageTitle('Webhooks - OpenReplay Preferences')(observer(Webhooks));
diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx
index a6f20b449..e4005098e 100644
--- a/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx
+++ b/frontend/app/components/Dashboard/components/Alerts/AlertsList.tsx
@@ -1,25 +1,21 @@
import React from 'react';
-import { NoContent, Pagination, Icon } from 'UI';
+import { NoContent, Pagination } from 'UI';
import { filterList } from 'App/utils';
import { sliceListPerPage } from 'App/utils';
-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 {
siteId: string;
- webhooks: Array;
- fetchWebhooks: () => void;
}
-function AlertsList({ siteId, fetchWebhooks, webhooks }: Props) {
- const { alertsStore } = useStore();
+function AlertsList({ siteId }: Props) {
+ const { alertsStore, settingsStore } = useStore();
+ const { fetchWebhooks, webhooks } = settingsStore
const { alerts: alertsList, alertsSearch, fetchList, init } = alertsStore
React.useEffect(() => { fetchList(); fetchWebhooks() }, []);
@@ -72,10 +68,4 @@ function AlertsList({ siteId, fetchWebhooks, webhooks }: Props) {
);
}
-export default connect(
- (state) => ({
- // @ts-ignore
- webhooks: state.getIn(['webhooks', 'list']),
- }),
- { fetchWebhooks }
-)(observer(AlertsList));
+export default observer(AlertsList);
diff --git a/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx b/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx
index 4ad94e8a0..aa6c18714 100644
--- a/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx
+++ b/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx
@@ -5,7 +5,6 @@ import { validateEmail } from 'App/validate';
import { confirm } from 'UI';
import { toast } from 'react-toastify';
import { SLACK, WEBHOOK, TEAMS } 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';
@@ -57,17 +56,15 @@ interface Select {
interface IProps extends RouteComponentProps {
siteId: string;
slackChannels: any[];
- webhooks: any[];
loading: boolean;
deleting: boolean;
triggerOptions: any[];
list: any;
onSubmit: (instance: Alert) => void;
- fetchWebhooks: () => void;
}
const NewAlert = (props: IProps) => {
- const { alertsStore } = useStore();
+ const { alertsStore, settingsStore } = useStore();
const {
fetchTriggerOptions,
init,
@@ -81,11 +78,10 @@ const NewAlert = (props: IProps) => {
loading,
} = alertsStore
const deleting = loading
-
+ const webhooks = settingsStore.webhooks
+ const fetchWebhooks = settingsStore.fetchWebhooks
const {
siteId,
- webhooks,
- fetchWebhooks,
} = props;
useEffect(() => {
@@ -288,12 +284,4 @@ const NewAlert = (props: IProps) => {
);
};
-export default withRouter(
- connect(
- (state) => ({
- // @ts-ignore
- webhooks: state.getIn(['webhooks', 'list']),
- }),
- { fetchWebhooks }
- )(observer(NewAlert))
-);
+export default withRouter(observer(NewAlert))
diff --git a/frontend/app/duck/index.ts b/frontend/app/duck/index.ts
index 5133d2a10..d9412eedc 100644
--- a/frontend/app/duck/index.ts
+++ b/frontend/app/duck/index.ts
@@ -13,7 +13,6 @@ import sources from './sources';
import members from './member';
import site from './site';
import customFields from './customField';
-import webhooks from './webhook';
import integrations from './integrations';
import rehydrate from './rehydrate';
import errors from './errors';
@@ -36,7 +35,6 @@ const rootReducer = combineReducers({
members,
site,
customFields,
- webhooks,
rehydrate,
errors,
funnels,
diff --git a/frontend/app/duck/webhook.js b/frontend/app/duck/webhook.js
deleted file mode 100644
index 8dc323a75..000000000
--- a/frontend/app/duck/webhook.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import Webhook from 'Types/webhook';
-import crudDuckGenerator from './tools/crudDuck';
-
-const crudDuck = crudDuckGenerator('webhook', Webhook, { idKey: 'webhookId' });
-export const { fetchList, init, edit, save, remove } = crudDuck.actions;
-
-export default crudDuck.reducer;
diff --git a/frontend/app/mstore/index.tsx b/frontend/app/mstore/index.tsx
index b9567c817..e177d86d0 100644
--- a/frontend/app/mstore/index.tsx
+++ b/frontend/app/mstore/index.tsx
@@ -6,17 +6,7 @@ import RoleStore from './roleStore';
import APIClient from 'App/api_client';
import FunnelStore from './funnelStore';
import {
- dashboardService,
- metricService,
- sessionService,
- userService,
- auditService,
- funnelService,
- errorService,
- notesService,
- recordingsService,
- configService,
- alertsService,
+ services
} from 'App/services';
import SettingsStore from './settingsStore';
import AuditStore from './auditStore';
@@ -69,17 +59,9 @@ export class RootStore {
initClient() {
const client = new APIClient();
- dashboardService.initClient(client);
- metricService.initClient(client);
- funnelService.initClient(client);
- sessionService.initClient(client);
- userService.initClient(client);
- auditService.initClient(client);
- errorService.initClient(client);
- notesService.initClient(client)
- recordingsService.initClient(client);
- configService.initClient(client);
- alertsService.initClient(client)
+ services.forEach(service => {
+ service.initClient(client);
+ })
}
}
diff --git a/frontend/app/mstore/settingsStore.ts b/frontend/app/mstore/settingsStore.ts
index 45cb9610c..dc4f6baa4 100644
--- a/frontend/app/mstore/settingsStore.ts
+++ b/frontend/app/mstore/settingsStore.ts
@@ -2,44 +2,89 @@ import { makeAutoObservable, observable, action } from "mobx"
import SessionSettings from "./types/sessionSettings"
import { sessionService } from "App/services"
import { toast } from 'react-toastify';
+import Webhook, { IWebhook } from 'Types/webhook';
+import {
+ webhookService
+} from 'App/services';
+import Alert, { IAlert } from "Types/alert";
export default class SettingsStore {
- loadingCaptureRate: boolean = false;
- sessionSettings: SessionSettings = new SessionSettings()
- captureRateFetched: boolean = false;
- limits: any = null;
+ loadingCaptureRate: boolean = false;
+ sessionSettings: SessionSettings = new SessionSettings()
+ captureRateFetched: boolean = false;
+ limits: any = null;
- constructor() {
- makeAutoObservable(this, {
- sessionSettings: observable,
+ webhooks: Webhook[] = []
+ webhookInst = new Webhook()
+
+ hooksLoading = false
+
+ constructor() {
+ makeAutoObservable(this, {
+ sessionSettings: observable,
+ })
+ }
+
+ saveCaptureRate(data: any) {
+ return sessionService.saveCaptureRate(data)
+ .then(data => data.json())
+ .then(({ data }) => {
+ this.sessionSettings.merge({
+ captureRate: data.rate,
+ captureAll: data.captureAll
})
- }
+ toast.success("Settings updated successfully");
+ }).catch(err => {
+ toast.error("Error saving capture rate");
+ })
+ }
- saveCaptureRate(data: any) {
- return sessionService.saveCaptureRate(data)
- .then(data => data.json())
- .then(({ data }) => {
- this.sessionSettings.merge({
- captureRate: data.rate,
- captureAll: data.captureAll
- })
- toast.success("Settings updated successfully");
- }).catch(err => {
- toast.error("Error saving capture rate");
- })
- }
+ fetchCaptureRate(): Promise {
+ this.loadingCaptureRate = true;
+ return sessionService.fetchCaptureRate()
+ .then(data => {
+ this.sessionSettings.merge({
+ captureRate: data.rate,
+ captureAll: data.captureAll
+ })
+ this.captureRateFetched = true;
+ }).finally(() => {
+ this.loadingCaptureRate = false;
+ })
+ }
- fetchCaptureRate(): Promise {
- this.loadingCaptureRate = true;
- return sessionService.fetchCaptureRate()
- .then(data => {
- this.sessionSettings.merge({
- captureRate: data.rate,
- captureAll: data.captureAll
- })
- this.captureRateFetched = true;
- }).finally(() => {
- this.loadingCaptureRate = false;
- })
- }
+ fetchWebhooks = () => {
+ this.hooksLoading = true
+ return webhookService.fetchList()
+ .then(data => {
+ this.webhooks = data.map(hook => new Webhook(hook))
+ this.hooksLoading = false
+ })
+ }
+
+ initWebhook = (inst: Partial | Webhook) => {
+ this.webhookInst = inst instanceof Webhook ? inst : new Webhook(inst)
+ }
+
+ saveWebhook = (inst: Webhook) => {
+ this.hooksLoading = true
+ return webhookService.saveWebhook(inst)
+ .then(data => {
+ this.webhookInst = new Webhook(data)
+ this.hooksLoading = false
+ })
+ }
+
+ removeWebhook = (hookId: string) => {
+ this.hooksLoading = true
+ return webhookService.removeWebhook(hookId)
+ .then(() => {
+ this.webhooks = this.webhooks.filter(hook => hook.webhookId!== hookId)
+ this.hooksLoading = false
+ })
+ }
+
+ editWebhook = (diff: Partial) => {
+ Object.assign(this.webhookInst, diff)
+ }
}
diff --git a/frontend/app/services/WebhookService.ts b/frontend/app/services/WebhookService.ts
new file mode 100644
index 000000000..2bcefa619
--- /dev/null
+++ b/frontend/app/services/WebhookService.ts
@@ -0,0 +1,25 @@
+import BaseService from './BaseService';
+import Webhook, { IWebhook } from "Types/webhook";
+
+export default class WebhookService extends BaseService {
+ fetchList(): Promise {
+ return this.client.get('/webhooks')
+ .then(r => r.json())
+ .then(j => j.data || [])
+ .catch(Promise.reject)
+ }
+
+ saveWebhook(inst: Webhook) {
+ return this.client.put('/webhooks', inst)
+ .then(r => r.json())
+ .then(j => j.data || {})
+ .catch(Promise.reject)
+ }
+
+ removeWebhook(id: Webhook["webhookId"]) {
+ return this.client.delete('/webhooks/' + id)
+ .then(r => r.json())
+ .then(j => j.data || {})
+ .catch(Promise.reject)
+ }
+}
\ No newline at end of file
diff --git a/frontend/app/services/index.ts b/frontend/app/services/index.ts
index 72d0f4d92..816113e68 100644
--- a/frontend/app/services/index.ts
+++ b/frontend/app/services/index.ts
@@ -9,6 +9,8 @@ import NotesService from "./NotesService";
import RecordingsService from "./RecordingsService";
import ConfigService from './ConfigService'
import AlertsService from './AlertsService'
+import WebhookService from './WebhookService'
+
export const dashboardService = new DashboardService();
export const metricService = new MetricService();
export const sessionService = new SessionSerivce();
@@ -19,4 +21,20 @@ 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();
\ No newline at end of file
+export const alertsService = new AlertsService();
+export const webhookService = new WebhookService();
+
+export const services = [
+ dashboardService,
+ metricService,
+ sessionService,
+ userService,
+ funnelService,
+ auditService,
+ errorService,
+ notesService,
+ recordingsService,
+ configService,
+ alertsService,
+ webhookService,
+]
\ No newline at end of file
diff --git a/frontend/app/types/webhook.js b/frontend/app/types/webhook.js
deleted file mode 100644
index 5024411f4..000000000
--- a/frontend/app/types/webhook.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import Record from 'Types/Record';
-import { validateName, validateURL } from 'App/validate';
-
-export default Record({
- webhookId: undefined,
- type: undefined,
- name: '',
- endpoint: '',
- authHeader: '',
-}, {
- idKey: 'webhookId',
- methods: {
- validate() {
- return !!this.name && validateName(this.name) && !!this.endpoint && validateURL(this.endpoint);
- },
- toData() {
- const js = this.toJS();
- delete js.key;
- return js;
- },
- },
-});
diff --git a/frontend/app/types/webhook.ts b/frontend/app/types/webhook.ts
new file mode 100644
index 000000000..a8771a623
--- /dev/null
+++ b/frontend/app/types/webhook.ts
@@ -0,0 +1,36 @@
+import { validateName, validateURL } from 'App/validate';
+import { makeAutoObservable } from 'mobx'
+
+export interface IWebhook {
+ webhookId: string
+ type: string
+ name: string
+ endpoint: string
+ authHeader: string
+}
+
+export default class Webhook {
+ webhookId: IWebhook["webhookId"]
+ type: IWebhook["type"]
+ name: IWebhook["name"] = ''
+ endpoint: IWebhook["endpoint"] = ''
+ authHeader: IWebhook["authHeader"] = ''
+
+ constructor(data: Partial = {}) {
+ Object.assign(this, data)
+
+ makeAutoObservable(this)
+ }
+
+ toData() {
+ return { ...this };
+ }
+
+ validate() {
+ return !!this.name && validateName(this.name) && !!this.endpoint && validateURL(this.endpoint);
+ }
+
+ exists() {
+ return !!this.webhookId
+ }
+}
\ No newline at end of file