-
How it works?
+
+
+
+
How it works?
Create a Service Account
Enter the details below
Propagate openReplaySessionToken
-
+
(
checkIfDisplayed: (config) =>
config.awsSecretAccessKey.length === SECRET_ACCESS_KEY_LENGTH &&
config.region !== '' &&
- config.awsAccessKeyId.length === ACCESS_KEY_ID_LENGTH
- }
+ config.awsAccessKeyId.length === ACCESS_KEY_ID_LENGTH,
+ },
]}
/>
diff --git a/frontend/app/components/Client/Integrations/ElasticsearchForm.js b/frontend/app/components/Client/Integrations/ElasticsearchForm.js
index 2c30cea47..8c5c3d7f6 100644
--- a/frontend/app/components/Client/Integrations/ElasticsearchForm.js
+++ b/frontend/app/components/Client/Integrations/ElasticsearchForm.js
@@ -1,97 +1,64 @@
import React from 'react';
-import { connect } from 'react-redux';
-import IntegrationForm from './IntegrationForm';
-import { withRequest } from 'HOCs';
-import { edit } from 'Duck/integrations/actions';
-import DocLink from 'Shared/DocLink/DocLink';
import IntegrationModalCard from 'Components/Client/Integrations/IntegrationModalCard';
-@connect(
- (state) => ({
- config: state.getIn(['elasticsearch', 'instance'])
- }),
- { edit }
-)
-@withRequest({
- dataName: 'isValid',
- initialData: false,
- dataWrapper: (data) => data.state,
- requestName: 'validateConfig',
- endpoint: '/integrations/elasticsearch/test',
- method: 'POST'
-})
-export default class ElasticsearchForm extends React.PureComponent {
- componentWillReceiveProps(newProps) {
- const {
- config: { host, port, apiKeyId, apiKey }
- } = this.props;
- const { loading, config } = newProps;
- const valuesChanged = host !== config.host || port !== config.port || apiKeyId !== config.apiKeyId || apiKey !== config.apiKey;
- if (!loading && valuesChanged && newProps.config.validateKeys() && newProps) {
- this.validateConfig(newProps);
- }
- }
+import DocLink from 'Shared/DocLink/DocLink';
- validateConfig = (newProps) => {
- const { config } = newProps;
- this.props
- .validateConfig({
- host: config.host,
- port: config.port,
- apiKeyId: config.apiKeyId,
- apiKey: config.apiKey
- })
- .then((res) => {
- const { isValid } = this.props;
- this.props.edit('elasticsearch', { isValid: isValid });
- });
- };
+import IntegrationForm from './IntegrationForm';
- render() {
- const props = this.props;
- return (
-
-
+const ElasticsearchForm = (props) => {
+ return (
+
+
-
-
How it works?
-
- Create a new Elastic API key
- Enter the API key below
- Propagate openReplaySessionToken
-
-
-
-
+ How it works?
+
+ Create a new Elastic API key
+ Enter the API key below
+ Propagate openReplaySessionToken
+
+
- );
- }
-}
+
+
+ );
+};
+
+export default ElasticsearchForm;
diff --git a/frontend/app/components/Client/Integrations/IntegrationForm.js b/frontend/app/components/Client/Integrations/IntegrationForm.js
deleted file mode 100644
index c4d634562..000000000
--- a/frontend/app/components/Client/Integrations/IntegrationForm.js
+++ /dev/null
@@ -1,142 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-import { Input, Form, Button, Checkbox, Loader } from 'UI';
-import { save, init, edit, remove } from 'Duck/integrations/actions';
-import { fetchIntegrationList } from 'Duck/integrations/integrations';
-
-@connect(
- (state, { name, customPath }) => ({
- sites: state.getIn(['site', 'list']),
- initialSiteId: state.getIn(['site', 'siteId']),
- list: state.getIn([name, 'list']),
- config: state.getIn([name, 'instance']),
- loading: state.getIn([name, 'fetchRequest', 'loading']),
- saving: state.getIn([customPath || name, 'saveRequest', 'loading']),
- removing: state.getIn([name, 'removeRequest', 'loading']),
- siteId: state.getIn(['integrations', 'siteId']),
- }),
- {
- save,
- init,
- edit,
- remove,
- // fetchList,
- fetchIntegrationList,
- }
-)
-export default class IntegrationForm extends React.PureComponent {
- constructor(props) {
- super(props);
- }
-
- fetchList = () => {
- const { siteId, initialSiteId } = this.props;
- if (!siteId) {
- this.props.fetchIntegrationList(initialSiteId);
- } else {
- this.props.fetchIntegrationList(siteId);
- }
- }
-
- write = ({ target: { value, name: key, type, checked } }) => {
- if (type === 'checkbox') this.props.edit(this.props.name, { [key]: checked });
- else this.props.edit(this.props.name, { [key]: value });
- };
-
- // onChangeSelect = ({ value }) => {
- // const { sites, list, name } = this.props;
- // const site = sites.find((s) => s.id === value.value);
- // this.setState({ currentSiteId: site.id });
- // this.init(value.value);
- // };
-
- // init = (siteId) => {
- // const { list, name } = this.props;
- // const config = parseInt(siteId) > 0 ? list.find((s) => s.projectId === siteId) : undefined;
- // this.props.init(name, config ? config : list.first());
- // };
-
- save = () => {
- const { config, name, customPath, ignoreProject } = this.props;
- const isExists = config.exists();
- // const { currentSiteId } = this.state;
- this.props.save(customPath || name, !ignoreProject ? this.props.siteId : null, config).then(() => {
- // this.props.fetchList(name);
- this.fetchList();
- this.props.onClose();
- if (isExists) return;
- });
- };
-
- remove = () => {
- const { name, config, ignoreProject } = this.props;
- this.props.remove(name, !ignoreProject ? config.projectId : null).then(() => {
- this.props.onClose();
- this.fetchList();
- });
- };
-
- render() {
- const { config, saving, removing, formFields, name, loading, integrated } = this.props;
- return (
-
-
-
-
-
- ) : (
-
- {label}
-
-
- ))
- )}
-
-
- {config.exists() ? 'Update' : 'Add'}
-
-
- {integrated && (
-
- {'Delete'}
-
- )}
-
-
-
- );
- }
-}
diff --git a/frontend/app/components/Client/Integrations/IntegrationForm.tsx b/frontend/app/components/Client/Integrations/IntegrationForm.tsx
new file mode 100644
index 000000000..905b41d70
--- /dev/null
+++ b/frontend/app/components/Client/Integrations/IntegrationForm.tsx
@@ -0,0 +1,110 @@
+import { observer } from 'mobx-react-lite';
+import React from 'react';
+import { connect } from 'react-redux';
+
+import { useStore } from 'App/mstore';
+import { namedStore } from 'App/mstore/integrationsStore';
+import { Button, Checkbox, Form, Input, Loader } from 'UI';
+
+function IntegrationForm(props: any) {
+ const { formFields, name, integrated } = props;
+ const { integrationsStore } = useStore();
+ const integrationStore = integrationsStore[name as unknown as namedStore];
+ const config = integrationStore.instance;
+ const loading = integrationStore.loading;
+ const onSave = integrationStore.saveIntegration;
+ const onRemove = integrationStore.deleteIntegration;
+ const edit = integrationStore.edit;
+ const fetchIntegrationList = integrationsStore.integrations.fetchIntegrations;
+
+ const fetchList = () => {
+ void fetchIntegrationList(props.initialSiteId);
+ };
+
+ const write = ({ target: { value, name: key, type, checked } }) => {
+ if (type === 'checkbox') edit({ [key]: checked });
+ else edit({ [key]: value });
+ };
+
+ const save = () => {
+ const { name, customPath } = props;
+ onSave(customPath || name).then(() => {
+ fetchList();
+ props.onClose();
+ });
+ };
+
+ const remove = () => {
+ onRemove().then(() => {
+ props.onClose();
+ fetchList();
+ });
+ };
+
+ return (
+
+
+
+
+
+ ) : (
+
+ {label}
+
+
+ ))
+ )}
+
+
+ {config?.exists() ? 'Update' : 'Add'}
+
+
+ {integrated && (
+
+ {'Delete'}
+
+ )}
+
+
+
+ );
+}
+
+export default connect((state: any) => ({
+ sites: state.getIn(['site', 'list']),
+ initialSiteId: state.getIn(['site', 'siteId']),
+}))(observer(IntegrationForm));
diff --git a/frontend/app/components/Client/Integrations/Integrations.tsx b/frontend/app/components/Client/Integrations/Integrations.tsx
index e81a7038a..31cd2e46d 100644
--- a/frontend/app/components/Client/Integrations/Integrations.tsx
+++ b/frontend/app/components/Client/Integrations/Integrations.tsx
@@ -1,88 +1,95 @@
+import withPageTitle from 'HOCs/withPageTitle';
+import cn from 'classnames';
+import { observer } from 'mobx-react-lite';
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
+
import { useModal } from 'App/components/Modal';
-import cn from 'classnames';
+import { useStore } from 'App/mstore';
+import IntegrationFilters from 'Components/Client/Integrations/IntegrationFilters';
+import { PageTitle } from 'UI';
-import { fetch, init } from 'Duck/integrations/actions';
-import { fetchIntegrationList, setSiteId } from 'Duck/integrations/integrations';
-import SiteDropdown from 'Shared/SiteDropdown';
-import ReduxDoc from './ReduxDoc';
-import VueDoc from './VueDoc';
-import GraphQLDoc from './GraphQLDoc';
-import NgRxDoc from './NgRxDoc';
-import MobxDoc from './MobxDoc';
-import ProfilerDoc from './ProfilerDoc';
-import AssistDoc from './AssistDoc';
-import PiniaDoc from './PiniaDoc';
-import ZustandDoc from './ZustandDoc';
-import MSTeams from './Teams';
import DocCard from 'Shared/DocCard/DocCard';
-import { PageTitle, Tooltip } from 'UI';
-import withPageTitle from 'HOCs/withPageTitle';
+import AssistDoc from './AssistDoc';
import BugsnagForm from './BugsnagForm';
import CloudwatchForm from './CloudwatchForm';
import DatadogForm from './DatadogForm';
import ElasticsearchForm from './ElasticsearchForm';
import GithubForm from './GithubForm';
+import GraphQLDoc from './GraphQLDoc';
import IntegrationItem from './IntegrationItem';
import JiraForm from './JiraForm';
+import MobxDoc from './MobxDoc';
import NewrelicForm from './NewrelicForm';
+import NgRxDoc from './NgRxDoc';
+import PiniaDoc from './PiniaDoc';
+import ProfilerDoc from './ProfilerDoc';
+import ReduxDoc from './ReduxDoc';
import RollbarForm from './RollbarForm';
import SentryForm from './SentryForm';
import SlackForm from './SlackForm';
import StackdriverForm from './StackdriverForm';
import SumoLogicForm from './SumoLogicForm';
-import IntegrationFilters from 'Components/Client/Integrations/IntegrationFilters';
+import MSTeams from './Teams';
+import VueDoc from './VueDoc';
+import ZustandDoc from './ZustandDoc';
interface Props {
- fetch: (name: string, siteId: string) => void;
- init: () => void;
- fetchIntegrationList: (siteId: any) => void;
- integratedList: any;
- initialSiteId: string;
- setSiteId: (siteId: string) => void;
siteId: string;
hideHeader?: boolean;
- loading?: boolean;
}
function Integrations(props: Props) {
- const { initialSiteId, hideHeader = false, loading = false } = props;
+ const { integrationsStore } = useStore();
+
+ const fetchIntegrationList = integrationsStore.integrations.fetchIntegrations;
+ const storeIntegratedList = integrationsStore.integrations.list;
+ const { siteId, hideHeader = false } = props;
const { showModal } = useModal();
const [integratedList, setIntegratedList] = useState
([]);
const [activeFilter, setActiveFilter] = useState('all');
useEffect(() => {
- const list = props.integratedList
+ const list = storeIntegratedList
.filter((item: any) => item.integrated)
.map((item: any) => item.name);
setIntegratedList(list);
- }, [props.integratedList]);
+ }, [storeIntegratedList]);
useEffect(() => {
- props.fetchIntegrationList(initialSiteId);
- props.setSiteId(initialSiteId);
- }, []);
+ void fetchIntegrationList(siteId);
+ }, [siteId]);
const onClick = (integration: any, width: number) => {
- if (integration.slug && integration.slug !== 'slack' && integration.slug !== 'msteams') {
- props.fetch(integration.slug, props.siteId);
+ if (
+ integration.slug &&
+ integration.slug !== 'slack' &&
+ integration.slug !== 'msteams'
+ ) {
+ const intName = integration.slug as
+ | 'sentry'
+ | 'bugsnag'
+ | 'rollbar'
+ | 'elasticsearch'
+ | 'datadog'
+ | 'sumologic'
+ | 'stackdriver'
+ | 'cloudwatch'
+ | 'newrelic';
+ if (integrationsStore[intName]) {
+ void integrationsStore[intName].fetchIntegration(siteId);
+ }
}
showModal(
React.cloneElement(integration.component, {
- integrated: integratedList.includes(integration.slug)
+ integrated: integratedList.includes(integration.slug),
}),
{ right: true, width }
);
};
- const onChangeSelect = ({ value }: any) => {
- props.setSiteId(value.value);
- props.fetchIntegrationList(value.value);
- };
-
const onChange = (key: string) => {
setActiveFilter(key);
};
@@ -99,83 +106,91 @@ function Integrations(props: Props) {
key: cat.key,
title: cat.title,
label: cat.title,
- icon: cat.icon
- }))
-
-
- const allIntegrations = filteredIntegrations.flatMap(cat => cat.integrations);
-
+ icon: cat.icon,
+ }));
+ const allIntegrations = filteredIntegrations.flatMap(
+ (cat) => cat.integrations
+ );
return (
<>
-
+
{!hideHeader &&
Integrations } />}
-
+
-
+
-
+`)}
+ >
{allIntegrations.map((integration: any) => (
- onClick(integration, filteredIntegrations.find(cat => cat.integrations.includes(integration)).title === 'Plugins' ? 500 : 350)
+ onClick(
+ integration,
+ filteredIntegrations.find((cat) =>
+ cat.integrations.includes(integration)
+ ).title === 'Plugins'
+ ? 500
+ : 350
+ )
}
hide={
(integration.slug === 'github' &&
integratedList.includes('jira')) ||
- (integration.slug === 'jira' &&
- integratedList.includes('github'))
+ (integration.slug === 'jira' && integratedList.includes('github'))
}
/>
))}
-
>
);
}
-export default connect(
- (state: any) => ({
- initialSiteId: state.getIn(['site', 'siteId']),
- integratedList: state.getIn(['integrations', 'list']) || [],
- loading: state.getIn(['integrations', 'fetchRequest', 'loading']),
- siteId: state.getIn(['integrations', 'siteId'])
- }),
- { fetch, init, fetchIntegrationList, setSiteId }
-)(withPageTitle('Integrations - OpenReplay Preferences')(Integrations));
-
+export default connect((state: any) => ({
+ siteId: state.getIn(['site', 'siteId']),
+}))(
+ withPageTitle('Integrations - OpenReplay Preferences')(observer(Integrations))
+);
const integrations = [
{
title: 'Issue Reporting',
key: 'issue-reporting',
- description: 'Seamlessly report issues or share issues with your team right from OpenReplay.',
+ description:
+ 'Seamlessly report issues or share issues with your team right from OpenReplay.',
isProject: false,
icon: 'exclamation-triangle',
integrations: [
{
title: 'Jira',
- subtitle: 'Integrate Jira with OpenReplay to enable the creation of a new ticket directly from a session.',
+ subtitle:
+ 'Integrate Jira with OpenReplay to enable the creation of a new ticket directly from a session.',
slug: 'jira',
category: 'Errors',
icon: 'integrations/jira',
- component:
+ component: ,
},
{
title: 'Github',
- subtitle: 'Integrate GitHub with OpenReplay to enable the direct creation of a new issue from a session.',
+ subtitle:
+ 'Integrate GitHub with OpenReplay to enable the direct creation of a new issue from a session.',
slug: 'github',
category: 'Errors',
icon: 'integrations/github',
- component:
- }
- ]
+ component: ,
+ },
+ ],
},
{
title: 'Backend Logging',
@@ -186,106 +201,119 @@ const integrations = [
'Sync your backend errors with sessions replays and see what happened front-to-back.',
docs: () => (
- Sync your backend errors with sessions replays and see what happened front-to-back.
+ Sync your backend errors with sessions replays and see what happened
+ front-to-back.
),
integrations: [
{
title: 'Sentry',
- subtitle: 'Integrate Sentry with session replays to seamlessly observe backend errors.',
+ subtitle:
+ 'Integrate Sentry with session replays to seamlessly observe backend errors.',
slug: 'sentry',
icon: 'integrations/sentry',
- component:
+ component: ,
},
{
title: 'Bugsnag',
- subtitle: 'Integrate Bugsnag to access the OpenReplay session linked to the JS exception within its interface.',
+ subtitle:
+ 'Integrate Bugsnag to access the OpenReplay session linked to the JS exception within its interface.',
slug: 'bugsnag',
icon: 'integrations/bugsnag',
- component:
+ component: ,
},
{
title: 'Rollbar',
- subtitle: 'Integrate Rollbar with session replays to seamlessly observe backend errors.',
+ subtitle:
+ 'Integrate Rollbar with session replays to seamlessly observe backend errors.',
slug: 'rollbar',
icon: 'integrations/rollbar',
- component:
+ component: ,
},
{
title: 'Elasticsearch',
- subtitle: 'Integrate Elasticsearch with session replays to seamlessly observe backend errors.',
+ subtitle:
+ 'Integrate Elasticsearch with session replays to seamlessly observe backend errors.',
slug: 'elasticsearch',
icon: 'integrations/elasticsearch',
- component:
+ component: ,
},
{
title: 'Datadog',
- subtitle: 'Incorporate DataDog to visualize backend errors alongside session replay, for easy troubleshooting.',
+ subtitle:
+ 'Incorporate DataDog to visualize backend errors alongside session replay, for easy troubleshooting.',
slug: 'datadog',
icon: 'integrations/datadog',
- component:
+ component: ,
},
{
title: 'Sumo Logic',
- subtitle: 'Integrate Sumo Logic with session replays to seamlessly observe backend errors.',
+ subtitle:
+ 'Integrate Sumo Logic with session replays to seamlessly observe backend errors.',
slug: 'sumologic',
icon: 'integrations/sumologic',
- component:
+ component: ,
},
{
title: 'Google Cloud',
- subtitle: 'Integrate Google Cloud to view backend logs and errors in conjunction with session replay',
+ subtitle:
+ 'Integrate Google Cloud to view backend logs and errors in conjunction with session replay',
slug: 'stackdriver',
icon: 'integrations/google-cloud',
- component:
+ component: ,
},
{
title: 'CloudWatch',
- subtitle: 'Integrate CloudWatch to see backend logs and errors alongside session replay.',
+ subtitle:
+ 'Integrate CloudWatch to see backend logs and errors alongside session replay.',
slug: 'cloudwatch',
icon: 'integrations/aws',
- component:
+ component: ,
},
{
title: 'Newrelic',
- subtitle: 'Integrate NewRelic with session replays to seamlessly observe backend errors.',
+ subtitle:
+ 'Integrate NewRelic with session replays to seamlessly observe backend errors.',
slug: 'newrelic',
icon: 'integrations/newrelic',
- component:
- }
- ]
+ component: ,
+ },
+ ],
},
{
title: 'Collaboration',
key: 'collaboration',
isProject: false,
icon: 'file-code',
- description: 'Share your sessions with your team and collaborate on issues.',
+ description:
+ 'Share your sessions with your team and collaborate on issues.',
integrations: [
{
title: 'Slack',
- subtitle: 'Integrate Slack to empower every user in your org with the ability to send sessions to any Slack channel.',
+ subtitle:
+ 'Integrate Slack to empower every user in your org with the ability to send sessions to any Slack channel.',
slug: 'slack',
category: 'Errors',
icon: 'integrations/slack',
component: ,
- shared: true
+ shared: true,
},
{
title: 'MS Teams',
- subtitle: 'Integrate MS Teams to empower every user in your org with the ability to send sessions to any MS Teams channel.',
+ subtitle:
+ 'Integrate MS Teams to empower every user in your org with the ability to send sessions to any MS Teams channel.',
slug: 'msteams',
category: 'Errors',
icon: 'integrations/teams',
component: ,
- shared: true
- }
- ]
+ shared: true,
+ },
+ ],
},
// {
// title: 'State Management',
@@ -302,72 +330,82 @@ const integrations = [
icon: 'chat-left-text',
docs: () => (
- Plugins capture your application’s store, monitor queries, track performance issues and even
- assist your end user through live sessions.
+ Plugins capture your application’s store, monitor queries, track
+ performance issues and even assist your end user through live sessions.
),
description:
- 'Reproduce issues as if they happened in your own browser. Plugins help capture your application\'s store, HTTP requeets, GraphQL queries, and more.',
+ "Reproduce issues as if they happened in your own browser. Plugins help capture your application's store, HTTP requeets, GraphQL queries, and more.",
integrations: [
{
title: 'Redux',
- subtitle: 'Capture Redux actions/state and inspect them later on while replaying session recordings.',
- icon: 'integrations/redux', component:
+ subtitle:
+ 'Capture Redux actions/state and inspect them later on while replaying session recordings.',
+ icon: 'integrations/redux',
+ component: ,
},
{
title: 'VueX',
- subtitle: 'Capture VueX mutations/state and inspect them later on while replaying session recordings.',
+ subtitle:
+ 'Capture VueX mutations/state and inspect them later on while replaying session recordings.',
icon: 'integrations/vuejs',
- component:
+ component: ,
},
{
title: 'Pinia',
- subtitle: 'Capture Pinia mutations/state and inspect them later on while replaying session recordings.',
+ subtitle:
+ 'Capture Pinia mutations/state and inspect them later on while replaying session recordings.',
icon: 'integrations/pinia',
- component:
+ component: ,
},
{
title: 'GraphQL',
- subtitle: 'Capture GraphQL requests and inspect them later on while replaying session recordings. This plugin is compatible with Apollo and Relay implementations.',
+ subtitle:
+ 'Capture GraphQL requests and inspect them later on while replaying session recordings. This plugin is compatible with Apollo and Relay implementations.',
icon: 'integrations/graphql',
- component:
+ component: ,
},
{
title: 'NgRx',
- subtitle: 'Capture NgRx actions/state and inspect them later on while replaying session recordings.\n',
+ subtitle:
+ 'Capture NgRx actions/state and inspect them later on while replaying session recordings.\n',
icon: 'integrations/ngrx',
- component:
+ component: ,
},
{
title: 'MobX',
- subtitle: 'Capture MobX mutations and inspect them later on while replaying session recordings.',
+ subtitle:
+ 'Capture MobX mutations and inspect them later on while replaying session recordings.',
icon: 'integrations/mobx',
- component:
+ component: ,
},
{
title: 'Profiler',
- subtitle: 'Plugin allows you to measure your JS functions performance and capture both arguments and result for each call.',
+ subtitle:
+ 'Plugin allows you to measure your JS functions performance and capture both arguments and result for each call.',
icon: 'integrations/openreplay',
- component:
+ component: ,
},
{
title: 'Assist',
- subtitle: 'OpenReplay Assist allows you to support your users by seeing their live screen and instantly hopping on call (WebRTC) with them without requiring any 3rd-party screen sharing software.\n',
+ subtitle:
+ 'OpenReplay Assist allows you to support your users by seeing their live screen and instantly hopping on call (WebRTC) with them without requiring any 3rd-party screen sharing software.\n',
icon: 'integrations/openreplay',
- component:
+ component: ,
},
{
title: 'Zustand',
- subtitle: 'Capture Zustand mutations/state and inspect them later on while replaying session recordings.',
+ subtitle:
+ 'Capture Zustand mutations/state and inspect them later on while replaying session recordings.',
icon: 'integrations/zustand',
// header: '🐻',
- component:
- }
- ]
- }
+ component: ,
+ },
+ ],
+ },
];
diff --git a/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js b/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js
index 65705b1e0..fb1c1ec3a 100644
--- a/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js
+++ b/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js
@@ -3,9 +3,14 @@ import ToggleContent from 'Shared/ToggleContent';
import DocLink from 'Shared/DocLink/DocLink';
import { connect } from 'react-redux';
import { CodeBlock } from "UI";
+import { useStore } from 'App/mstore';
+import { observer } from 'mobx-react-lite';
const MobxDoc = (props) => {
- const { projectKey } = props;
+ const { integrationsStore } = useStore();
+ const sites = props.sites ? props.sites.toJS() : []
+ const siteId = integrationsStore.integrations.siteId
+ const projectKey = siteId ? sites.find((site) => site.id === siteId)?.projectKey : sites[0]?.projectKey
const mobxUsage = `import OpenReplay from '@openreplay/tracker';
import trackerMobX from '@openreplay/tracker-mobx';
@@ -68,9 +73,8 @@ function SomeFunctionalComponent() {
MobxDoc.displayName = 'MobxDoc';
export default connect((state) => {
- const siteId = state.getIn(['integrations', 'siteId']);
const sites = state.getIn(['site', 'list']);
return {
- projectKey: sites.find((site) => site.get('id') === siteId).get('projectKey'),
+ sites,
};
-})(MobxDoc);
+})(observer(MobxDoc))
diff --git a/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js b/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js
index 26cfc9520..60edc363d 100644
--- a/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js
+++ b/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js
@@ -1,25 +1,36 @@
import React from 'react';
import { connect } from 'react-redux';
-import { edit, save, init, update } from 'Duck/integrations/slack';
import { Form, Input, Button, Message } from 'UI';
import { confirm } from 'UI';
-import { remove } from 'Duck/integrations/slack';
+import { observer } from 'mobx-react-lite'
+import { useStore } from 'App/mstore'
-class SlackAddForm extends React.PureComponent {
- componentWillUnmount() {
- this.props.init({});
- }
+function SlackAddForm(props) {
+ const { onClose } = props;
+ const { integrationsStore } = useStore();
+ const instance = integrationsStore.slack.instance;
+ const saving = integrationsStore.slack.loading;
+ const errors = integrationsStore.slack.errors;
+ const edit = integrationsStore.slack.edit;
+ const onSave = integrationsStore.slack.saveIntegration;
+ const update = integrationsStore.slack.update;
+ const init = integrationsStore.slack.init;
+ const onRemove = integrationsStore.slack.removeInt;
+
+ React.useEffect(() => {
+ return () => init({})
+ }, [])
- save = () => {
- const instance = this.props.instance;
+
+ const save = () => {
if (instance.exists()) {
- this.props.update(this.props.instance);
+ void update(instance);
} else {
- this.props.save(this.props.instance);
+ void onSave(instance);
}
};
- remove = async (id) => {
+ const remove = async (id) => {
if (
await confirm({
header: 'Confirm',
@@ -27,79 +38,68 @@ class SlackAddForm extends React.PureComponent {
confirmation: `Are you sure you want to permanently delete this channel?`,
})
) {
- this.props.remove(id);
+ await onRemove(id);
+ onClose();
}
};
- write = ({ target: { name, value } }) => this.props.edit({ [name]: value });
-
- render() {
- const { instance, saving, errors, onClose } = this.props;
- return (
-
-
- Name
-
-
-
- URL
-
-
-
-
-
- {instance.exists() ? 'Update' : 'Add'}
-
-
- {'Cancel'}
-
-
-
this.remove(instance.webhookId)} disabled={!instance.exists()}>
- {'Delete'}
+ const write = ({ target: { name, value } }) => edit({ [name]: value });
+
+ return (
+
+
+ Name
+
+
+
+ URL
+
+
+
+
+
+ {instance.exists() ? 'Update' : 'Add'}
-
-
- {errors && (
-
- {errors.map((error) => (
-
- {error}
-
- ))}
+ {'Cancel'}
- )}
-
- );
- }
+
+
remove(instance.webhookId)} disabled={!instance.exists()}>
+ {'Delete'}
+
+
+
+
+ {errors && (
+
+ {errors.map((error) => (
+
+ {error}
+
+ ))}
+
+ )}
+
+ );
}
-export default connect(
- (state) => ({
- instance: state.getIn(['slack', 'instance']),
- saving:
- state.getIn(['slack', 'saveRequest', 'loading']) ||
- state.getIn(['slack', 'updateRequest', 'loading']),
- errors: state.getIn(['slack', 'saveRequest', 'errors']),
- }),
- { edit, save, init, remove, update }
-)(SlackAddForm);
+export default observer(SlackAddForm);
diff --git a/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js b/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js
index 8d25b4454..db53d3100 100644
--- a/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js
+++ b/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js
@@ -1,14 +1,16 @@
import React from 'react';
-import { connect } from 'react-redux';
import { NoContent } from 'UI';
-import { remove, edit, init } from 'Duck/integrations/slack';
import DocLink from 'Shared/DocLink/DocLink';
+import { observer } from 'mobx-react-lite'
+import { useStore } from 'App/mstore'
function SlackChannelList(props) {
- const { list } = props;
+ const { integrationsStore } = useStore();
+ const list = integrationsStore.slack.list;
+ const edit = integrationsStore.slack.edit;
const onEdit = (instance) => {
- props.edit(instance);
+ edit(instance.toData());
props.onEdit();
};
@@ -24,7 +26,7 @@ function SlackChannelList(props) {
}
size="small"
- show={list.size === 0}
+ show={list.length === 0}
>
{list.map((c) => (
({
- list: state.getIn(['slack', 'list']),
- }),
- { remove, edit, init }
-)(SlackChannelList);
+export default observer(SlackChannelList);
diff --git a/frontend/app/components/Client/Integrations/SlackForm.tsx b/frontend/app/components/Client/Integrations/SlackForm.tsx
index 018dbe885..43a720da4 100644
--- a/frontend/app/components/Client/Integrations/SlackForm.tsx
+++ b/frontend/app/components/Client/Integrations/SlackForm.tsx
@@ -1,17 +1,14 @@
import React, { useEffect } from 'react';
import SlackChannelList from './SlackChannelList/SlackChannelList';
-import { fetchList, init } from 'Duck/integrations/slack';
-import { connect } from 'react-redux';
import SlackAddForm from './SlackAddForm';
import { Button } from 'UI';
+import { observer } from 'mobx-react-lite'
+import { useStore } from 'App/mstore'
-interface Props {
- onEdit?: (integration: any) => void;
- istance: any;
- fetchList: any;
- init: any;
-}
-const SlackForm = (props: Props) => {
+const SlackForm = () => {
+ const { integrationsStore } = useStore();
+ const init = integrationsStore.slack.init;
+ const fetchList = integrationsStore.slack.fetchIntegrations;
const [active, setActive] = React.useState(false);
const onEdit = () => {
@@ -20,11 +17,11 @@ const SlackForm = (props: Props) => {
const onNew = () => {
setActive(true);
- props.init({});
+ init({});
}
useEffect(() => {
- props.fetchList();
+ void fetchList();
}, []);
return (
@@ -47,9 +44,4 @@ const SlackForm = (props: Props) => {
SlackForm.displayName = 'SlackForm';
-export default connect(
- (state: any) => ({
- istance: state.getIn(['slack', 'instance']),
- }),
- { fetchList, init }
-)(SlackForm);
+export default observer(SlackForm);
\ No newline at end of file
diff --git a/frontend/app/components/Client/Integrations/Teams/TeamsAddForm.tsx b/frontend/app/components/Client/Integrations/Teams/TeamsAddForm.tsx
index f13efc535..e45d6d7b1 100644
--- a/frontend/app/components/Client/Integrations/Teams/TeamsAddForm.tsx
+++ b/frontend/app/components/Client/Integrations/Teams/TeamsAddForm.tsx
@@ -1,36 +1,38 @@
+import { observer } from 'mobx-react-lite';
import React from 'react';
-import { connect } from 'react-redux';
-import { edit, save, init, update, remove } from 'Duck/integrations/teams';
-import { Form, Input, Button, Message } from 'UI';
+
+import { useStore } from 'App/mstore';
+import { Button, Form, Input, Message } from 'UI';
import { confirm } from 'UI';
interface Props {
- edit: (inst: any) => void;
- save: (inst: any) => void;
- init: (inst: any) => void;
- update: (inst: any) => void;
- remove: (id: string) => void;
onClose: () => void;
- instance: any;
- saving: boolean;
- errors: any;
}
-class TeamsAddForm extends React.PureComponent
{
- componentWillUnmount() {
- this.props.init({});
- }
+function TeamsAddForm({ onClose }: Props) {
+ const { integrationsStore } = useStore();
+ const instance = integrationsStore.msteams.instance;
+ const saving = integrationsStore.msteams.loading;
+ const errors = integrationsStore.msteams.errors;
+ const edit = integrationsStore.msteams.edit;
+ const onSave = integrationsStore.msteams.saveIntegration;
+ const init = integrationsStore.msteams.init;
+ const onRemove = integrationsStore.msteams.removeInt;
+ const update = integrationsStore.msteams.update;
- save = () => {
- const instance = this.props.instance;
- if (instance.exists()) {
- this.props.update(this.props.instance);
+ React.useEffect(() => {
+ return () => init({});
+ }, []);
+
+ const save = () => {
+ if (instance?.exists()) {
+ void update();
} else {
- this.props.save(this.props.instance);
+ void onSave();
}
};
- remove = async (id: string) => {
+ const remove = async (id: string) => {
if (
await confirm({
header: 'Confirm',
@@ -38,80 +40,74 @@ class TeamsAddForm extends React.PureComponent {
confirmation: `Are you sure you want to permanently delete this channel?`,
})
) {
- this.props.remove(id);
+ void onRemove(id);
}
};
- write = ({ target: { name, value } }: { target: { name: string; value: string } }) =>
- this.props.edit({ [name]: value });
+ const write = ({
+ target: { name, value },
+ }: {
+ target: { name: string; value: string };
+ }) => edit({ [name]: value });
- render() {
- const { instance, saving, errors, onClose } = this.props;
- return (
-
-
- Name
-
-
-
- URL
-
-
-
-
-
- {instance.exists() ? 'Update' : 'Add'}
-
-
- {'Cancel'}
-
-
-
this.remove(instance.webhookId)} disabled={!instance.exists()}>
- {'Delete'}
+ return (
+
+
+ Name
+
+
+
+ URL
+
+
+
+
+
+ {instance?.exists() ? 'Update' : 'Add'}
-
-
- {errors && (
-
- {errors.map((error: any) => (
-
- {error}
-
- ))}
+ {'Cancel'}
- )}
-
- );
- }
+
+
remove(instance?.webhookId)}
+ disabled={!instance.exists()}
+ >
+ {'Delete'}
+
+
+
+
+ {errors && (
+
+ {errors.map((error: any) => (
+
+ {error}
+
+ ))}
+
+ )}
+
+ );
}
-export default connect(
- (state: any) => ({
- instance: state.getIn(['teams', 'instance']),
- saving:
- state.getIn(['teams', 'saveRequest', 'loading']) ||
- state.getIn(['teams', 'updateRequest', 'loading']),
- errors: state.getIn(['teams', 'saveRequest', 'errors']),
- }),
- { edit, save, init, remove, update }
-)(TeamsAddForm);
+export default observer(TeamsAddForm);
diff --git a/frontend/app/components/Client/Integrations/Teams/TeamsChannelList.tsx b/frontend/app/components/Client/Integrations/Teams/TeamsChannelList.tsx
index 942e1e32c..131a404c8 100644
--- a/frontend/app/components/Client/Integrations/Teams/TeamsChannelList.tsx
+++ b/frontend/app/components/Client/Integrations/Teams/TeamsChannelList.tsx
@@ -1,51 +1,57 @@
+import { observer } from 'mobx-react-lite';
import React from 'react';
-import { connect } from 'react-redux';
+
+import { useStore } from 'App/mstore';
import { NoContent } from 'UI';
-import { remove, edit, init } from 'Duck/integrations/teams';
+
import DocLink from 'Shared/DocLink/DocLink';
-function TeamsChannelList(props: { list: any, edit: (inst: any) => any, onEdit: () => void }) {
- const { list } = props;
+function TeamsChannelList(props: { onEdit: () => void }) {
+ const { integrationsStore } = useStore();
+ const list = integrationsStore.msteams.list;
+ const edit = integrationsStore.msteams.edit;
- const onEdit = (instance: Record
) => {
- props.edit(instance);
- props.onEdit();
- };
+ const onEdit = (instance: Record) => {
+ edit(instance);
+ props.onEdit();
+ };
- return (
-
-
-
- Integrate MS Teams with OpenReplay and share insights with the rest of the team, directly from the recording page.
-
-
-
- }
- size="small"
- show={list.size === 0}
- >
- {list.map((c: any) => (
- onEdit(c)}
- >
-
-
{c.name}
-
{c.endpoint}
-
-
- ))}
-
-
- );
+ return (
+
+
+
+ Integrate MS Teams with OpenReplay and share insights with the
+ rest of the team, directly from the recording page.
+
+
+
+ }
+ size="small"
+ show={list.length === 0}
+ >
+ {list.map((c: any) => (
+ onEdit(c)}
+ >
+
+
{c.name}
+
+ {c.endpoint}
+
+
+
+ ))}
+
+
+ );
}
-export default connect(
- (state: any) => ({
- list: state.getIn(['teams', 'list']),
- }),
- { remove, edit, init }
-)(TeamsChannelList);
+export default observer(TeamsChannelList);
diff --git a/frontend/app/components/Client/Integrations/Teams/index.tsx b/frontend/app/components/Client/Integrations/Teams/index.tsx
index e51bd64b1..b85a4f010 100644
--- a/frontend/app/components/Client/Integrations/Teams/index.tsx
+++ b/frontend/app/components/Client/Integrations/Teams/index.tsx
@@ -1,17 +1,15 @@
import React, { useEffect } from 'react';
import TeamsChannelList from './TeamsChannelList';
-import { fetchList, init } from 'Duck/integrations/teams';
-import { connect } from 'react-redux';
+import { useStore } from 'App/mstore';
+import { observer } from 'mobx-react-lite';
+
import TeamsAddForm from './TeamsAddForm';
import { Button } from 'UI';
-interface Props {
- onEdit?: (integration: any) => void;
- istance: any;
- fetchList: any;
- init: any;
-}
-const MSTeams = (props: Props) => {
+const MSTeams = () => {
+ const { integrationsStore } = useStore();
+ const fetchList = integrationsStore.msteams.fetchIntegrations;
+ const init = integrationsStore.msteams.init;
const [active, setActive] = React.useState(false);
const onEdit = () => {
@@ -20,11 +18,11 @@ const MSTeams = (props: Props) => {
const onNew = () => {
setActive(true);
- props.init({});
+ init({});
}
useEffect(() => {
- props.fetchList();
+ void fetchList();
}, []);
return (
@@ -47,9 +45,4 @@ const MSTeams = (props: Props) => {
MSTeams.displayName = 'MSTeams';
-export default connect(
- (state: any) => ({
- istance: state.getIn(['teams', 'instance']),
- }),
- { fetchList, init }
-)(MSTeams);
+export default observer(MSTeams);
diff --git a/frontend/app/components/Session/LiveSession.js b/frontend/app/components/Session/LiveSession.js
index a9bba11b9..eec861e04 100644
--- a/frontend/app/components/Session/LiveSession.js
+++ b/frontend/app/components/Session/LiveSession.js
@@ -3,28 +3,29 @@ import { useEffect } from 'react';
import { connect } from 'react-redux';
import usePageTitle from 'App/hooks/usePageTitle';
import { fetch as fetchSession, clearCurrentSession } from 'Duck/sessions';
-import { fetchList as fetchSlackList } from 'Duck/integrations/slack';
import { Loader } from 'UI';
import withPermissions from 'HOCs/withPermissions';
import LivePlayer from './LivePlayer';
import { clearLogs } from 'App/dev/console';
import { toast } from 'react-toastify';
+import { useStore } from 'App/mstore'
function LiveSession({
sessionId,
fetchSession,
- fetchSlackList,
hasSessionsPath,
session,
fetchFailed,
clearCurrentSession,
}) {
+ const { integrationsStore } = useStore();
+ const fetchSlackList = integrationsStore.slack.fetchIntegrations;
const [initialLoading, setInitialLoading] = React.useState(true);
usePageTitle('OpenReplay Assist');
useEffect(() => {
clearLogs();
- fetchSlackList();
+ void fetchSlackList();
return () => {
clearCurrentSession()
@@ -77,7 +78,6 @@ export default withPermissions(['ASSIST_LIVE', 'SERVICE_ASSIST_LIVE'], '', true,
},
{
fetchSession,
- fetchSlackList,
clearCurrentSession,
}
)(LiveSession)
diff --git a/frontend/app/components/Session/MobilePlayer.tsx b/frontend/app/components/Session/MobilePlayer.tsx
index 16f4a9178..c03efefa9 100644
--- a/frontend/app/components/Session/MobilePlayer.tsx
+++ b/frontend/app/components/Session/MobilePlayer.tsx
@@ -1,7 +1,6 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { Modal, Loader } from 'UI';
-import { fetchList } from 'Duck/integrations';
import { createIOSPlayer } from 'Player';
import { makeAutoObservable } from 'mobx';
import withLocationHandlers from 'HOCs/withLocationHandlers';
@@ -24,7 +23,7 @@ let playerInst: IOSPlayerContext['player'] | undefined;
function MobilePlayer(props: any) {
const { session, fetchList } = props;
- const { notesStore, sessionStore, uiPlayerStore } = useStore();
+ const { notesStore, sessionStore, uiPlayerStore, integrationsStore } = useStore();
const [activeTab, setActiveTab] = useState('');
const [noteItem, setNoteItem] = useState(undefined);
// @ts-ignore
@@ -37,7 +36,7 @@ function MobilePlayer(props: any) {
useEffect(() => {
playerInst = undefined;
if (!session.sessionId || contextValue.player !== undefined) return;
- fetchList('issues');
+ void integrationsStore.issues.fetchIntegrations();
sessionStore.setUserTimezone(session.timezone);
const [IOSPlayerInst, PlayerStore] = createIOSPlayer(
session,
diff --git a/frontend/app/components/Session/Session.tsx b/frontend/app/components/Session/Session.tsx
index 026ae0c7b..02ab206a5 100644
--- a/frontend/app/components/Session/Session.tsx
+++ b/frontend/app/components/Session/Session.tsx
@@ -8,7 +8,6 @@ import usePageTitle from 'App/hooks/usePageTitle';
import { useStore } from 'App/mstore';
import { sessions as sessionsRoute } from 'App/routes';
import MobilePlayer from 'Components/Session/MobilePlayer';
-import { fetchList as fetchSlackList } from 'Duck/integrations/slack';
import { clearCurrentSession, fetchV2 } from 'Duck/sessions';
import { Link, Loader, NoContent } from 'UI';
@@ -89,7 +88,6 @@ export default withPermissions(
};
},
{
- fetchSlackList,
fetchV2,
clearCurrentSession,
}
diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx
index 93970d515..8ccd9f49e 100644
--- a/frontend/app/components/Session/WebPlayer.tsx
+++ b/frontend/app/components/Session/WebPlayer.tsx
@@ -9,7 +9,6 @@ import { toast } from 'react-toastify';
import { useStore } from 'App/mstore';
import { Note } from 'App/services/NotesService';
-import { fetchList } from 'Duck/integrations';
import { Loader, Modal } from 'UI';
import ReadNote from '../Session_/Player/Controls/components/ReadNote';
@@ -36,10 +35,9 @@ let playerInst: IPlayerContext['player'] | undefined;
function WebPlayer(props: any) {
const {
session,
- fetchList,
startedAt,
} = props;
- const { notesStore, sessionStore, uxtestingStore, uiPlayerStore } = useStore();
+ const { notesStore, sessionStore, uxtestingStore, uiPlayerStore, integrationsStore } = useStore();
const fullscreen = uiPlayerStore.fullscreen;
const toggleFullscreen = uiPlayerStore.toggleFullscreen;
const closeBottomBlock = uiPlayerStore.closeBottomBlock;
@@ -72,7 +70,7 @@ function WebPlayer(props: any) {
| Record
| undefined;
const usePrefetched = props.prefetched && mobData?.data;
- fetchList('issues');
+ void integrationsStore.issues.fetchIntegrations();
sessionStore.setUserTimezone(session.timezone);
const [WebPlayerInst, PlayerStore] = createWebPlayer(
session,
@@ -256,7 +254,5 @@ export default connect(
jwt: state.getIn(['user', 'jwt']),
startedAt: state.getIn(['sessions', 'current']).startedAt || 0,
}),
- {
- fetchList,
- }
+
)(withLocationHandlers()(observer(WebPlayer)));
diff --git a/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx b/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx
index 3da4a31d7..4d4eb49b6 100644
--- a/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx
+++ b/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx
@@ -1,10 +1,9 @@
import { Tag } from 'antd';
-import { List } from 'immutable';
import { Duration } from 'luxon';
import React from 'react';
import { connect } from 'react-redux';
import { toast } from 'react-toastify';
-
+import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import {
Note,
@@ -13,8 +12,6 @@ import {
iTag,
tagProps,
} from 'App/services/NotesService';
-import { fetchList as fetchSlack } from 'Duck/integrations/slack';
-import { fetchList as fetchTeams } from 'Duck/integrations/teams';
import { addNote, updateNote } from 'Duck/sessions';
import { Button, Checkbox, Icon } from 'UI';
@@ -27,10 +24,6 @@ interface Props {
sessionId: string;
isEdit?: boolean;
editNote?: WriteNote;
- slackChannels: List>;
- teamsChannels: List>;
- fetchSlack: () => void;
- fetchTeams: () => void;
hideModal: () => void;
}
@@ -40,12 +33,13 @@ function CreateNote({
isEdit,
editNote,
updateNote,
- slackChannels,
- fetchSlack,
- teamsChannels,
- fetchTeams,
hideModal,
}: Props) {
+ const { notesStore, integrationsStore } = useStore();
+ const slackChannels = integrationsStore.slack.list;
+ const fetchSlack = integrationsStore.slack.fetchIntegrations;
+ const teamsChannels = integrationsStore.msteams.list;
+ const fetchTeams = integrationsStore.msteams.fetchIntegrations;
const [text, setText] = React.useState('');
const [slackChannel, setSlackChannel] = React.useState('');
const [teamsChannel, setTeamsChannel] = React.useState('');
@@ -56,7 +50,6 @@ function CreateNote({
const [useTeams, setTeams] = React.useState(false);
const inputRef = React.createRef();
- const { notesStore } = useStore();
React.useEffect(() => {
if (isEdit && editNote) {
@@ -151,14 +144,12 @@ function CreateNote({
.map(({ webhookId, name }) => ({
value: webhookId,
label: name,
- }))
- .toJS() as unknown as { value: string; label: string }[];
+ })) as unknown as { value: string; label: string }[];
const teamsChannelsOptions = teamsChannels
.map(({ webhookId, name }) => ({
value: webhookId,
label: name,
- }))
- .toJS() as unknown as { value: string; label: string }[];
+ })) as unknown as { value: string; label: string }[];
slackChannelsOptions.unshift({
// @ts-ignore
@@ -334,10 +325,8 @@ function CreateNote({
export default connect(
(state: any) => {
- const slackChannels = state.getIn(['slack', 'list']);
- const teamsChannels = state.getIn(['teams', 'list']);
const sessionId = state.getIn(['sessions', 'current']).sessionId;
- return { sessionId, slackChannels, teamsChannels };
+ return { sessionId };
},
- { addNote, updateNote, fetchSlack, fetchTeams }
-)(CreateNote);
+ { addNote, updateNote,}
+)(observer(CreateNote));
diff --git a/frontend/app/components/shared/SharePopup/SharePopup.tsx b/frontend/app/components/shared/SharePopup/SharePopup.tsx
index ce7955924..66b61ef81 100644
--- a/frontend/app/components/shared/SharePopup/SharePopup.tsx
+++ b/frontend/app/components/shared/SharePopup/SharePopup.tsx
@@ -7,11 +7,10 @@ import styles from './sharePopup.module.css';
import IntegrateSlackButton from '../IntegrateSlackButton/IntegrateSlackButton';
import SessionCopyLink from './SessionCopyLink';
import Select from 'Shared/Select';
-import { fetchList as fetchSlack, sendSlackMsg } from 'Duck/integrations/slack';
-import { fetchList as fetchTeams, sendMsTeamsMsg } from 'Duck/integrations/teams';
import { Button, Segmented } from 'antd';
import { PlayerContext } from 'App/components/Session/playerContext';
import { observer } from 'mobx-react-lite';
+import { useStore } from 'App/mstore';
interface Msg {
integrationId: string;
@@ -51,15 +50,7 @@ const SharePopup = ({
interface Props {
sessionId: string;
- channels: { webhookId: string; name: string }[];
- slackLoaded: boolean;
- msTeamsChannels: { webhookId: string; name: string }[];
- msTeamsLoaded: boolean;
tenantId: string;
- fetchSlack: () => void;
- fetchTeams: () => void;
- sendSlackMsg: (msg: Msg) => any;
- sendMsTeamsMsg: (msg: Msg) => any;
showCopyLink?: boolean;
hideModal: () => void;
time: number;
@@ -67,18 +58,20 @@ interface Props {
function ShareModalComp({
sessionId,
- sendSlackMsg,
- sendMsTeamsMsg,
showCopyLink,
- channels,
- slackLoaded,
- msTeamsChannels,
- msTeamsLoaded,
- fetchSlack,
- fetchTeams,
hideModal,
time,
}: Props) {
+ const { integrationsStore } = useStore();
+ const channels = integrationsStore.slack.list;
+ const slackLoaded = integrationsStore.slack.loaded;
+ const msTeamsChannels = integrationsStore.msteams.list;
+ const msTeamsLoaded = integrationsStore.msteams.loaded;
+ const fetchSlack = integrationsStore.slack.fetchIntegrations;
+ const fetchTeams = integrationsStore.msteams.fetchIntegrations;
+ const sendSlackMsg = integrationsStore.slack.sendMessage;
+ const sendMsTeamsMsg = integrationsStore.msteams.sendMessage;
+
const [shareTo, setShareTo] = useState('slack');
const [comment, setComment] = useState('');
// @ts-ignore
@@ -104,7 +97,7 @@ function ShareModalComp({
const editMessage = (e: React.ChangeEvent) => setComment(e.target.value);
const shareToSlack = () => {
setLoadingSlack(true);
- sendSlackMsg({
+ void sendSlackMsg({
integrationId: channelId,
entity: 'sessions',
entityId: sessionId,
@@ -140,16 +133,12 @@ function ShareModalComp({
value: webhookId,
label: name,
}))
- // @ts-ignore
- .toJS();
const msTeamsOptions = msTeamsChannels
.map(({ webhookId, name }) => ({
value: webhookId,
label: name,
}))
- // @ts-ignore
- .toJS();
const sendMsg = () => {
if (shareTo === 'slack') {
@@ -279,18 +268,9 @@ function ShareModalComp({
const mapStateToProps = (state: Record) => ({
sessionId: state.getIn(['sessions', 'current']).sessionId,
- channels: state.getIn(['slack', 'list']),
- slackLoaded: state.getIn(['slack', 'loaded']),
- msTeamsChannels: state.getIn(['teams', 'list']),
- msTeamsLoaded: state.getIn(['teams', 'loaded']),
tenantId: state.getIn(['user', 'account', 'tenantId']),
});
-const ShareModal = connect(mapStateToProps, {
- fetchSlack,
- fetchTeams,
- sendSlackMsg,
- sendMsTeamsMsg,
-})(ShareModalComp);
+const ShareModal = connect(mapStateToProps)(ShareModalComp);
export default observer(SharePopup);
diff --git a/frontend/app/duck/integrations/teams.js b/frontend/app/duck/integrations/teams.js
index e77835a37..29fac4710 100644
--- a/frontend/app/duck/integrations/teams.js
+++ b/frontend/app/duck/integrations/teams.js
@@ -93,8 +93,6 @@ export function remove(id) {
};
}
-// https://api.openreplay.com/5587/integrations/msteams/notify/315/sessions/7856803626558104
-//
export function sendMsTeamsMsg({ integrationId, entity, entityId, data }) {
return {
types: SEND_MSG.toArray(),
diff --git a/frontend/app/mstore/index.tsx b/frontend/app/mstore/index.tsx
index 723655adb..b127fa585 100644
--- a/frontend/app/mstore/index.tsx
+++ b/frontend/app/mstore/index.tsx
@@ -27,6 +27,7 @@ import FilterStore from './filterStore';
import UiPlayerStore from './uiPlayerStore';
import IssueReportingStore from './issueReportingStore';
import CustomFieldStore from './customFieldStore';
+import { IntegrationsStore } from "./integrationsStore";
export class RootStore {
dashboardStore: DashboardStore;
@@ -55,6 +56,7 @@ export class RootStore {
uiPlayerStore: UiPlayerStore;
issueReportingStore: IssueReportingStore;
customFieldStore: CustomFieldStore;
+ integrationsStore: IntegrationsStore
constructor() {
this.dashboardStore = new DashboardStore();
@@ -83,6 +85,7 @@ export class RootStore {
this.uiPlayerStore = new UiPlayerStore();
this.issueReportingStore = new IssueReportingStore();
this.customFieldStore = new CustomFieldStore();
+ this.integrationsStore = new IntegrationsStore();
}
initClient() {
diff --git a/frontend/app/mstore/integrationsStore.ts b/frontend/app/mstore/integrationsStore.ts
index e10e23158..fd3b7fc8c 100644
--- a/frontend/app/mstore/integrationsStore.ts
+++ b/frontend/app/mstore/integrationsStore.ts
@@ -1,7 +1,9 @@
import { makeAutoObservable } from 'mobx';
import { integrationsService } from 'App/services';
+import ElasticsearchForm from "../components/Client/Integrations/ElasticsearchForm";
+import { MessengerConfig } from './types/integrations/messengers';
import {
Bugsnag,
Cloudwatch,
@@ -16,11 +18,12 @@ import {
SentryInt,
StackDriverInt,
SumoLogic,
-} from './types/integrations';
+} from './types/integrations/services';
class GenericIntegrationsStore {
list: any[] = [];
- siteId: string | null = null;
+ isLoading: boolean = false;
+ siteId: string = '';
constructor() {
makeAutoObservable(this);
}
@@ -33,9 +36,20 @@ class GenericIntegrationsStore {
this.list = list;
}
- fetchIntegrations = async () => {
- //client.get(`/${siteID}/integrations`)
- // this.setList()
+ setLoading(loading: boolean) {
+ this.isLoading = loading;
+ }
+
+ fetchIntegrations = async (siteId?: string) => {
+ this.setLoading(true);
+ try {
+ const { data } = await integrationsService.fetchList(siteId);
+ this.setList(data);
+ } catch (e) {
+ console.log(e);
+ } finally {
+ this.setLoading(false);
+ }
};
}
@@ -44,83 +58,235 @@ class NamedIntegrationStore {
list: T[] = [];
fetched: boolean = false;
issuesFetched: boolean = false;
+ loading = false;
constructor(
private readonly name: string,
- private readonly NamedType: new (config: Record) => T
+ private readonly namedTypeCreator: (config: Record) => T
) {
+ this.instance = namedTypeCreator({});
makeAutoObservable(this);
}
+ setLoading(loading: boolean): void {
+ this.loading = loading;
+ }
+
setInstance(instance: T): void {
this.instance = instance;
}
- setList(list: T[]): void {
+ setList = (list: T[]): void => {
this.list = list;
}
- setFetched(fetched: boolean): void {
+ setFetched = (fetched: boolean): void => {
this.fetched = fetched;
}
- setIssuesFetched(issuesFetched: boolean): void {
+ setIssuesFetched = (issuesFetched: boolean): void => {
this.issuesFetched = issuesFetched;
}
fetchIntegrations = async (): Promise => {
- const { data } = await integrationsService.fetchList(this.name);
- this.setList(
- data.map((config: Record) => new this.NamedType(config))
- );
+ this.setLoading(true);
+ try {
+ const { data } = await integrationsService.fetchList(this.name);
+ this.setList(
+ data.map((config: Record) => this.namedTypeCreator(config))
+ );
+ } catch (e) {
+ console.log(e);
+ } finally {
+ this.setFetched(true);
+ this.setLoading(false);
+ }
};
- fetchIntegration = async (siteId: string): void => {
- const { data } = await integrationsService.fetchIntegration(
- this.name,
- siteId
- );
- this.setInstance(new this.NamedType(data));
+ fetchIntegration = async (siteId: string): Promise => {
+ this.setLoading(true);
+ try {
+ const { data } = await integrationsService.fetchIntegration(
+ this.name,
+ siteId
+ );
+ this.setInstance(this.namedTypeCreator(data));
+ } catch (e) {
+ console.log(e);
+ } finally {
+ this.setLoading(false);
+ }
};
- saveIntegration(name: string, siteId: string): void {
+ saveIntegration = async (name: string, siteId?: string): Promise => {
if (!this.instance) return;
- const response = integrationsService.saveIntegration(
- name,
- siteId,
- this.instance.toData()
+ await integrationsService.saveIntegration(
+ this.name ?? name,
+ this.instance.toData(),
+ siteId
);
return;
}
- edit(data: T): void {
- this.setInstance(data);
+ edit = (data: T): void => {
+ if (!this.instance) {
+ this.instance = this.namedTypeCreator({});
+ }
+ this.instance.edit(data);
}
- deleteIntegration(siteId: string) {
+ deleteIntegration = async (siteId?: string) => {
if (!this.instance) return;
return integrationsService.removeIntegration(this.name, siteId);
}
- init(config: Record): void {
- this.instance = new this.NamedType(config);
+ init = (config: Record): void => {
+ this.instance = this.namedTypeCreator(config);
}
}
-export class IntegrationsStore {
- sentry = new NamedIntegrationStore('sentry', SentryInt);
- datadog = new NamedIntegrationStore('datadog', DatadogInt);
- stackdriver = new NamedIntegrationStore('stackdriver', StackDriverInt);
- rollbar = new NamedIntegrationStore('rollbar', RollbarInt);
- newrelic = new NamedIntegrationStore('newrelic', NewRelicInt);
- bugsnag = new NamedIntegrationStore('bugsnag', Bugsnag);
- cloudwatch = new NamedIntegrationStore('cloudwatch', Cloudwatch);
- elasticsearch = new NamedIntegrationStore('elasticsearch', ElasticSearchInt);
- sumologic = new NamedIntegrationStore('sumologic', SumoLogic);
- jira = new NamedIntegrationStore('jira', JiraInt);
- github = new NamedIntegrationStore('github', GithubInt);
- issues = new NamedIntegrationStore('issues', IssueTracker);
- integrations = new GenericIntegrationsStore();
- // + slack
- // + teams
+class MessengerIntegrationStore {
+ list: MessengerConfig[] = [];
+ instance: MessengerConfig | null = null;
+ loaded: boolean = false;
+ loading: boolean = false;
+ errors: any[] = [];
+
+ constructor(private readonly mName: 'slack' | 'msteams') {
+ makeAutoObservable(this);
+ }
+
+ setList(list: MessengerConfig[]): void {
+ this.list = list;
+ }
+
+ setLoading(loading: boolean): void {
+ this.loading = loading;
+ }
+
+ setInstance(instance: MessengerConfig): void {
+ this.instance = instance;
+ }
+
+ setLoaded(loaded: boolean): void {
+ this.loaded = loaded;
+ }
+
+ setErrors = (errors: any[]) => {
+ this.errors = errors;
+ };
+
+ saveIntegration = async (): Promise => {
+ // redux todo: errors
+ if (!this.instance) return;
+ this.setLoading(true);
+ try {
+ await integrationsService.saveIntegration(
+ this.mName,
+ this.instance.toData(),
+ undefined
+ );
+ this.setList([...this.list, this.instance]);
+ } catch (e) {
+ console.log(e);
+ this.setErrors(["Couldn't process the request: check your data."]);
+ } finally {
+ this.setLoading(false);
+ }
+ };
+
+ fetchIntegrations = async (): Promise => {
+ const { data } = await integrationsService.fetchMessengerChannels(
+ this.mName
+ );
+ this.setList(
+ data.map((config: Record) => new MessengerConfig(config))
+ );
+ this.setLoaded(true);
+ };
+
+ sendMessage = ({
+ integrationId,
+ entity,
+ entityId,
+ data,
+ }: {
+ integrationId: string;
+ entity: string;
+ entityId: string;
+ data: any;
+ }) => {
+ return integrationsService.sendMsg(
+ integrationId,
+ entity,
+ entityId,
+ this.mName,
+ data
+ );
+ };
+
+ init = (config: Record): void => {
+ this.instance = new MessengerConfig(config);
+ };
+
+ removeInt = async (intId: string) => {
+ await integrationsService.removeMessengerInt(this.mName, intId);
+ this.setList(this.list.filter((int) => int.webhookId !== intId));
+ };
+
+ edit = (data: Record): void => {
+ if (!this.instance) {
+ this.instance = new MessengerConfig({});
+ }
+ this.instance.edit(data);
+ };
+
+ update = async () => {
+ // redux todo: errors
+ if (!this.instance) return;
+ this.setLoading(true);
+ await integrationsService.updateMessengerInt(
+ this.mName,
+ this.instance.toData()
+ );
+ this.setList(
+ this.list.map((int) =>
+ int.webhookId === this.instance?.webhookId ? this.instance : int
+ )
+ );
+ this.setLoading(false);
+ };
+}
+export type namedStore = 'sentry'
+ | 'datadog'
+ | 'stackdriver'
+ | 'rollbar'
+ | 'newrelic'
+ | 'bugsnag'
+ | 'cloudwatch'
+ | 'elasticsearch'
+ | 'sumologic'
+ | 'jira'
+ | 'github'
+ | 'issues'
+export class IntegrationsStore {
+ sentry = new NamedIntegrationStore('sentry', (d) => new SentryInt(d));
+ datadog = new NamedIntegrationStore('datadog', (d) => new DatadogInt(d));
+ stackdriver = new NamedIntegrationStore('stackdriver', (d) => new StackDriverInt(d));
+ rollbar = new NamedIntegrationStore('rollbar', (d) => new RollbarInt(d));
+ newrelic = new NamedIntegrationStore('newrelic', (d) => new NewRelicInt(d));
+ bugsnag = new NamedIntegrationStore('bugsnag', (d) => new Bugsnag(d));
+ cloudwatch = new NamedIntegrationStore('cloudwatch', (d) => new Cloudwatch(d));
+ elasticsearch = new NamedIntegrationStore('elasticsearch', (d) => new ElasticSearchInt(d));
+ sumologic = new NamedIntegrationStore('sumologic', (d) => new SumoLogic(d));
+ jira = new NamedIntegrationStore('jira', (d) => new JiraInt(d));
+ github = new NamedIntegrationStore('github', (d) => new GithubInt(d));
+ issues = new NamedIntegrationStore('issues', (d) => new IssueTracker(d));
+ integrations = new GenericIntegrationsStore();
+ slack = new MessengerIntegrationStore('slack');
+ msteams = new MessengerIntegrationStore('msteams');
+
+ constructor() {
+ makeAutoObservable(this);
+ }
}
diff --git a/frontend/app/mstore/types/integrations/consts.ts b/frontend/app/mstore/types/integrations/consts.ts
new file mode 100644
index 000000000..6e9561657
--- /dev/null
+++ b/frontend/app/mstore/types/integrations/consts.ts
@@ -0,0 +1,37 @@
+export const sumoRegionLabels = {
+ au: 'Asia Pacific (Sydney)',
+ ca: 'Canada (Central)',
+ de: 'EU (Frankfurt)',
+ eu: 'EU (Ireland)',
+ fed: 'US East (N. Virginia)',
+ in: 'Asia Pacific (Mumbai)',
+ jp: 'Asia Pacific (Tokyo)',
+ us1: 'US East (N. Virginia)',
+ us2: 'US West (Oregon)',
+};
+export const API_KEY_ID_LENGTH = 5;
+export const API_KEY_LENGTH = 5;
+export const SECRET_ACCESS_KEY_LENGTH = 40;
+export const ACCESS_KEY_ID_LENGTH = 20;
+export const tokenRE =
+ /^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$/i;
+export const awsRegionLabels = {
+ 'us-east-1': 'US East (N. Virginia)',
+ 'us-east-2': 'US East (Ohio)',
+ 'us-west-1': 'US West (N. California)',
+ 'us-west-2': 'US West (Oregon)',
+ 'ap-east-1': 'Asia Pacific (Hong Kong)',
+ 'ap-south-1': 'Asia Pacific (Mumbai)',
+ 'ap-northeast-2': 'Asia Pacific (Seoul)',
+ 'ap-southeast-1': 'Asia Pacific (Singapore)',
+ 'ap-southeast-2': 'Asia Pacific (Sydney)',
+ 'ap-northeast-1': 'Asia Pacific (Tokyo)',
+ 'ca-central-1': 'Canada (Central)',
+ 'eu-central-1': 'EU (Frankfurt)',
+ 'eu-west-1': 'EU (Ireland)',
+ 'eu-west-2': 'EU (London)',
+ 'eu-west-3': 'EU (Paris)',
+ 'eu-north-1': 'EU (Stockholm)',
+ 'me-south-1': 'Middle East (Bahrain)',
+ 'sa-east-1': 'South America (São Paulo)',
+};
diff --git a/frontend/app/mstore/types/integrations/messengers.ts b/frontend/app/mstore/types/integrations/messengers.ts
new file mode 100644
index 000000000..1e6ae9300
--- /dev/null
+++ b/frontend/app/mstore/types/integrations/messengers.ts
@@ -0,0 +1,41 @@
+import { validateURL } from "App/validate";
+import { makeAutoObservable } from "mobx";
+
+export class MessengerConfig {
+ endpoint: string = "";
+ name: string = "";
+ webhookId: string = "";
+
+ constructor(config: any) {
+ Object.assign(this, {
+ endpoint: config.endpoint,
+ name: config.name,
+ webhookId: config.webhookId
+ });
+ makeAutoObservable(this);
+ }
+
+ edit = (data: any): void => {
+ Object.keys(data).forEach((key) => {
+ // @ts-ignore
+ this[key] = data[key];
+ })
+ }
+
+ validate(): boolean {
+ return this.endpoint !== '' && this.name != '' && validateURL(this.endpoint);
+ }
+
+ exists(): boolean {
+ return !!this.webhookId;
+ }
+
+ toData(): { endpoint: string, url: string, name: string, webhookId: string } {
+ return {
+ endpoint: this.endpoint,
+ url: this.endpoint,
+ name: this.name,
+ webhookId: this.webhookId
+ };
+ }
+}
diff --git a/frontend/app/mstore/types/integrations.ts b/frontend/app/mstore/types/integrations/services.ts
similarity index 73%
rename from frontend/app/mstore/types/integrations.ts
rename to frontend/app/mstore/types/integrations/services.ts
index 3b3eec493..0acbdc687 100644
--- a/frontend/app/mstore/types/integrations.ts
+++ b/frontend/app/mstore/types/integrations/services.ts
@@ -2,10 +2,21 @@ import { makeAutoObservable } from 'mobx';
import { validateURL } from 'App/validate';
+import {
+ ACCESS_KEY_ID_LENGTH,
+ API_KEY_ID_LENGTH,
+ API_KEY_LENGTH,
+ SECRET_ACCESS_KEY_LENGTH,
+ awsRegionLabels,
+ sumoRegionLabels,
+ tokenRE,
+} from './consts';
+
export interface Integration {
validate(): boolean;
exists(): boolean;
toData(): Record;
+ edit(data: Record): void;
}
export class SentryInt implements Integration {
@@ -17,10 +28,17 @@ export class SentryInt implements Integration {
constructor(config: any) {
Object.assign(this, {
...config,
- projectId: config.projectId || -1,
+ projectId: config?.projectId ?? -1,
});
+ makeAutoObservable(this);
}
+ edit = (data: Record) => {
+ Object.keys(data).forEach((key) => {
+ this[key] = data[key];
+ });
+ };
+
validate() {
return Boolean(this.organizationSlug && this.projectSlug && this.token);
}
@@ -47,10 +65,17 @@ export class DatadogInt implements Integration {
constructor(config: any) {
Object.assign(this, {
...config,
- projectId: config.projectId || -1,
+ projectId: config?.projectId ?? -1,
});
+ makeAutoObservable(this);
}
+ edit = (data: Record) => {
+ Object.keys(data).forEach((key) => {
+ this[key] = data[key];
+ });
+ };
+
validate() {
return Boolean(this.apiKey && this.applicationKey);
}
@@ -76,10 +101,17 @@ export class StackDriverInt implements Integration {
constructor(config: any) {
Object.assign(this, {
...config,
- projectId: config.projectId || -1,
+ projectId: config?.projectId ?? -1,
});
+ makeAutoObservable(this);
}
+ edit = (data: Record) => {
+ Object.keys(data).forEach((key) => {
+ this[key] = data[key];
+ });
+ };
+
validate() {
return Boolean(
this.serviceAccountCredentials !== '' && this.logName !== ''
@@ -106,10 +138,17 @@ export class RollbarInt implements Integration {
constructor(config: any) {
Object.assign(this, {
...config,
- projectId: config.projectId || -1,
+ projectId: config?.projectId ?? -1,
});
+ makeAutoObservable(this);
}
+ edit = (data: Record) => {
+ Object.keys(data).forEach((key) => {
+ this[key] = data[key];
+ });
+ };
+
validate() {
return Boolean(this.accessToken);
}
@@ -135,10 +174,17 @@ export class NewRelicInt implements Integration {
constructor(config: any) {
Object.assign(this, {
...config,
- projectId: config.projectId || -1,
+ projectId: config?.projectId ?? -1,
});
+ makeAutoObservable(this);
}
+ edit = (data: Record) => {
+ Object.keys(data).forEach((key) => {
+ this[key] = data[key];
+ });
+ };
+
validate() {
return Boolean(this.applicationId && this.xQueryKey);
}
@@ -165,10 +211,17 @@ export class Bugsnag implements Integration {
constructor(config: any) {
Object.assign(this, {
...config,
- projectId: config.projectId || -1,
+ projectId: config?.projectId ?? -1,
});
+ makeAutoObservable(this);
}
+ edit = (data: Record) => {
+ Object.keys(data).forEach((key) => {
+ this[key] = data[key];
+ });
+ };
+
validate() {
return Boolean(
this.bugsnagProjectId !== '' && tokenRE.test(this.authorizationToken)
@@ -198,10 +251,17 @@ export class Cloudwatch implements Integration {
constructor(config: any) {
Object.assign(this, {
...config,
- projectId: config.projectId || -1,
+ projectId: config?.projectId ?? -1,
});
+ makeAutoObservable(this);
}
+ edit = (data: Record) => {
+ Object.keys(data).forEach((key) => {
+ this[key] = data[key];
+ });
+ };
+
validate() {
return Boolean(
this.awsAccessKeyId !== '' &&
@@ -237,10 +297,17 @@ export class ElasticSearchInt implements Integration {
constructor(config: any) {
Object.assign(this, {
...config,
- projectId: config.projectId || -1,
+ projectId: config?.projectId ?? -1,
});
+ makeAutoObservable(this);
}
+ edit = (data: Record) => {
+ Object.keys(data).forEach((key) => {
+ this[key] = data[key];
+ });
+ };
+
private validateKeys() {
return Boolean(
this.apiKeyId.length > API_KEY_ID_LENGTH &&
@@ -285,10 +352,17 @@ export class SumoLogic implements Integration {
constructor(config: any) {
Object.assign(this, {
...config,
- projectId: config.projectId || -1,
+ projectId: config?.projectId ?? -1,
});
+ makeAutoObservable(this);
}
+ edit = (data: Record) => {
+ Object.keys(data).forEach((key) => {
+ this[key] = data[key];
+ });
+ };
+
validate() {
return Boolean(this.accessKey && this.accessId);
}
@@ -316,10 +390,17 @@ export class JiraInt implements Integration {
constructor(config: any) {
Object.assign(this, {
...config,
- projectId: config.projectId || -1,
+ projectId: config?.projectId ?? -1,
});
+ makeAutoObservable(this);
}
+ edit = (data: Record) => {
+ Object.keys(data).forEach((key) => {
+ this[key] = data[key];
+ });
+ };
+
validateFetchProjects() {
return this.username !== '' && this.token !== '' && validateURL(this.url);
}
@@ -350,10 +431,17 @@ export class GithubInt implements Integration {
constructor(config: any) {
Object.assign(this, {
...config,
- projectId: config.projectId || -1,
+ projectId: config?.projectId ?? -1,
});
+ makeAutoObservable(this);
}
+ edit = (data: Record) => {
+ Object.keys(data).forEach((key) => {
+ this[key] = data[key];
+ });
+ };
+
validate() {
return this.token !== '';
}
@@ -381,8 +469,15 @@ export class IssueTracker implements Integration {
Object.assign(this, {
...config,
});
+ makeAutoObservable(this);
}
+ edit = (data: Record) => {
+ Object.keys(data).forEach((key) => {
+ this[key] = data[key];
+ });
+ };
+
validateFetchProjects() {
return this.username !== '' && this.token !== '' && validateURL(this.url);
}
@@ -404,41 +499,3 @@ export class IssueTracker implements Integration {
};
}
}
-
-export const sumoRegionLabels = {
- au: 'Asia Pacific (Sydney)',
- ca: 'Canada (Central)',
- de: 'EU (Frankfurt)',
- eu: 'EU (Ireland)',
- fed: 'US East (N. Virginia)',
- in: 'Asia Pacific (Mumbai)',
- jp: 'Asia Pacific (Tokyo)',
- us1: 'US East (N. Virginia)',
- us2: 'US West (Oregon)',
-};
-export const API_KEY_ID_LENGTH = 5;
-export const API_KEY_LENGTH = 5;
-export const SECRET_ACCESS_KEY_LENGTH = 40;
-export const ACCESS_KEY_ID_LENGTH = 20;
-export const tokenRE =
- /^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$/i;
-export const awsRegionLabels = {
- 'us-east-1': 'US East (N. Virginia)',
- 'us-east-2': 'US East (Ohio)',
- 'us-west-1': 'US West (N. California)',
- 'us-west-2': 'US West (Oregon)',
- 'ap-east-1': 'Asia Pacific (Hong Kong)',
- 'ap-south-1': 'Asia Pacific (Mumbai)',
- 'ap-northeast-2': 'Asia Pacific (Seoul)',
- 'ap-southeast-1': 'Asia Pacific (Singapore)',
- 'ap-southeast-2': 'Asia Pacific (Sydney)',
- 'ap-northeast-1': 'Asia Pacific (Tokyo)',
- 'ca-central-1': 'Canada (Central)',
- 'eu-central-1': 'EU (Frankfurt)',
- 'eu-west-1': 'EU (Ireland)',
- 'eu-west-2': 'EU (London)',
- 'eu-west-3': 'EU (Paris)',
- 'eu-north-1': 'EU (Stockholm)',
- 'me-south-1': 'Middle East (Bahrain)',
- 'sa-east-1': 'South America (São Paulo)',
-};
diff --git a/frontend/app/services/IntegrationsService.ts b/frontend/app/services/IntegrationsService.ts
index d54f90aad..c39595a2a 100644
--- a/frontend/app/services/IntegrationsService.ts
+++ b/frontend/app/services/IntegrationsService.ts
@@ -1,8 +1,8 @@
import BaseService from "./BaseService";
export default class IntegrationsService extends BaseService {
- fetchList = async (name: string) => {
- const r = await this.client.get(`/integrations/${name}`)
+ fetchList = async (name?: string, siteId?: string) => {
+ const r = await this.client.get(`${siteId ? `/${siteId}` : ''}/integrations/${name}`)
const data = await r.json()
return data
@@ -16,7 +16,7 @@ export default class IntegrationsService extends BaseService {
return data
}
- saveIntegration = async (name: string, siteId: string, data: any) => {
+ saveIntegration = async (name: string, data: any, siteId?: string) => {
const url = (siteId ? `/${siteId}` : '') + `/integrations/${name}`
const r = await this.client.post(url, data)
const res = await r.json()
@@ -24,11 +24,40 @@ export default class IntegrationsService extends BaseService {
return res
}
- removeIntegration = async (name: string, siteId: string) => {
+ removeIntegration = async (name: string, siteId?: string) => {
const url = (siteId ? `/${siteId}` : '') + `/integrations/${name}`
const r = await this.client.delete(url)
- const res = await r.json()
- return res
+ return await r.json()
+ }
+
+ fetchMessengerChannels = async (name: string) => {
+ const r = await this.client.get(`/integrations/${name}/channels`)
+
+ return await r.json()
+ }
+
+ updateMessengerInt = async (name: string, data: any) => {
+ const r = await this.client.put(`/integrations/${name}/${data.webhookId}`, data)
+
+ return await r.json()
+ }
+
+ removeMessengerInt = async (name: string, webhookId: string) => {
+ const r = await this.client.delete(`/integrations/${name}/${webhookId}`)
+
+ return await r.json()
+ }
+
+ sendMsg = async (integrationId, entity, entityId, name, data) => {
+ const r = await this.client.post(`/integrations/${name}/notify/${integrationId}/${entity}/${entityId}`, data)
+
+ return await r.json()
+ }
+
+ testElastic = async (data: any) => {
+ const r = await this.client.post('/integrations/elasticsearch/test', data)
+
+ return r.json();
}
}