How to integrate Datadog with OpenReplay and see backend errors alongside session recordings.
- >
);
-DatadogForm.displayName = "DatadogForm";
+DatadogForm.displayName = 'DatadogForm';
export default DatadogForm;
diff --git a/frontend/app/components/Client/Integrations/ElasticsearchForm.js b/frontend/app/components/Client/Integrations/ElasticsearchForm.js
index 271ccefe1..ad33b6302 100644
--- a/frontend/app/components/Client/Integrations/ElasticsearchForm.js
+++ b/frontend/app/components/Client/Integrations/ElasticsearchForm.js
@@ -1,75 +1,88 @@
import React from 'react';
import { connect } from 'react-redux';
-import IntegrationForm from './IntegrationForm';
+import IntegrationForm from './IntegrationForm';
import { withRequest } from 'HOCs';
import { edit } from 'Duck/integrations/actions';
import DocLink from 'Shared/DocLink/DocLink';
-@connect(state => ({
- config: state.getIn([ 'elasticsearch', 'instance' ])
-}), { edit })
+@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',
+ 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);
+ 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);
+ }
}
- }
- 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 })
- });
- }
+ 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 });
+ });
+ };
- render() {
- const props = this.props;
- return (
- <>
-
- >
- )
- }
-};
+ render() {
+ const props = this.props;
+ return (
+
+ );
+ }
+}
diff --git a/frontend/app/components/Client/Integrations/FetchDoc/FetchDoc.js b/frontend/app/components/Client/Integrations/FetchDoc/FetchDoc.js
index 8d9bbd5b9..b4b8b537d 100644
--- a/frontend/app/components/Client/Integrations/FetchDoc/FetchDoc.js
+++ b/frontend/app/components/Client/Integrations/FetchDoc/FetchDoc.js
@@ -1,29 +1,32 @@
import React from 'react';
-import Highlight from 'react-highlight'
-import ToggleContent from 'Shared/ToggleContent'
+import Highlight from 'react-highlight';
+import ToggleContent from 'Shared/ToggleContent';
import DocLink from 'Shared/DocLink/DocLink';
const FetchDoc = (props) => {
- const { projectKey } = props;
- return (
-
+ );
};
-FetchDoc.displayName = "FetchDoc";
+FetchDoc.displayName = 'FetchDoc';
export default FetchDoc;
diff --git a/frontend/app/components/Client/Integrations/GithubForm.js b/frontend/app/components/Client/Integrations/GithubForm.js
index 586ab3093..7d140732b 100644
--- a/frontend/app/components/Client/Integrations/GithubForm.js
+++ b/frontend/app/components/Client/Integrations/GithubForm.js
@@ -1,30 +1,31 @@
import React from 'react';
-import IntegrationForm from './IntegrationForm';
+import IntegrationForm from './IntegrationForm';
import DocLink from 'Shared/DocLink/DocLink';
const GithubForm = (props) => (
- <>
-
-
Integrate GitHub with OpenReplay and create issues directly from the recording page.
-
-
-
+
+
Github
+
+
Integrate GitHub with OpenReplay and create issues directly from the recording page.
+
+
+
+
+
-
- >
);
-GithubForm.displayName = "GithubForm";
+GithubForm.displayName = 'GithubForm';
-export default GithubForm;
\ No newline at end of file
+export default GithubForm;
diff --git a/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js b/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js
index a9150bc44..36e883f25 100644
--- a/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js
+++ b/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js
@@ -1,30 +1,36 @@
import React from 'react';
-import Highlight from 'react-highlight'
+import Highlight from 'react-highlight';
import DocLink from 'Shared/DocLink/DocLink';
import ToggleContent from 'Shared/ToggleContent';
const GraphQLDoc = (props) => {
- const { projectKey } = props;
- return (
-
-
This plugin allows you to capture GraphQL requests and inspect them later on while replaying session recordings. This is very useful for understanding and fixing issues.
-
GraphQL plugin is compatible with Apollo and Relay implementations.
-
-
Installation
-
- {`npm i @openreplay/tracker-graphql --save`}
-
-
-
Usage
-
The plugin call will return the function, which receives four variables operationKind, operationName, variables and result. It returns result without changes.
-
-
+ const { projectKey } = props;
+ return (
+
+
GraphQL
+
+
+ This plugin allows you to capture GraphQL requests and inspect them later on while replaying session recordings. This is very
+ useful for understanding and fixing issues.
+
+
GraphQL plugin is compatible with Apollo and Relay implementations.
-
- {`import OpenReplay from '@openreplay/tracker';
+ Installation
+ {`npm i @openreplay/tracker-graphql --save`}
+
+ Usage
+
+ The plugin call will return the function, which receives four variables operationKind, operationName, variables and result. It
+ returns result without changes.
+
+
+
+
+
+ {`import OpenReplay from '@openreplay/tracker';
import trackerGraphQL from '@openreplay/tracker-graphql';
//...
const tracker = new OpenReplay({
@@ -33,11 +39,11 @@ const tracker = new OpenReplay({
tracker.start();
//...
export const recordGraphQL = tracker.use(trackerGraphQL());`}
-
- }
- second={
-
- {`import OpenReplay from '@openreplay/tracker/cjs';
+
+ }
+ second={
+
+ {`import OpenReplay from '@openreplay/tracker/cjs';
import trackerGraphQL from '@openreplay/tracker-graphql/cjs';
//...
const tracker = new OpenReplay({
@@ -51,15 +57,16 @@ function SomeFunctionalComponent() {
}
//...
export const recordGraphQL = tracker.use(trackerGraphQL());`}
-
- }
- />
+
+ }
+ />
-
-
- )
+
+
+
+ );
};
-GraphQLDoc.displayName = "GraphQLDoc";
+GraphQLDoc.displayName = 'GraphQLDoc';
export default GraphQLDoc;
diff --git a/frontend/app/components/Client/Integrations/IntegrationForm.js b/frontend/app/components/Client/Integrations/IntegrationForm.js
index aeb28fe31..ad6689f3b 100644
--- a/frontend/app/components/Client/Integrations/IntegrationForm.js
+++ b/frontend/app/components/Client/Integrations/IntegrationForm.js
@@ -1,144 +1,147 @@
import React from 'react';
import { connect } from 'react-redux';
-import { Input, Form, Button, Checkbox } from 'UI';
+import { Input, Form, Button, Checkbox, Loader } from 'UI';
import SiteDropdown from 'Shared/SiteDropdown';
import { save, init, edit, remove, fetchList } 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']),
- saving: state.getIn([ customPath || name, 'saveRequest', 'loading']),
- removing: state.getIn([ name, 'removeRequest', 'loading']),
-}), {
- save,
- init,
- edit,
- remove,
- fetchList
-})
+@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);
- const currentSiteId = this.props.initialSiteId;
- this.state = { currentSiteId };
- this.init(currentSiteId);
- }
-
- 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 })
- };
+ constructor(props) {
+ super(props);
+ // const currentSiteId = this.props.initialSiteId;
+ // this.state = { currentSiteId };
+ // this.init(currentSiteId);
+ }
- 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);
- }
+ 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 });
+ };
- 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());
- }
+ // 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);
+ // };
- save = () => {
- const { config, name, customPath } = this.props;
- const isExists = config.exists();
- const { currentSiteId } = this.state;
- const { ignoreProject } = this.props;
- this.props.save(customPath || name, (!ignoreProject ? currentSiteId : null), config)
- .then(() => {
- this.props.fetchList(name)
- this.props.onClose();
- if (isExists) return;
- });
- }
+ // 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());
+ // };
- remove = () => {
- const { name, config, ignoreProject } = this.props;
- this.props.remove(name, !ignoreProject ? config.projectId : null).then(function() {
- this.props.onClose();
- this.props.fetchList(name)
- }.bind(this));
- }
+ 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.props.onClose();
+ if (isExists) return;
+ });
+ };
- render() {
- const { config, saving, removing, formFields, name, loading, ignoreProject } = this.props;
- const { currentSiteId } = this.state;
+ remove = () => {
+ const { name, config, ignoreProject } = this.props;
+ this.props.remove(name, !ignoreProject ? config.projectId : null).then(
+ function () {
+ this.props.onClose();
+ this.props.fetchList(name);
+ }.bind(this)
+ );
+ };
- return (
-
-
-
-
-
- }
+ render() {
+ const { config, saving, removing, formFields, name, loading, ignoreProject } = this.props;
+ // const { currentSiteId } = this.state;
- { formFields.map(({
- key,
- label,
- placeholder=label,
- component: Component = 'input',
- type = "text",
- checkIfDisplayed,
- autoFocus=false
- }) => (typeof checkIfDisplayed !== 'function' || checkIfDisplayed(config)) &&
- ((type === 'checkbox') ?
-
-
-
- :
-
-
-
-
- )
- )}
-
-
+ return (
+
+
+
+
+
+
+ )} */}
- {config.exists() && (
-
- )}
-
-
- );
- }
+ {formFields.map(
+ ({
+ key,
+ label,
+ placeholder = label,
+ component: Component = 'input',
+ type = 'text',
+ checkIfDisplayed,
+ autoFocus = false,
+ }) =>
+ (typeof checkIfDisplayed !== 'function' || checkIfDisplayed(config)) &&
+ (type === 'checkbox' ? (
+
+
+
+ ) : (
+
+
+
+
+ ))
+ )}
+
+
+
+ {config.exists() && (
+
+ )}
+
+
+
+ );
+ }
}
diff --git a/frontend/app/components/Client/Integrations/IntegrationItem.js b/frontend/app/components/Client/Integrations/IntegrationItem.js
deleted file mode 100644
index b0bfa258a..000000000
--- a/frontend/app/components/Client/Integrations/IntegrationItem.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from 'react';
-import cn from 'classnames';
-import { Icon } from 'UI';
-import stl from './integrationItem.module.css';
-
-const onDocLinkClick = (e, link) => {
- e.stopPropagation();
- window.open(link, '_blank');
-}
-
-const IntegrationItem = ({
- deleteHandler = null, icon, url = null, title = '', description = '', onClick = null, dockLink = '', integrated = false
-}) => {
- return (
-
onClick(e, url) }>
- {integrated && (
-
-
-
- )}
-

-
{ title }
-
- )
-};
-
-export default IntegrationItem;
diff --git a/frontend/app/components/Client/Integrations/IntegrationItem.tsx b/frontend/app/components/Client/Integrations/IntegrationItem.tsx
new file mode 100644
index 000000000..7e36adaef
--- /dev/null
+++ b/frontend/app/components/Client/Integrations/IntegrationItem.tsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import cn from 'classnames';
+import { Icon, Popup } from 'UI';
+import stl from './integrationItem.module.css';
+import { connect } from 'react-redux';
+
+interface Props {
+ integration: any;
+ onClick?: (e: React.MouseEvent
) => void;
+ integrated?: boolean;
+ hide?: boolean;
+}
+
+const IntegrationItem = (props: Props) => {
+ const { integration, integrated, hide = false } = props;
+ return hide ? <>> : (
+ props.onClick(e)}>
+ {integrated && (
+
+ )}
+

+
+
{integration.title}
+
{integration.subtitle && integration.subtitle}
+
+
+ );
+};
+
+export default connect((state: any, props: Props) => {
+ const list = state.getIn([props.integration.slug, 'list']) || [];
+ return {
+ // integrated: props.integration.slug === 'issues' ? !!(list.first() && list.first().token) : list.size > 0,
+ };
+})(IntegrationItem);
diff --git a/frontend/app/components/Client/Integrations/Integrations.js b/frontend/app/components/Client/Integrations/Integrations.js_
similarity index 100%
rename from frontend/app/components/Client/Integrations/Integrations.js
rename to frontend/app/components/Client/Integrations/Integrations.js_
diff --git a/frontend/app/components/Client/Integrations/Integrations.tsx b/frontend/app/components/Client/Integrations/Integrations.tsx
new file mode 100644
index 000000000..d43079d6d
--- /dev/null
+++ b/frontend/app/components/Client/Integrations/Integrations.tsx
@@ -0,0 +1,166 @@
+import { useModal } from 'App/components/Modal';
+import React, { useEffect } from 'react';
+import BugsnagForm from './BugsnagForm';
+import CloudwatchForm from './CloudwatchForm';
+import DatadogForm from './DatadogForm';
+import ElasticsearchForm from './ElasticsearchForm';
+import GithubForm from './GithubForm';
+import IntegrationItem from './IntegrationItem';
+import JiraForm from './JiraForm';
+import NewrelicForm from './NewrelicForm';
+import RollbarForm from './RollbarForm';
+import SentryForm from './SentryForm';
+import SlackForm from './SlackForm';
+import StackdriverForm from './StackdriverForm';
+import SumoLogicForm from './SumoLogicForm';
+import { fetch, init } from 'Duck/integrations/actions';
+import { fetchIntegrationList, setSiteId } from 'Duck/integrations/integrations';
+import { connect } from 'react-redux';
+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 FetchDoc from './FetchDoc';
+import ProfilerDoc from './ProfilerDoc';
+import AxiosDoc from './AxiosDoc';
+import AssistDoc from './AssistDoc';
+
+interface Props {
+ fetch: (name: string, siteId: string) => void;
+ init: () => void;
+ fetchIntegrationList: (siteId: any) => void;
+ integratedList: any;
+ initialSiteId: string;
+ setSiteId: (siteId: string) => void;
+ siteId: string;
+}
+function Integrations(props: Props) {
+ const { initialSiteId } = props;
+ const { showModal } = useModal();
+ const [loading, setLoading] = React.useState(true);
+ const [integratedList, setIntegratedList] = React.useState([]);
+ // const [siteId, setSiteId] = React.useState(props.siteId || initialSiteId);
+
+ useEffect(() => {
+ const list = props.integratedList.filter((item: any) => item.integrated).map((item: any) => item.name);
+ setIntegratedList(list);
+ }, [props.integratedList]);
+
+ useEffect(() => {
+ if (!props.siteId) {
+ props.setSiteId(initialSiteId);
+ props.fetchIntegrationList(initialSiteId);
+ } else {
+ props.fetchIntegrationList(props.siteId);
+ }
+ }, []);
+
+ useEffect(() => {
+ if (loading) {
+ return;
+ }
+ }, [loading]);
+
+ const onClick = (integration: any) => {
+ if (integration.slug) {
+ props.fetch(integration.slug, props.siteId);
+ }
+ showModal(integration.component, { right: true });
+ };
+
+ const onChangeSelect = ({ value }: any) => {
+ props.setSiteId(value.value);
+ props.fetchIntegrationList(value.value);
+ };
+
+ return (
+
+ {integrations.map((cat: any) => (
+
+
+
{cat.title}
+ {cat.isProject && (
+
+
+
+ )}
+
+
{cat.description}
+
+
+ {cat.integrations.map((integration: any) => (
+ onClick(integration)}
+ hide={(integration.slug === 'github' && integratedList.includes('jira') || integration.slug === 'jira' && integratedList.includes('github'))}
+ />
+ ))}
+
+
+ ))}
+
+ );
+}
+
+export default connect(
+ (state: any) => ({
+ initialSiteId: state.getIn(['site', 'siteId']),
+ integratedList: state.getIn(['integrations', 'list']),
+ siteId: state.getIn(['integrations', 'siteId']),
+ }),
+ { fetch, init, fetchIntegrationList, setSiteId }
+)(Integrations);
+
+const integrations = [
+ {
+ title: 'Issue Reporting and Collaborations',
+ description: 'Seamlessly report issues or share issues with your team right from OpenReplay.',
+ integrations: [
+ { title: 'Jira', slug: 'jira', category: 'Errors', icon: 'integrations/jira', component: },
+ { title: 'Github', slug: 'github', category: 'Errors', icon: 'integrations/github', component: },
+ { title: 'Slack', category: 'Errors', icon: 'integrations/slack', component: },
+ ],
+ },
+ {
+ title: 'Backend Logging',
+ isProject: true,
+ description: 'Sync your backend errors with sessions replays and see what happened front-to-back.',
+ integrations: [
+ { title: 'Sentry', slug: 'sentry', icon: 'integrations/sentry', component: },
+ { title: 'Datadog', slug: 'datadog', icon: 'integrations/datadog', component: },
+ { title: 'Rollbar', slug: 'rollbar', icon: 'integrations/rollbar', component: },
+ { title: 'Elasticsearch', slug: 'elasticsearch', icon: 'integrations/elasticsearch', component: },
+ { title: 'Datadog', slug: 'datadog', icon: 'integrations/datadog', component: },
+ { title: 'Sumo Logic', slug: 'sumologic', icon: 'integrations/sumologic', component: },
+ {
+ title: 'Google Cloud',
+ slug: 'stackdriver',
+ subtitle: '(Stackdriver)',
+ icon: 'integrations/google-cloud',
+ component: ,
+ },
+ { title: 'AWS', slug: 'cloudwatch', subtitle: '(CloudWatch)', icon: 'integrations/aws', component: },
+ { title: 'Newrelic', slug: 'newrelic', icon: 'integrations/newrelic', component: },
+ ],
+ },
+ {
+ title: 'Plugins',
+ description:
+ "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', slug: '', icon: 'integrations/redux', component: },
+ { title: 'VueX', slug: '', icon: 'integrations/vuejs', component: },
+ { title: 'GraphQL', slug: '', icon: 'integrations/graphql', component: },
+ { title: 'NgRx', slug: '', icon: 'integrations/ngrx', component: },
+ { title: 'MobX', slug: '', icon: 'integrations/mobx', component: },
+ { title: 'Fetch', slug: '', icon: 'integrations/openreplay', component: },
+ { title: 'Profiler', slug: '', icon: 'integrations/openreplay', component: },
+ { title: 'Axios', slug: '', icon: 'integrations/openreplay', component: },
+ { title: 'Assist', slug: '', icon: 'integrations/openreplay', component: },
+ ],
+ },
+];
diff --git a/frontend/app/components/Client/Integrations/JiraForm/JiraForm.js b/frontend/app/components/Client/Integrations/JiraForm/JiraForm.js
index dc4585872..b17bbc460 100644
--- a/frontend/app/components/Client/Integrations/JiraForm/JiraForm.js
+++ b/frontend/app/components/Client/Integrations/JiraForm/JiraForm.js
@@ -1,37 +1,41 @@
import React from 'react';
-import IntegrationForm from '../IntegrationForm';
+import IntegrationForm from '../IntegrationForm';
import DocLink from 'Shared/DocLink/DocLink';
const JiraForm = (props) => (
- <>
-
-
How to integrate Jira Cloud with OpenReplay.
-
-
-
+
+
Jira
+
+
How to integrate Jira Cloud with OpenReplay.
+
+
+
+
+
-
- >
);
-JiraForm.displayName = "JiraForm";
+JiraForm.displayName = 'JiraForm';
-export default JiraForm;
\ No newline at end of file
+export default JiraForm;
diff --git a/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js b/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js
index 320e1a742..bbe36d45b 100644
--- a/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js
+++ b/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js
@@ -1,29 +1,35 @@
import React from 'react';
-import Highlight from 'react-highlight'
-import ToggleContent from 'Shared/ToggleContent'
+import Highlight from 'react-highlight';
+import ToggleContent from 'Shared/ToggleContent';
import DocLink from 'Shared/DocLink/DocLink';
const MobxDoc = (props) => {
- const { projectKey } = props;
- return (
-
-
This plugin allows you to capture MobX events and inspect them later on while replaying session recordings. This is very useful for understanding and fixing issues.
-
-
Installation
-
- {`npm i @openreplay/tracker-mobx --save`}
-
-
-
Usage
-
Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put the generated middleware into your Redux chain.
-
+ const { projectKey } = props;
+ return (
+
+
MobX
+
+
+ This plugin allows you to capture MobX events and inspect them later on while replaying session recordings. This is very useful
+ for understanding and fixing issues.
+
-
Usage
-
- {`import OpenReplay from '@openreplay/tracker';
+ Installation
+ {`npm i @openreplay/tracker-mobx --save`}
+
+ Usage
+
+ Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put the generated middleware into your Redux
+ chain.
+
+
+
+ Usage
+
+ {`import OpenReplay from '@openreplay/tracker';
import trackerMobX from '@openreplay/tracker-mobx';
//...
const tracker = new OpenReplay({
@@ -31,11 +37,11 @@ const tracker = new OpenReplay({
});
tracker.use(trackerMobX()); // check list of available options below
tracker.start();`}
-
- }
- second={
-
- {`import OpenReplay from '@openreplay/tracker/cjs';
+
+ }
+ second={
+
+ {`import OpenReplay from '@openreplay/tracker/cjs';
import trackerMobX from '@openreplay/tracker-mobx/cjs';
//...
const tracker = new OpenReplay({
@@ -48,15 +54,16 @@ function SomeFunctionalComponent() {
tracker.start();
}, [])
}`}
-
- }
- />
+
+ }
+ />
-
-
- )
+
+
+
+ );
};
-MobxDoc.displayName = "MobxDoc";
+MobxDoc.displayName = 'MobxDoc';
export default MobxDoc;
diff --git a/frontend/app/components/Client/Integrations/NewrelicForm/NewrelicForm.js b/frontend/app/components/Client/Integrations/NewrelicForm/NewrelicForm.js
index d7ce557e8..670656583 100644
--- a/frontend/app/components/Client/Integrations/NewrelicForm/NewrelicForm.js
+++ b/frontend/app/components/Client/Integrations/NewrelicForm/NewrelicForm.js
@@ -1,32 +1,36 @@
import React from 'react';
-import IntegrationForm from '../IntegrationForm';
+import IntegrationForm from '../IntegrationForm';
import DocLink from 'Shared/DocLink/DocLink';
const NewrelicForm = (props) => (
- <>
-
-
How to integrate NewRelic with OpenReplay and see backend errors alongside session recordings.
-
+
+
New Relic
+
+
How to integrate NewRelic with OpenReplay and see backend errors alongside session recordings.
+
+
+
-
- >
);
-NewrelicForm.displayName = "NewrelicForm";
+NewrelicForm.displayName = 'NewrelicForm';
-export default NewrelicForm;
\ No newline at end of file
+export default NewrelicForm;
diff --git a/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js b/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js
index 385b0d4e4..956e4f57e 100644
--- a/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js
+++ b/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js
@@ -1,29 +1,32 @@
import React from 'react';
-import Highlight from 'react-highlight'
-import ToggleContent from 'Shared/ToggleContent'
+import Highlight from 'react-highlight';
+import ToggleContent from 'Shared/ToggleContent';
import DocLink from 'Shared/DocLink/DocLink';
const NgRxDoc = (props) => {
- const { projectKey } = props;
- return (
-
-
This plugin allows you to capture NgRx actions/state and inspect them later on while replaying session recordings. This is very useful for understanding and fixing issues.
-
-
Installation
-
- {`npm i @openreplay/tracker-ngrx --save`}
-
-
-
Usage
-
Add the generated meta-reducer into your imports. See NgRx documentation for more details.
-
+ const { projectKey } = props;
+ return (
+
+
NgRx
+
+
+ This plugin allows you to capture NgRx actions/state and inspect them later on while replaying session recordings. This is very
+ useful for understanding and fixing issues.
+
-
Usage
-
- {`import { StoreModule } from '@ngrx/store';
+ Installation
+ {`npm i @openreplay/tracker-ngrx --save`}
+
+ Usage
+ Add the generated meta-reducer into your imports. See NgRx documentation for more details.
+
+
+ Usage
+
+ {`import { StoreModule } from '@ngrx/store';
import { reducers } from './reducers';
import OpenReplay from '@openreplay/tracker';
import trackerNgRx from '@openreplay/tracker-ngrx';
@@ -39,11 +42,11 @@ const metaReducers = [tracker.use(trackerNgRx())]; // check list of ava
imports: [StoreModule.forRoot(reducers, { metaReducers })]
})
export class AppModule {}`}
-
- }
- second={
-
- {`import { StoreModule } from '@ngrx/store';
+
+ }
+ second={
+
+ {`import { StoreModule } from '@ngrx/store';
import { reducers } from './reducers';
import OpenReplay from '@openreplay/tracker/cjs';
import trackerNgRx from '@openreplay/tracker-ngrx/cjs';
@@ -64,15 +67,16 @@ const metaReducers = [tracker.use(trackerNgRx())]; // check list of ava
})
export class AppModule {}
}`}
-
- }
- />
+
+ }
+ />
-
-
- )
+
+
+
+ );
};
-NgRxDoc.displayName = "NgRxDoc";
+NgRxDoc.displayName = 'NgRxDoc';
export default NgRxDoc;
diff --git a/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js b/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js
index 9cada092b..f5ffab724 100644
--- a/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js
+++ b/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js
@@ -1,29 +1,32 @@
import React from 'react';
-import Highlight from 'react-highlight'
-import ToggleContent from 'Shared/ToggleContent'
+import Highlight from 'react-highlight';
+import ToggleContent from 'Shared/ToggleContent';
import DocLink from 'Shared/DocLink/DocLink';
const ProfilerDoc = (props) => {
- const { projectKey } = props;
- return (
-
-
The profiler plugin allows you to measure your JS functions' performance and capture both arguments and result for each function call.
-
-
Installation
-
- {`npm i @openreplay/tracker-profiler --save`}
-
-
-
Usage
-
Initialize the tracker and load the plugin into it. Then decorate any function inside your code with the generated function.
-
+ const { projectKey } = props;
+ return (
+
+
Profiler
+
+
+ The profiler plugin allows you to measure your JS functions' performance and capture both arguments and result for each function
+ call.
+
-
Usage
-
- {`import OpenReplay from '@openreplay/tracker';
+ Installation
+ {`npm i @openreplay/tracker-profiler --save`}
+
+ Usage
+ Initialize the tracker and load the plugin into it. Then decorate any function inside your code with the generated function.
+
+
+ Usage
+
+ {`import OpenReplay from '@openreplay/tracker';
import trackerProfiler from '@openreplay/tracker-profiler';
//...
const tracker = new OpenReplay({
@@ -36,11 +39,11 @@ export const profiler = tracker.use(trackerProfiler());
const fn = profiler('call_name')(() => {
//...
}, thisArg); // thisArg is optional`}
-
- }
- second={
-
- {`import OpenReplay from '@openreplay/tracker/cjs';
+
+ }
+ second={
+
+ {`import OpenReplay from '@openreplay/tracker/cjs';
import trackerProfiler from '@openreplay/tracker-profiler/cjs';
//...
const tracker = new OpenReplay({
@@ -58,15 +61,16 @@ const fn = profiler('call_name')(() => {
//...
}, thisArg); // thisArg is optional
}`}
-
- }
- />
+
+ }
+ />
-
-
- )
+
+
+
+ );
};
-ProfilerDoc.displayName = "ProfilerDoc";
+ProfilerDoc.displayName = 'ProfilerDoc';
export default ProfilerDoc;
diff --git a/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js b/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js
index 8e3b12432..e16eecbba 100644
--- a/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js
+++ b/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js
@@ -1,28 +1,31 @@
import React from 'react';
-import Highlight from 'react-highlight'
+import Highlight from 'react-highlight';
import ToggleContent from '../../../shared/ToggleContent';
import DocLink from 'Shared/DocLink/DocLink';
const ReduxDoc = (props) => {
- const { projectKey } = props;
- return (
-
-
This plugin allows you to capture Redux actions/state and inspect them later on while replaying session recordings. This is very useful for understanding and fixing issues.
-
-
Installation
-
- {`npm i @openreplay/tracker-redux --save`}
-
-
+ const { projectKey } = props;
+ return (
+
+
Redux
-
Usage
-
Initialize the tracker then put the generated middleware into your Redux chain.
-
-
- {`import { applyMiddleware, createStore } from 'redux';
+
+
+ This plugin allows you to capture Redux actions/state and inspect them later on while replaying session recordings. This is very
+ useful for understanding and fixing issues.
+
+
+
Installation
+
{`npm i @openreplay/tracker-redux --save`}
+
+
Usage
+
Initialize the tracker then put the generated middleware into your Redux chain.
+
+
+ {`import { applyMiddleware, createStore } from 'redux';
import OpenReplay from '@openreplay/tracker';
import trackerRedux from '@openreplay/tracker-redux';
//...
@@ -35,11 +38,11 @@ const store = createStore(
reducer,
applyMiddleware(tracker.use(trackerRedux())) // check list of available options below
);`}
-
- }
- second={
-
- {`import { applyMiddleware, createStore } from 'redux';
+
+ }
+ second={
+
+ {`import { applyMiddleware, createStore } from 'redux';
import OpenReplay from '@openreplay/tracker/cjs';
import trackerRedux from '@openreplay/tracker-redux/cjs';
//...
@@ -57,15 +60,16 @@ const store = createStore(
applyMiddleware(tracker.use(trackerRedux())) // check list of available options below
);
}`}
-
- }
- />
+
+ }
+ />
-
-
- )
+
+
+
+ );
};
-ReduxDoc.displayName = "ReduxDoc";
+ReduxDoc.displayName = 'ReduxDoc';
export default ReduxDoc;
diff --git a/frontend/app/components/Client/Integrations/RollbarForm.js b/frontend/app/components/Client/Integrations/RollbarForm.js
index 3b8830423..441819323 100644
--- a/frontend/app/components/Client/Integrations/RollbarForm.js
+++ b/frontend/app/components/Client/Integrations/RollbarForm.js
@@ -1,25 +1,27 @@
import React from 'react';
-import IntegrationForm from './IntegrationForm';
+import IntegrationForm from './IntegrationForm';
import DocLink from 'Shared/DocLink/DocLink';
const RollbarForm = (props) => (
- <>
-
-
How to integrate Rollbar with OpenReplay and see backend errors alongside session replays.
-
+
+
Rollbar
+
+
How to integrate Rollbar with OpenReplay and see backend errors alongside session replays.
+
+
+
-
- >
);
-RollbarForm.displayName = "RollbarForm";
+RollbarForm.displayName = 'RollbarForm';
-export default RollbarForm;
\ No newline at end of file
+export default RollbarForm;
diff --git a/frontend/app/components/Client/Integrations/SentryForm.js b/frontend/app/components/Client/Integrations/SentryForm.js
index fd7bf1f11..bd119ba31 100644
--- a/frontend/app/components/Client/Integrations/SentryForm.js
+++ b/frontend/app/components/Client/Integrations/SentryForm.js
@@ -1,31 +1,35 @@
import React from 'react';
-import IntegrationForm from './IntegrationForm';
+import IntegrationForm from './IntegrationForm';
import DocLink from 'Shared/DocLink/DocLink';
const SentryForm = (props) => (
- <>
-
-
How to integrate Sentry with OpenReplay and see backend errors alongside session recordings.
-
+
+
Sentry
+
+
How to integrate Sentry with OpenReplay and see backend errors alongside session recordings.
+
+
+
-
- >
);
-SentryForm.displayName = "SentryForm";
+SentryForm.displayName = 'SentryForm';
-export default SentryForm;
\ No newline at end of file
+export default SentryForm;
diff --git a/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js b/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js
index 8e1bb121e..f018da3e5 100644
--- a/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js
+++ b/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js
@@ -1,101 +1,91 @@
-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 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 { remove } from 'Duck/integrations/slack';
class SlackAddForm extends React.PureComponent {
- componentWillUnmount() {
- this.props.init({});
- }
-
- save = () => {
- const instance = this.props.instance;
- if(instance.exists()) {
- this.props.update(this.props.instance)
- } else {
- this.props.save(this.props.instance)
- }
- }
-
- remove = async (id) => {
- if (await confirm({
- header: 'Confirm',
- confirmButton: 'Yes, delete',
- confirmation: `Are you sure you want to permanently delete this channel?`
- })) {
- this.props.remove(id);
+ componentWillUnmount() {
+ this.props.init({});
}
- }
- write = ({ target: { name, value } }) => this.props.edit({ [ name ]: value });
-
- render() {
- const { instance, saving, errors, onClose } = this.props;
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- { errors &&
-
- { errors.map(error => { error }) }
-
+ save = () => {
+ const instance = this.props.instance;
+ if (instance.exists()) {
+ this.props.update(this.props.instance);
+ } else {
+ this.props.save(this.props.instance);
}
-
- )
- }
+ };
+
+ remove = async (id) => {
+ if (
+ await confirm({
+ header: 'Confirm',
+ confirmButton: 'Yes, delete',
+ confirmation: `Are you sure you want to permanently delete this channel?`,
+ })
+ ) {
+ this.props.remove(id);
+ }
+ };
+
+ write = ({ target: { name, value } }) => this.props.edit({ [name]: value });
+
+ render() {
+ const { instance, saving, errors, onClose } = this.props;
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {errors && (
+
+ {errors.map((error) => (
+
+ {error}
+
+ ))}
+
+ )}
+
+ );
+ }
}
-export default connect(state => ({
- instance: state.getIn(['slack', 'instance']),
- saving: state.getIn(['slack', 'saveRequest', 'loading']),
- errors: state.getIn([ 'slack', 'saveRequest', 'errors' ]),
-}), { edit, save, init, remove, update })(SlackAddForm)
\ No newline at end of file
+export default connect(
+ (state) => ({
+ instance: state.getIn(['slack', 'instance']),
+ saving: state.getIn(['slack', 'saveRequest', 'loading']),
+ errors: state.getIn(['slack', 'saveRequest', 'errors']),
+ }),
+ { edit, save, init, remove, update }
+)(SlackAddForm);
diff --git a/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js b/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js
index f78527204..07f1aa123 100644
--- a/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js
+++ b/frontend/app/components/Client/Integrations/SlackChannelList/SlackChannelList.js
@@ -1,49 +1,47 @@
-import React from 'react'
-import { connect } from 'react-redux'
+import React from 'react';
+import { connect } from 'react-redux';
import { NoContent } from 'UI';
-import { remove, edit } from 'Duck/integrations/slack'
+import { remove, edit, init } from 'Duck/integrations/slack';
import DocLink from 'Shared/DocLink/DocLink';
function SlackChannelList(props) {
- const { list } = props;
+ const { list } = props;
- const onEdit = (instance) => {
- props.edit(instance)
- props.onEdit()
- }
+ const onEdit = (instance) => {
+ props.edit(instance);
+ props.onEdit();
+ };
- return (
-
-
- Integrate Slack 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 => (
-
onEdit(c)}
- >
-
-
{c.name}
-
- {c.endpoint}
-
-
-
- ))}
-
-
- )
+ return (
+
+
+
+ Integrate Slack 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) => (
+
onEdit(c)}>
+
+
{c.name}
+
{c.endpoint}
+
+
+ ))}
+
+
+ );
}
-export default connect(state => ({
- list: state.getIn(['slack', 'list'])
-}), { remove, edit })(SlackChannelList)
+export default connect(
+ (state) => ({
+ list: state.getIn(['slack', 'list']),
+ }),
+ { remove, edit, init }
+)(SlackChannelList);
diff --git a/frontend/app/components/Client/Integrations/SlackForm.js b/frontend/app/components/Client/Integrations/SlackForm.js
deleted file mode 100644
index 986af20ab..000000000
--- a/frontend/app/components/Client/Integrations/SlackForm.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react';
-import SlackChannelList from './SlackChannelList/SlackChannelList';
-
-const SlackForm = (props) => {
- const { onEdit } = props;
- return (
- <>
-
- >
- )
-}
-
-SlackForm.displayName = "SlackForm";
-
-export default SlackForm;
\ No newline at end of file
diff --git a/frontend/app/components/Client/Integrations/SlackForm.tsx b/frontend/app/components/Client/Integrations/SlackForm.tsx
new file mode 100644
index 000000000..207f9b765
--- /dev/null
+++ b/frontend/app/components/Client/Integrations/SlackForm.tsx
@@ -0,0 +1,48 @@
+import React, { useEffect } from 'react';
+import SlackChannelList from './SlackChannelList/SlackChannelList';
+import { fetchList } from 'Duck/integrations/slack';
+import { connect } from 'react-redux';
+import SlackAddForm from './SlackAddForm';
+import { useModal } from 'App/components/Modal';
+
+interface Props {
+ onEdit: (integration: any) => void;
+ istance: any;
+ fetchList: any;
+}
+const SlackForm = (props: Props) => {
+ const { istance } = props;
+ const { hideModal } = useModal();
+ const [active, setActive] = React.useState(false);
+
+ const onEdit = () => {
+ setActive(true);
+ };
+
+ useEffect(() => {
+ props.fetchList();
+ }, []);
+
+ return (
+
+
+
Slack
+
+
+ {active && (
+
+ setActive(false)} />
+
+ )}
+
+ );
+};
+
+SlackForm.displayName = 'SlackForm';
+
+export default connect(
+ (state: any) => ({
+ istance: state.getIn(['slack', 'instance']),
+ }),
+ { fetchList }
+)(SlackForm);
diff --git a/frontend/app/components/Client/Integrations/StackdriverForm.js b/frontend/app/components/Client/Integrations/StackdriverForm.js
index b8e29fa3c..ce137bd99 100644
--- a/frontend/app/components/Client/Integrations/StackdriverForm.js
+++ b/frontend/app/components/Client/Integrations/StackdriverForm.js
@@ -1,29 +1,32 @@
import React from 'react';
-import IntegrationForm from './IntegrationForm';
+import IntegrationForm from './IntegrationForm';
import DocLink from 'Shared/DocLink/DocLink';
const StackdriverForm = (props) => (
- <>
-
-
How to integrate Stackdriver with OpenReplay and see backend errors alongside session recordings.
-
+
+
Stackdriver
+
+
How to integrate Stackdriver with OpenReplay and see backend errors alongside session recordings.
+
+
+
-
- >
);
-StackdriverForm.displayName = "StackdriverForm";
+StackdriverForm.displayName = 'StackdriverForm';
export default StackdriverForm;
diff --git a/frontend/app/components/Client/Integrations/SumoLogicForm/SumoLogicForm.js b/frontend/app/components/Client/Integrations/SumoLogicForm/SumoLogicForm.js
index 0a807edb6..6aea9fe6e 100644
--- a/frontend/app/components/Client/Integrations/SumoLogicForm/SumoLogicForm.js
+++ b/frontend/app/components/Client/Integrations/SumoLogicForm/SumoLogicForm.js
@@ -4,30 +4,34 @@ import RegionDropdown from './RegionDropdown';
import DocLink from 'Shared/DocLink/DocLink';
const SumoLogicForm = (props) => (
- <>
-
-
How to integrate SumoLogic with OpenReplay and see backend errors alongside session recordings.
-
+
+
Sumologic
+
+
How to integrate SumoLogic with OpenReplay and see backend errors alongside session recordings.
+
+
+
-
- >
);
-SumoLogicForm.displayName = "SumoLogicForm";
+SumoLogicForm.displayName = 'SumoLogicForm';
export default SumoLogicForm;
diff --git a/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js b/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js
index e00d1c0ad..cece7c01e 100644
--- a/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js
+++ b/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js
@@ -1,29 +1,34 @@
import React from 'react';
-import Highlight from 'react-highlight'
+import Highlight from 'react-highlight';
import ToggleContent from '../../../shared/ToggleContent';
import DocLink from 'Shared/DocLink/DocLink';
const VueDoc = (props) => {
- const { projectKey } = props;
- return (
-
-
This plugin allows you to capture VueX mutations/state and inspect them later on while replaying session recordings. This is very useful for understanding and fixing issues.
-
-
Installation
-
- {`npm i @openreplay/tracker-vuex --save`}
-
-
-
Usage
-
Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put the generated plugin into your plugins field of your store.
-
+ const { projectKey } = props;
+ return (
+
+
VueX
+
+
+ This plugin allows you to capture VueX mutations/state and inspect them later on while replaying session recordings. This is very
+ useful for understanding and fixing issues.
+
-
-
- {`import Vuex from 'vuex'
+ Installation
+ {`npm i @openreplay/tracker-vuex --save`}
+
+ Usage
+
+ Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put the generated plugin into your plugins
+ field of your store.
+
+
+
+
+ {`import Vuex from 'vuex'
import OpenReplay from '@openreplay/tracker';
import trackerVuex from '@openreplay/tracker-vuex';
//...
@@ -36,11 +41,11 @@ const store = new Vuex.Store({
//...
plugins: [tracker.use(trackerVuex())] // check list of available options below
});`}
-
- }
- second={
-
- {`import Vuex from 'vuex'
+
+ }
+ second={
+
+ {`import Vuex from 'vuex'
import OpenReplay from '@openreplay/tracker/cjs';
import trackerVuex from '@openreplay/tracker-vuex/cjs';
//...
@@ -58,15 +63,16 @@ const store = new Vuex.Store({
plugins: [tracker.use(trackerVuex())] // check list of available options below
});
}`}
-
- }
- />
+
+ }
+ />
-
-
- )
+
+
+
+ );
};
-VueDoc.displayName = "VueDoc";
+VueDoc.displayName = 'VueDoc';
export default VueDoc;
diff --git a/frontend/app/components/Client/Integrations/_IntegrationItem .js_old b/frontend/app/components/Client/Integrations/_IntegrationItem .js_old
deleted file mode 100644
index 962135633..000000000
--- a/frontend/app/components/Client/Integrations/_IntegrationItem .js_old
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-import cn from 'classnames';
-import { Icon } from 'UI';
-import styles from './integrationItem.module.css';
-
-const onDocLinkClick = (e, link) => {
- e.stopPropagation();
- window.open(link, '_blank');
-}
-
-const IntegrationItem = ({
- deleteHandler = null, icon, url = null, title = '', description = '', onClick = null, dockLink = '', integrated = false
-}) => {
- return (
-
onClick(e, url) }>
-
-
{ title }
-
{ description }
-
-
- {deleteHandler && (
-
-
- { 'Remove' }
-
- )}
- { dockLink && (
-
onDocLinkClick(e, dockLink) }>
-
- { 'Documentation' }
-
- )}
-
-
- { 'Integrated' }
-
-
-
- )
-};
-
-export default IntegrationItem;
diff --git a/frontend/app/components/Client/Integrations/integrationItem.module.css b/frontend/app/components/Client/Integrations/integrationItem.module.css
index 94ab26726..fca162909 100644
--- a/frontend/app/components/Client/Integrations/integrationItem.module.css
+++ b/frontend/app/components/Client/Integrations/integrationItem.module.css
@@ -9,7 +9,7 @@
display: flex;
flex-direction: column;
align-items: center;
- justify-content: center;
+ justify-content: flex-start;
/* min-height: 250px; */
/* min-width: 260px; */
/* max-width: 300px; */
diff --git a/frontend/app/components/Client/Notifications/Notifications.js b/frontend/app/components/Client/Notifications/Notifications.js
index 15d6b9b4d..d01b12456 100644
--- a/frontend/app/components/Client/Notifications/Notifications.js
+++ b/frontend/app/components/Client/Notifications/Notifications.js
@@ -1,46 +1,50 @@
-import React, { useEffect } from 'react'
-import cn from 'classnames'
-import stl from './notifications.module.css'
-import { Checkbox } from 'UI'
-import { connect } from 'react-redux'
-import { withRequest } from 'HOCs'
-import { fetch as fetchConfig, edit as editConfig, save as saveConfig } from 'Duck/config'
+import React, { useEffect } from 'react';
+import cn from 'classnames';
+import stl from './notifications.module.css';
+import { Checkbox, Toggler } from 'UI';
+import { connect } from 'react-redux';
+import { withRequest } from 'HOCs';
+import { fetch as fetchConfig, edit as editConfig, save as saveConfig } from 'Duck/config';
import withPageTitle from 'HOCs/withPageTitle';
function Notifications(props) {
- const { config } = props;
+ const { config } = props;
- useEffect(() => {
- props.fetchConfig();
- }, [])
+ useEffect(() => {
+ props.fetchConfig();
+ }, []);
- const onChange = () => {
- const _config = { 'weeklyReport' : !config.weeklyReport };
- props.editConfig(_config);
- props.saveConfig(_config)
- }
+ const onChange = () => {
+ const _config = { weeklyReport: !config.weeklyReport };
+ props.editConfig(_config);
+ props.saveConfig(_config);
+ };
- return (
-
-
- {
{ 'Notifications' }
}
-
-
-
-

-
-
- )
+ return (
+
+
{
{'Notifications'}
}
+
+
Weekly project summary
+
Receive wekly report for each project on email.
+
+ {/*
*/}
+ {/*

*/}
+
+
+ );
}
-export default connect(state => ({
- config: state.getIn(['config', 'options'])
-}), { fetchConfig, editConfig, saveConfig })(withPageTitle('Notifications - OpenReplay Preferences')(Notifications));
+export default connect(
+ (state) => ({
+ config: state.getIn(['config', 'options']),
+ }),
+ { fetchConfig, editConfig, saveConfig }
+)(withPageTitle('Notifications - OpenReplay Preferences')(Notifications));
diff --git a/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js b/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js
index 820fe14e4..8314e521a 100644
--- a/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js
+++ b/frontend/app/components/Client/PreferencesMenu/PreferencesMenu.js
@@ -13,14 +13,14 @@ function PreferencesMenu({ account, activeTab, history, isEnterprise }) {
};
return (
-
+
-
+
-
+
-
+
{
-
+
}
-
+
{isEnterprise && isAdmin && (
-
+
+
- setTab(CLIENT_TABS.MANAGE_USERS)}
- />
-
+
+ setTab(CLIENT_TABS.MANAGE_USERS)}
+ />
+
)}
-
+
{} }: any) {
const { userStore } = useStore();
+ const { showModal, hideModal } = useModal();
const limtis = useObserver(() => userStore.limits);
const canAddProject = useObserver(() => isAdmin && (limtis.projects === -1 || limtis.projects > 0));
+
+ const onClick = () => {
+ init();
+ showModal(, { right: true });
+ };
return (
- {/* */}
);
}
-export default AddProjectButton;
+export default connect(null, { init, remove, fetchGDPR })(AddProjectButton);
diff --git a/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx b/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx
new file mode 100644
index 000000000..c6d04f2f4
--- /dev/null
+++ b/frontend/app/components/Client/Sites/InstallButton/InstallButton.tsx
@@ -0,0 +1,25 @@
+import { useModal } from 'App/components/Modal';
+import React from 'react';
+import TrackingCodeModal from 'Shared/TrackingCodeModal';
+import { Button } from 'UI';
+
+interface Props {
+ site: any;
+}
+function InstallButton(props: Props) {
+ const { site } = props;
+ const { showModal, hideModal } = useModal();
+ const onClick = () => {
+ showModal(
+ ,
+ { right: true }
+ );
+ };
+ return (
+
+ );
+}
+
+export default InstallButton;
diff --git a/frontend/app/components/Client/Sites/InstallButton/index.ts b/frontend/app/components/Client/Sites/InstallButton/index.ts
new file mode 100644
index 000000000..c64b2ff6c
--- /dev/null
+++ b/frontend/app/components/Client/Sites/InstallButton/index.ts
@@ -0,0 +1 @@
+export { default } from './InstallButton'
\ No newline at end of file
diff --git a/frontend/app/components/Client/Sites/NewSiteForm.js b/frontend/app/components/Client/Sites/NewSiteForm.js
index c6633b73b..0a9dc81c7 100644
--- a/frontend/app/components/Client/Sites/NewSiteForm.js
+++ b/frontend/app/components/Client/Sites/NewSiteForm.js
@@ -1,121 +1,122 @@
import React from 'react';
import { connect } from 'react-redux';
import { Form, Input, Button, Icon } from 'UI';
-import { save, edit, update , fetchList, remove } from 'Duck/site';
+import { save, edit, update, fetchList, remove } from 'Duck/site';
import { pushNewSite } from 'Duck/user';
import { setSiteId } from 'Duck/site';
import { withRouter } from 'react-router-dom';
import styles from './siteForm.module.css';
import { confirm } from 'UI';
-@connect(state => ({
- site: state.getIn([ 'site', 'instance' ]),
- sites: state.getIn([ 'site', 'list' ]),
- siteList: state.getIn([ 'site', 'list' ]),
- loading: state.getIn([ 'site', 'save', 'loading' ]) || state.getIn([ 'site', 'remove', 'loading' ]),
-}), {
- save,
- remove,
- edit,
- update,
- pushNewSite,
- fetchList,
- setSiteId
-})
+@connect(
+ (state) => ({
+ site: state.getIn(['site', 'instance']),
+ sites: state.getIn(['site', 'list']),
+ siteList: state.getIn(['site', 'list']),
+ loading: state.getIn(['site', 'save', 'loading']) || state.getIn(['site', 'remove', 'loading']),
+ }),
+ {
+ save,
+ remove,
+ edit,
+ update,
+ pushNewSite,
+ fetchList,
+ setSiteId,
+ }
+)
@withRouter
export default class NewSiteForm extends React.PureComponent {
- state = {
- existsError: false,
- }
+ state = {
+ existsError: false,
+ };
- componentDidMount() {
- const { location: { pathname }, match: { params: { siteId } } } = this.props;
- if (pathname.includes('onboarding')) {
- this.props.setSiteId(siteId);
- }
- }
+ componentDidMount() {
+ const {
+ location: { pathname },
+ match: {
+ params: { siteId },
+ },
+ } = this.props;
+ if (pathname.includes('onboarding')) {
+ this.props.setSiteId(siteId);
+ }
+ }
- onSubmit = e => {
- e.preventDefault();
- const { site, siteList, location: { pathname } } = this.props;
- if (!site.exists() && siteList.some(({ name }) => name === site.name)) {
- return this.setState({ existsError: true });
- }
- if (site.exists()) {
- this.props.update(this.props.site, this.props.site.id).then(() => {
- this.props.onClose(null)
- this.props.fetchList();
- })
- } else {
- this.props.save(this.props.site).then(() => {
- this.props.fetchList().then(() => {
- const { sites } = this.props;
- const site = sites.last();
- if (!pathname.includes('/client')) {
- this.props.setSiteId(site.get('id'))
- }
- this.props.onClose(null, site)
- })
-
- // this.props.pushNewSite(site)
- });
- }
- }
+ onSubmit = (e) => {
+ e.preventDefault();
+ const {
+ site,
+ siteList,
+ location: { pathname },
+ } = this.props;
+ if (!site.exists() && siteList.some(({ name }) => name === site.name)) {
+ return this.setState({ existsError: true });
+ }
+ if (site.exists()) {
+ this.props.update(this.props.site, this.props.site.id).then(() => {
+ this.props.onClose(null);
+ this.props.fetchList();
+ });
+ } else {
+ this.props.save(this.props.site).then(() => {
+ this.props.fetchList().then(() => {
+ const { sites } = this.props;
+ const site = sites.last();
+ if (!pathname.includes('/client')) {
+ this.props.setSiteId(site.get('id'));
+ }
+ this.props.onClose(null, site);
+ });
- remove = async (site) => {
- if (await confirm({
- header: 'Projects',
- confirmation: `Are you sure you want to delete this Project? We won't be able to record anymore sessions.`
- })) {
- this.props.remove(site.id).then(() => {
- this.props.onClose(null)
- });
- }
- };
+ // this.props.pushNewSite(site)
+ });
+ }
+ };
- edit = ({ target: { name, value } }) => {
- this.setState({ existsError: false });
- this.props.edit({ [ name ]: value });
- }
+ remove = async (site) => {
+ if (
+ await confirm({
+ header: 'Projects',
+ confirmation: `Are you sure you want to delete this Project? We won't be able to record anymore sessions.`,
+ })
+ ) {
+ this.props.remove(site.id).then(() => {
+ this.props.onClose(null);
+ });
+ }
+ };
- render() {
- const { site, loading } = this.props;
- return (
-
- );
- }
-}
\ No newline at end of file
+ edit = ({ target: { name, value } }) => {
+ this.setState({ existsError: false });
+ this.props.edit({ [name]: value });
+ };
+
+ render() {
+ const { site, loading } = this.props;
+ return (
+
+
{site.exists() ? 'Edit Project' : 'New Project'}
+
+
+ );
+ }
+}
diff --git a/frontend/app/components/Client/Sites/ProjectKey.tsx b/frontend/app/components/Client/Sites/ProjectKey.tsx
new file mode 100644
index 000000000..d53b336f8
--- /dev/null
+++ b/frontend/app/components/Client/Sites/ProjectKey.tsx
@@ -0,0 +1,8 @@
+import { withCopy } from 'HOCs';
+import React from 'react';
+
+function ProjectKey({ value, tooltip }: any) {
+ return {value}
;
+}
+
+export default withCopy(ProjectKey);
diff --git a/frontend/app/components/Client/Sites/Sites.js b/frontend/app/components/Client/Sites/Sites.js
index 1c96c0b3c..4158a57ea 100644
--- a/frontend/app/components/Client/Sites/Sites.js
+++ b/frontend/app/components/Client/Sites/Sites.js
@@ -1,18 +1,18 @@
import React from 'react';
import { connect } from 'react-redux';
-import cn from 'classnames';
import withPageTitle from 'HOCs/withPageTitle';
-import { Loader, SlideModal, Icon, Button, Popup, TextLink } from 'UI';
+import { Loader, Button, Popup, TextLink } from 'UI';
import { init, remove, fetchGDPR } from 'Duck/site';
import { RED, YELLOW, GREEN, STATUS_COLOR_MAP } from 'Types/site';
import stl from './sites.module.css';
import NewSiteForm from './NewSiteForm';
-import GDPRForm from './GDPRForm';
-import TrackingCodeModal from 'Shared/TrackingCodeModal';
-import BlockedIps from './BlockedIps';
import { confirm, PageTitle } from 'UI';
import SiteSearch from './SiteSearch';
import AddProjectButton from './AddProjectButton';
+import InstallButton from './InstallButton';
+import ProjectKey from './ProjectKey';
+import { useModal } from 'App/components/Modal';
+import { getInitials } from 'App/utils';
const STATUS_MESSAGE_MAP = {
[RED]: ' There seems to be an issue (please verify your installation)',
@@ -20,11 +20,7 @@ const STATUS_MESSAGE_MAP = {
[GREEN]: 'All good!',
};
-const BLOCKED_IPS = 'BLOCKED_IPS';
-const NONE = 'NONE';
-
const NEW_SITE_FORM = 'NEW_SITE_FORM';
-const GDPR_FORM = 'GDPR_FORM';
@connect(
(state) => ({
@@ -43,20 +39,9 @@ const GDPR_FORM = 'GDPR_FORM';
@withPageTitle('Projects - OpenReplay Preferences')
class Sites extends React.PureComponent {
state = {
- showTrackingCode: false,
- modalContent: NONE,
- detailContent: NONE,
searchQuery: '',
};
- toggleBlockedIp = () => {
- this.setState({
- detailContent: this.state.detailContent === BLOCKED_IPS ? NONE : BLOCKED_IPS,
- });
- };
-
- closeModal = () => this.setState({ modalContent: NONE, detailContent: NONE });
-
edit = (site) => {
this.props.init(site);
this.setState({ modalContent: NEW_SITE_FORM });
@@ -73,128 +58,59 @@ class Sites extends React.PureComponent {
}
};
- showGDPRForm = (site) => {
- this.props.init(site);
- this.setState({ modalContent: GDPR_FORM });
- };
-
- showNewSiteForm = () => {
- this.props.init();
- this.setState({ modalContent: NEW_SITE_FORM });
- };
-
- showTrackingCode = (site) => {
- this.props.init(site);
- this.setState({ showTrackingCode: true });
- };
-
- getModalTitle() {
- switch (this.state.modalContent) {
- case NEW_SITE_FORM:
- return this.props.site.exists() ? 'Update Project' : 'New Project';
- case GDPR_FORM:
- return 'Project Settings';
- default:
- return '';
- }
- }
-
- renderModalContent() {
- switch (this.state.modalContent) {
- case NEW_SITE_FORM:
- return ;
- case GDPR_FORM:
- return ;
- default:
- return null;
- }
- }
-
- renderModalDetailContent() {
- switch (this.state.detailContent) {
- case BLOCKED_IPS:
- return ;
- default:
- return null;
- }
- }
-
render() {
- const { loading, sites, site, user, account } = this.props;
- const { modalContent, showTrackingCode } = this.state;
+ const { loading, sites, user } = this.props;
const isAdmin = user.admin || user.superAdmin;
const filteredSites = sites.filter((site) => site.name.toLowerCase().includes(this.state.searchQuery.toLowerCase()));
return (
- this.setState({ showTrackingCode: false })}
- site={site}
- />
-
}
- actionButton={
}
- />
+
Projects } actionButton={} />
-
+
this.setState({ searchQuery: value })} />
-
Name
+
Project Name
Key
{filteredSites.map((_site) => (
-
-
-
+
+
+
+
+ {getInitials(_site.name)}
+
{_site.host}
-
+
-
+ this.props.init(_site)} />
@@ -207,3 +123,12 @@ class Sites extends React.PureComponent {
}
export default Sites;
+
+function EditButton({ isAdmin, onClick }) {
+ const { showModal, hideModal } = useModal();
+ const _onClick = () => {
+ onClick();
+ showModal(
);
+ };
+ return
;
+}
diff --git a/frontend/app/components/Client/Users/UsersView.tsx b/frontend/app/components/Client/Users/UsersView.tsx
index a1240eb37..860f62d6d 100644
--- a/frontend/app/components/Client/Users/UsersView.tsx
+++ b/frontend/app/components/Client/Users/UsersView.tsx
@@ -21,15 +21,15 @@ function UsersView(props: Props) {
const userCount = useObserver(() => userStore.list.length);
const roles = useObserver(() => roleStore.list);
const { showModal } = useModal();
-
- const reachedLimit = (limits.remaining + userStore.modifiedCount) <= 0;
+
+ const reachedLimit = limits.remaining + userStore.modifiedCount <= 0;
const isAdmin = account.admin || account.superAdmin;
- const editHandler = (user = null) => {
+ const editHandler = (user: any = null) => {
userStore.initUser(user).then(() => {
- showModal(
, {});
+ showModal(
, { right: true });
});
- }
+ };
useEffect(() => {
if (roles.length === 0 && isEnterprise) {
@@ -41,10 +41,12 @@ function UsersView(props: Props) {
}
- actionButton={(
-
editHandler(null)} />
- )}
+ title={
+
+ Team {userCount}
+
+ }
+ actionButton={ editHandler(null)} />}
/>
@@ -55,8 +57,8 @@ function UsersView(props: Props) {
);
}
-export default connect(state => ({
- account: state.getIn([ 'user', 'account' ]),
- isEnterprise: state.getIn([ 'user', 'account', 'edition' ]) === 'ee',
- limits: state.getIn([ 'user', 'account', 'limits', 'teamMember' ]),
-}))(UsersView);
\ No newline at end of file
+export default connect((state: any) => ({
+ account: state.getIn(['user', 'account']),
+ isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
+ limits: state.getIn(['user', 'account', 'limits', 'teamMember']),
+}))(UsersView);
diff --git a/frontend/app/components/Client/Users/components/UserList/UserList.tsx b/frontend/app/components/Client/Users/components/UserList/UserList.tsx
index a86e6d7d7..4cf90a8b7 100644
--- a/frontend/app/components/Client/Users/components/UserList/UserList.tsx
+++ b/frontend/app/components/Client/Users/components/UserList/UserList.tsx
@@ -13,7 +13,7 @@ interface Props {
isEnterprise?: boolean;
}
function UserList(props: Props) {
- const { isEnterprise = false, isOnboarding = false } = props;
+ const { isEnterprise = false, isOnboarding = false } = props;
const { userStore } = useStore();
const loading = useObserver(() => userStore.loading);
const users = useObserver(() => userStore.list);
@@ -22,24 +22,24 @@ function UserList(props: Props) {
const filterList = (list) => {
const filterRE = getRE(searchQuery, 'i');
- let _list = list.filter(w => {
+ let _list = list.filter((w) => {
return filterRE.test(w.email) || filterRE.test(w.roleName);
});
- return _list
- }
-
+ return _list;
+ };
+
const list: any = searchQuery !== '' ? filterList(users) : users;
const length = list.length;
-
+
useEffect(() => {
userStore.fetchUsers();
}, []);
- const editHandler = (user) => {
+ const editHandler = (user: any) => {
userStore.initUser(user).then(() => {
- showModal(
, { });
+ showModal(
, { right: true });
});
- }
+ };
return useObserver(() => (
@@ -56,7 +56,7 @@ function UserList(props: Props) {
Name
Role
- {!isOnboarding &&
Created On
}
+ {!isOnboarding &&
Created On
}
@@ -88,4 +88,4 @@ function UserList(props: Props) {
));
}
-export default UserList;
\ No newline at end of file
+export default UserList;
diff --git a/frontend/app/components/Client/Webhooks/ListItem.js b/frontend/app/components/Client/Webhooks/ListItem.js
index c493cc176..35608f0bf 100644
--- a/frontend/app/components/Client/Webhooks/ListItem.js
+++ b/frontend/app/components/Client/Webhooks/ListItem.js
@@ -3,22 +3,19 @@ import { Icon } from 'UI';
import styles from './listItem.module.css';
const ListItem = ({ webhook, onEdit, onDelete }) => {
- return (
-
-
-
{ webhook.name }
-
{ webhook.endpoint }
-
-
-
{ e.stopPropagation(); onDelete(webhook) } }>
-
+ return (
+
+
+
{webhook.name}
+
{webhook.endpoint}
+
+
-
-
-
-
-
- );
+ );
};
-export default ListItem;
\ No newline at end of file
+export default ListItem;
diff --git a/frontend/app/components/Client/Webhooks/WebhookForm.js b/frontend/app/components/Client/Webhooks/WebhookForm.js
index 6ea5737ea..b64a63af8 100644
--- a/frontend/app/components/Client/Webhooks/WebhookForm.js
+++ b/frontend/app/components/Client/Webhooks/WebhookForm.js
@@ -4,80 +4,91 @@ import { edit, save } from 'Duck/webhook';
import { Form, Button, Input } from 'UI';
import styles from './webhookForm.module.css';
-@connect(state => ({
- webhook: state.getIn(['webhooks', 'instance']),
- loading: state.getIn(['webhooks', 'saveRequest', 'loading']),
-}), {
- edit,
- save,
-})
+@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 });
+ setFocus = () => this.focusElement.focus();
+ onChangeSelect = (event, { name, value }) => this.props.edit({ [name]: value });
+ write = ({ target: { value, name } }) => this.props.edit({ [name]: value });
- save = () => {
- this.props.save(this.props.webhook).then(() => {
- this.props.onClose();
- });
- };
+ save = () => {
+ this.props.save(this.props.webhook).then(() => {
+ this.props.onClose();
+ });
+ };
- render() {
- const { webhook, loading } = this.props;
- return (
-
-
- { this.focusElement = ref; } }
- name="name"
- value={ webhook.name }
- onChange={ this.write }
- placeholder="Name"
- />
-
+ render() {
+ const { webhook, loading } = this.props;
+ return (
+
+ );
+ }
}
export default WebhookForm;
diff --git a/frontend/app/components/Client/Webhooks/Webhooks.js b/frontend/app/components/Client/Webhooks/Webhooks.js
index eb5306aa6..076ed0587 100644
--- a/frontend/app/components/Client/Webhooks/Webhooks.js
+++ b/frontend/app/components/Client/Webhooks/Webhooks.js
@@ -1,8 +1,8 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import cn from 'classnames';
import withPageTitle from 'HOCs/withPageTitle';
-import { IconButton, SlideModal, Loader, NoContent } from 'UI';
+import { Button, Loader, NoContent } from 'UI';
import { init, fetchList, remove } from 'Duck/webhook';
import WebhookForm from './WebhookForm';
import ListItem from './ListItem';
@@ -10,87 +10,74 @@ import styles from './webhooks.module.css';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import { confirm } from 'UI';
import { toast } from 'react-toastify';
+import { useModal } from 'App/components/Modal';
-@connect(state => ({
- webhooks: state.getIn(['webhooks', 'list']),
- loading: state.getIn(['webhooks', 'loading']),
-}), {
- init,
- fetchList,
- remove,
-})
-@withPageTitle('Webhooks - OpenReplay Preferences')
-class Webhooks extends React.PureComponent {
- state = { showModal: false };
+function Webhooks(props) {
+ const { webhooks, loading } = props;
+ const { showModal, hideModal } = useModal();
- componentWillMount() {
- this.props.fetchList();
- }
+ const noSlackWebhooks = webhooks.filter((hook) => hook.type !== 'slack');
+ useEffect(() => {
+ props.fetchList();
+ }, []);
- closeModal = () => this.setState({ showModal: false });
- init = (v) => {
- this.props.init(v);
- this.setState({ showModal: true });
- }
+ const init = (v) => {
+ props.init(v);
+ showModal(
);
+ };
- removeWebhook = async (id) => {
- if (await confirm({
- header: 'Confirm',
- confirmButton: 'Yes, delete',
- confirmation: `Are you sure you want to remove this webhook?`
- })) {
- this.props.remove(id).then(() => {
- toast.success('Webhook removed successfully');
- });
- }
- }
+ 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();
+ }
+ };
- render() {
- const { webhooks, loading } = this.props;
- const { showModal } = this.state;
-
- const noSlackWebhooks = webhooks.filter(hook => hook.type !== 'slack');
return (
-
-
}
- onClose={ this.closeModal }
- />
-
-
{ 'Webhooks' }
- this.init() } />
-
-
-
-
-
- No webhooks available.
-
- }
- size="small"
- show={ noSlackWebhooks.size === 0 }
- // animatedIcon="no-results"
- >
-
- { noSlackWebhooks.map(webhook => (
-
this.init(webhook) }
- onDelete={ () => this.removeWebhook(webhook.webhookId) }
- />
- ))}
+
+
+
{'Webhooks'}
+
-
-
-
+
+
+
+
+ No webhooks available.
+
+ }
+ size="small"
+ show={noSlackWebhooks.size === 0}
+ >
+
+ {noSlackWebhooks.map((webhook) => (
+ init(webhook)} />
+ ))}
+
+
+
+
);
- }
}
-export default Webhooks;
\ No newline at end of file
+export default connect(
+ (state) => ({
+ webhooks: state.getIn(['webhooks', 'list']),
+ loading: state.getIn(['webhooks', 'loading']),
+ }),
+ {
+ init,
+ fetchList,
+ remove,
+ }
+)(withPageTitle('Webhooks - OpenReplay Preferences')(Webhooks));
diff --git a/frontend/app/components/Modal/Modal.tsx b/frontend/app/components/Modal/Modal.tsx
index d14f6411a..9dc622a18 100644
--- a/frontend/app/components/Modal/Modal.tsx
+++ b/frontend/app/components/Modal/Modal.tsx
@@ -3,14 +3,14 @@ import ReactDOM from 'react-dom';
import ModalOverlay from './ModalOverlay';
export default function Modal({ component, props, hideModal }: any) {
- return component ? ReactDOM.createPortal(
-
- {component}
- ,
- document.querySelector("#modal-root"),
- ) : <>>;
-}
\ No newline at end of file
+ return component ? (
+ ReactDOM.createPortal(
+
+ {component}
+ ,
+ document.querySelector('#modal-root')
+ )
+ ) : (
+ <>>
+ );
+}
diff --git a/frontend/app/components/Modal/ModalOverlay.tsx b/frontend/app/components/Modal/ModalOverlay.tsx
index 398e27f2f..5b2a9edab 100644
--- a/frontend/app/components/Modal/ModalOverlay.tsx
+++ b/frontend/app/components/Modal/ModalOverlay.tsx
@@ -1,18 +1,14 @@
import React from 'react';
-import stl from './ModalOverlay.module.css'
+import stl from './ModalOverlay.module.css';
import cn from 'classnames';
function ModalOverlay({ hideModal, children, left = false, right = false }: any) {
return (
-
-
{children}
+
+
{children}
);
}
-export default ModalOverlay;
\ No newline at end of file
+export default ModalOverlay;
diff --git a/frontend/app/components/Modal/index.tsx b/frontend/app/components/Modal/index.tsx
index 04e2acd91..920cb2d14 100644
--- a/frontend/app/components/Modal/index.tsx
+++ b/frontend/app/components/Modal/index.tsx
@@ -3,60 +3,59 @@ import React, { Component, createContext } from 'react';
import Modal from './Modal';
const ModalContext = createContext({
- component: null,
- props: {
- right: false,
- onClose: () => {},
- },
- showModal: (component: any, props: any) => {},
- hideModal: () => {}
+ component: null,
+ props: {
+ right: true,
+ onClose: () => {},
+ },
+ showModal: (component: any, props: any) => {},
+ hideModal: () => {},
});
export class ModalProvider extends Component {
-
- handleKeyDown = (e: any) => {
- if (e.keyCode === 27) {
- this.hideModal();
- }
- }
-
- showModal = (component, props = { }) => {
- this.setState({
- component,
- props
- });
- document.addEventListener('keydown', this.handleKeyDown);
- document.querySelector("body").style.overflow = 'hidden';
- };
-
- hideModal = () => {
- document.removeEventListener('keydown', this.handleKeyDown);
- document.querySelector("body").style.overflow = 'visible';
- const { props } = this.state;
- if (props.onClose) {
- props.onClose();
+ handleKeyDown = (e: any) => {
+ if (e.keyCode === 27) {
+ this.hideModal();
+ }
};
- this.setState({
- component: null,
- props: {}
- });
- }
- state = {
- component: null,
- props: {},
- showModal: this.showModal,
- hideModal: this.hideModal
- };
+ showModal = (component, props = { right: true }) => {
+ this.setState({
+ component,
+ props,
+ });
+ document.addEventListener('keydown', this.handleKeyDown);
+ document.querySelector('body').style.overflow = 'hidden';
+ };
- render() {
- return (
-
-
- {this.props.children}
-
- );
- }
+ hideModal = () => {
+ document.removeEventListener('keydown', this.handleKeyDown);
+ document.querySelector('body').style.overflow = 'visible';
+ const { props } = this.state;
+ if (props.onClose) {
+ props.onClose();
+ }
+ this.setState({
+ component: null,
+ props: {},
+ });
+ };
+
+ state = {
+ component: null,
+ props: {},
+ showModal: this.showModal,
+ hideModal: this.hideModal,
+ };
+
+ render() {
+ return (
+
+
+ {this.props.children}
+
+ );
+ }
}
export const ModalConsumer = ModalContext.Consumer;
diff --git a/frontend/app/components/hocs/index.js b/frontend/app/components/hocs/index.js
index 444ad0180..5f08b86f0 100644
--- a/frontend/app/components/hocs/index.js
+++ b/frontend/app/components/hocs/index.js
@@ -1,2 +1,3 @@
export { default as withRequest } from './withRequest';
-export { default as withToggle } from './withToggle';
\ No newline at end of file
+export { default as withToggle } from './withToggle';
+export { default as withCopy } from './withCopy'
\ No newline at end of file
diff --git a/frontend/app/components/hocs/withCopy.tsx b/frontend/app/components/hocs/withCopy.tsx
new file mode 100644
index 000000000..2b3a9d541
--- /dev/null
+++ b/frontend/app/components/hocs/withCopy.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import copy from 'copy-to-clipboard';
+import { Tooltip } from 'react-tippy';
+
+const withCopy = (WrappedComponent: React.ComponentType) => {
+ const ComponentWithCopy = (props: any) => {
+ const [copied, setCopied] = React.useState(false);
+ const { value, tooltip } = props;
+ const copyToClipboard = (text: string) => {
+ copy(text);
+ setCopied(true);
+ setTimeout(() => {
+ setCopied(false);
+ }, 1000);
+ };
+ return (
+ copyToClipboard(value)} className="w-fit">
+
+
+
+
+ );
+ };
+ return ComponentWithCopy;
+};
+
+export default withCopy;
diff --git a/frontend/app/components/hocs/withRequest.js b/frontend/app/components/hocs/withRequest.js
index 80dfaccf3..992b0ce4e 100644
--- a/frontend/app/components/hocs/withRequest.js
+++ b/frontend/app/components/hocs/withRequest.js
@@ -2,66 +2,66 @@ import React from 'react';
import APIClient from 'App/api_client';
export default ({
- initialData = null,
- endpoint = '',
- method = 'GET',
- requestName = "request",
- loadingName = "loading",
- errorName = "requestError",
- dataName = "data",
- dataWrapper = data => data,
- loadOnInitialize = false,
- resetBeforeRequest = false, // Probably use handler?
-}) => BaseComponent => class extends React.PureComponent {
- constructor(props) {
- super(props);
- this.state = {
- data: typeof initialData === 'function' ? initialData(props) : initialData,
- loading: loadOnInitialize,
- error: false,
- };
- if (loadOnInitialize) {
- this.request();
- }
- }
+ initialData = null,
+ endpoint = '',
+ method = 'GET',
+ requestName = 'request',
+ loadingName = 'loading',
+ errorName = 'requestError',
+ dataName = 'data',
+ dataWrapper = (data) => data,
+ loadOnInitialize = false,
+ resetBeforeRequest = false, // Probably use handler?
+ }) =>
+ (BaseComponent) =>
+ class extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ data: typeof initialData === 'function' ? initialData(props) : initialData,
+ loading: loadOnInitialize,
+ error: false,
+ };
+ if (loadOnInitialize) {
+ this.request();
+ }
+ }
- request = (params, edpParams) => {
- this.setState({
- loading: true,
- error: false,
- data: resetBeforeRequest
- ? (typeof initialData === 'function' ? initialData(this.props) : initialData)
- : this.state.data,
- });
- const edp = typeof endpoint === 'function'
- ? endpoint(this.props, edpParams)
- : endpoint;
- return new APIClient()[ method.toLowerCase() ](edp, params)
- .then(response => response.json())
- .then(({ errors, data }) => {
- if (errors) {
- return this.setError();
- }
- this.setState({
- data: dataWrapper(data, this.state.data),
- loading: false,
- });
- })
- .catch(this.setError);
- }
+ request = (params, edpParams) => {
+ this.setState({
+ loading: true,
+ error: false,
+ data: resetBeforeRequest ? (typeof initialData === 'function' ? initialData(this.props) : initialData) : this.state.data,
+ });
+ const edp = typeof endpoint === 'function' ? endpoint(this.props, edpParams) : endpoint;
+ return new APIClient()
+ [method.toLowerCase()](edp, params)
+ .then((response) => response.json())
+ .then(({ errors, data }) => {
+ if (errors) {
+ return this.setError();
+ }
+ this.setState({
+ data: dataWrapper(data, this.state.data),
+ loading: false,
+ });
+ })
+ .catch(this.setError);
+ };
- setError = () => this.setState({
- loading: false,
- error: true,
- })
+ setError = () =>
+ this.setState({
+ loading: false,
+ error: true,
+ });
- render() {
- const ownProps = {
- [ requestName ]: this.request,
- [ loadingName ]: this.state.loading,
- [ dataName ]: this.state.data,
- [ errorName ]: this.state.error,
- };
- return
- }
-}
\ No newline at end of file
+ render() {
+ const ownProps = {
+ [requestName]: this.request,
+ [loadingName]: this.state.loading,
+ [dataName]: this.state.data,
+ [errorName]: this.state.error,
+ };
+ return ;
+ }
+ };
diff --git a/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js b/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js
index cd8c23707..586cc8742 100644
--- a/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js
+++ b/frontend/app/components/shared/TrackingCodeModal/TrackingCodeModal.js
@@ -3,65 +3,79 @@ import { Modal, Icon, Tabs } from 'UI';
import styles from './trackingCodeModal.module.css';
import { editGDPR, saveGDPR } from 'Duck/site';
import { connect } from 'react-redux';
-import ProjectCodeSnippet from './ProjectCodeSnippet';
+import ProjectCodeSnippet from './ProjectCodeSnippet';
import InstallDocs from './InstallDocs';
import cn from 'classnames';
const PROJECT = 'Using Script';
const DOCUMENTATION = 'Using NPM';
const TABS = [
- { key: DOCUMENTATION, text: DOCUMENTATION },
- { key: PROJECT, text: PROJECT },
+ { key: DOCUMENTATION, text: DOCUMENTATION },
+ { key: PROJECT, text: PROJECT },
];
class TrackingCodeModal extends React.PureComponent {
- state = { copied: false, changed: false, activeTab: DOCUMENTATION };
+ state = { copied: false, changed: false, activeTab: DOCUMENTATION };
- setActiveTab = (tab) => {
- this.setState({ activeTab: tab });
- }
+ setActiveTab = (tab) => {
+ this.setState({ activeTab: tab });
+ };
- renderActiveTab = () => {
- const { site } = this.props;
- switch (this.state.activeTab) {
- case PROJECT:
- return ;
- case DOCUMENTATION:
- return ;
+ renderActiveTab = () => {
+ const { site } = this.props;
+ switch (this.state.activeTab) {
+ case PROJECT:
+ return ;
+ case DOCUMENTATION:
+ return ;
+ }
+ return null;
+ };
+
+ render() {
+ const { site, displayed, onClose, title = '', subTitle } = this.props;
+ const { activeTab } = this.state;
+ return (
+
+
+ {title} {subTitle && {subTitle}}
+
+
+
+
+
{this.renderActiveTab()}
+
+
+ // displayed &&
+ //
+ //
+ // { title } { subTitle && {subTitle}}
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ // { this.renderActiveTab() }
+ //
+ //
+ //
+ );
}
- return null;
- }
-
- render() {
- const { site, displayed, onClose, title = '', subTitle } = this.props;
- const { activeTab } = this.state;
- return (
- displayed &&
-
-
- { title } { subTitle && {subTitle}}
-
-
-
-
-
-
-
- { this.renderActiveTab() }
-
-
-
- );
- }
}
-export default connect(state => ({
- site: state.getIn([ 'site', 'instance' ]),
- gdpr: state.getIn([ 'site', 'instance', 'gdpr' ]),
- saving: state.getIn([ 'site', 'saveGDPR', 'loading' ]),
-}), {
- editGDPR, saveGDPR
-})(TrackingCodeModal);
\ No newline at end of file
+export default connect(
+ (state) => ({
+ site: state.getIn(['site', 'instance']),
+ gdpr: state.getIn(['site', 'instance', 'gdpr']),
+ saving: state.getIn(['site', 'saveGDPR', 'loading']),
+ }),
+ {
+ editGDPR,
+ saveGDPR,
+ }
+)(TrackingCodeModal);
diff --git a/frontend/app/components/ui/Form/Form.tsx b/frontend/app/components/ui/Form/Form.tsx
index c9ab7c036..a85af0b23 100644
--- a/frontend/app/components/ui/Form/Form.tsx
+++ b/frontend/app/components/ui/Form/Form.tsx
@@ -2,16 +2,15 @@ import React from 'react';
interface Props {
children: React.ReactNode;
- onSubmit?: any
- [x: string]: any
+ onSubmit?: any;
+ [x: string]: any;
}
-
interface FormFieldProps {
children: React.ReactNode;
- [x: string]: any
+ [x: string]: any;
}
-function FormField (props: FormFieldProps) {
+function FormField(props: FormFieldProps) {
const { children, ...rest } = props;
return (
@@ -20,16 +19,18 @@ function FormField (props: FormFieldProps) {
);
}
-
function Form(props: Props) {
const { children, ...rest } = props;
return (
-
);
@@ -37,4 +38,4 @@ function Form(props: Props) {
Form.Field = FormField;
-export default Form;
\ No newline at end of file
+export default Form;
diff --git a/frontend/app/components/ui/Input/Input.tsx b/frontend/app/components/ui/Input/Input.tsx
index 1897ece13..1c36f7a8a 100644
--- a/frontend/app/components/ui/Input/Input.tsx
+++ b/frontend/app/components/ui/Input/Input.tsx
@@ -11,13 +11,14 @@ interface Props {
rows?: number;
[x: string]: any;
}
-function Input(props: Props) {
+const Input = React.forwardRef((props: Props, ref: any) => {
const { className = '', leadingButton = '', wrapperClassName = '', icon = '', type = 'text', rows = 4, ...rest } = props;
return (
{icon && }
{type === 'textarea' ? (
) : (
{leadingButton}
}
);
-}
+});
export default Input;
diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx
index 8f7bab660..a6df932b4 100644
--- a/frontend/app/components/ui/SVG.tsx
+++ b/frontend/app/components/ui/SVG.tsx
@@ -235,26 +235,28 @@ const SVG = (props: Props) => {
case 'info': return ;
case 'inspect': return ;
case 'integrations/assist': return ;
+ case 'integrations/aws': return ;
case 'integrations/bugsnag-text': return ;
- case 'integrations/bugsnag': return ;
+ case 'integrations/bugsnag': return ;
case 'integrations/cloudwatch-text': return ;
case 'integrations/cloudwatch': return ;
case 'integrations/datadog': return ;
case 'integrations/elasticsearch-text': return ;
case 'integrations/elasticsearch': return ;
case 'integrations/github': return ;
+ case 'integrations/google-cloud': return ;
case 'integrations/graphql': return ;
case 'integrations/jira-text': return ;
case 'integrations/jira': return ;
case 'integrations/mobx': return ;
case 'integrations/newrelic-text': return ;
- case 'integrations/newrelic': return ;
+ case 'integrations/newrelic': return ;
case 'integrations/ngrx': return ;
case 'integrations/openreplay-text': return ;
case 'integrations/openreplay': return ;
case 'integrations/redux': return ;
case 'integrations/rollbar-text': return ;
- case 'integrations/rollbar': return ;
+ case 'integrations/rollbar': return ;
case 'integrations/segment': return ;
case 'integrations/sentry-text': return ;
case 'integrations/sentry': return ;
diff --git a/frontend/app/components/ui/TextLink/TextLink.js b/frontend/app/components/ui/TextLink/TextLink.js
index 4738e2152..089122ed4 100644
--- a/frontend/app/components/ui/TextLink/TextLink.js
+++ b/frontend/app/components/ui/TextLink/TextLink.js
@@ -1,24 +1,14 @@
-import React from 'react'
-import cn from 'classnames'
+import React from 'react';
+import cn from 'classnames';
import { Icon } from 'UI';
-function TextLink({
- target = '_blank',
- href = '',
- icon = '',
- label='',
- className = ''
-}) {
- return (
-
- { icon && }
- {label}
-
- )
+function TextLink({ target = '_blank', href = '', icon = '', label = '', className = '' }) {
+ return (
+
+ {icon && }
+ {label}
+
+ );
}
-export default TextLink
+export default TextLink;
diff --git a/frontend/app/components/ui/Toggler/Toggler.js b/frontend/app/components/ui/Toggler/Toggler.js
index f4db7cb7a..7aa670743 100644
--- a/frontend/app/components/ui/Toggler/Toggler.js
+++ b/frontend/app/components/ui/Toggler/Toggler.js
@@ -1,26 +1,14 @@
import React from 'react';
import styles from './toggler.module.css';
-export default ({
- onChange,
- name,
- className = '',
- checked,
- label = '',
- plain = false,
-}) => (
-
-
-
+export default ({ onChange, name, className = '', checked, label = '', plain = false }) => (
+
+
+
);
diff --git a/frontend/app/duck/integrations/actions.js b/frontend/app/duck/integrations/actions.js
index 4bffda55c..9ab831c41 100644
--- a/frontend/app/duck/integrations/actions.js
+++ b/frontend/app/duck/integrations/actions.js
@@ -1,39 +1,47 @@
import { array } from '../funcTools/tools';
-import { fetchListType, saveType, editType, initType, removeType } from '../funcTools/types';
+import { fetchListType, fetchType, saveType, editType, initType, removeType } from '../funcTools/types';
export function fetchList(name) {
- return {
- types: fetchListType(name).array,
- call: client => client.get(`/integrations/${ name }`),
- name
- };
+ return {
+ types: fetchListType(name).array,
+ call: (client) => client.get(`/integrations/${name}`),
+ name,
+ };
+}
+
+export function fetch(name, siteId) {
+ return {
+ types: fetchType(name).array,
+ call: (client) => client.get(siteId && name !== 'github' && name !== 'jira' ? `/${siteId}/integrations/${name}` : `/integrations/${name}`),
+ name,
+ };
}
export function save(name, siteId, instance) {
- return {
- types: saveType(name).array,
- call: client => client.post( (siteId ? `/${siteId}` : '') + `/integrations/${ name }`, instance.toData()),
- };
+ return {
+ types: saveType(name).array,
+ call: (client) => client.post((siteId ? `/${siteId}` : '') + `/integrations/${name}`, instance.toData()),
+ };
}
export function edit(name, instance) {
- return {
- type: editType(name),
- instance,
- };
+ return {
+ type: editType(name),
+ instance,
+ };
}
export function init(name, instance) {
- return {
- type: initType(name),
- instance,
- };
+ return {
+ type: initType(name),
+ instance,
+ };
}
export function remove(name, siteId) {
- return {
- types: removeType(name).array,
- call: client => client.delete((siteId ? `/${siteId}` : '') + `/integrations/${ name }`),
- siteId,
- };
-}
\ No newline at end of file
+ return {
+ types: removeType(name).array,
+ call: (client) => client.delete((siteId ? `/${siteId}` : '') + `/integrations/${name}`),
+ siteId,
+ };
+}
diff --git a/frontend/app/duck/integrations/index.js b/frontend/app/duck/integrations/index.js
index 5e439675d..0274f7c80 100644
--- a/frontend/app/duck/integrations/index.js
+++ b/frontend/app/duck/integrations/index.js
@@ -11,23 +11,25 @@ import JiraConfig from 'Types/integrations/jiraConfig';
import GithubConfig from 'Types/integrations/githubConfig';
import IssueTracker from 'Types/integrations/issueTracker';
import slack from './slack';
+import integrations from './integrations';
-import { createIntegrationReducer } from './reducer'
+import { createIntegrationReducer } from './reducer';
-export default {
- sentry: createIntegrationReducer("sentry", SentryConfig),
- datadog: createIntegrationReducer("datadog", DatadogConfig),
- stackdriver: createIntegrationReducer("stackdriver", StackdriverConfig),
- rollbar: createIntegrationReducer("rollbar", RollbarConfig),
- newrelic: createIntegrationReducer("newrelic", NewrelicConfig),
- bugsnag: createIntegrationReducer("bugsnag", BugsnagConfig),
- cloudwatch: createIntegrationReducer("cloudwatch", CloudWatch),
- elasticsearch: createIntegrationReducer("elasticsearch", ElasticsearchConfig),
- sumologic: createIntegrationReducer("sumologic", SumoLogicConfig),
- jira: createIntegrationReducer("jira", JiraConfig),
- issues: createIntegrationReducer("issues", IssueTracker),
- github: createIntegrationReducer("github", GithubConfig),
- slack,
+export default {
+ sentry: createIntegrationReducer('sentry', SentryConfig),
+ datadog: createIntegrationReducer('datadog', DatadogConfig),
+ stackdriver: createIntegrationReducer('stackdriver', StackdriverConfig),
+ rollbar: createIntegrationReducer('rollbar', RollbarConfig),
+ newrelic: createIntegrationReducer('newrelic', NewrelicConfig),
+ bugsnag: createIntegrationReducer('bugsnag', BugsnagConfig),
+ cloudwatch: createIntegrationReducer('cloudwatch', CloudWatch),
+ elasticsearch: createIntegrationReducer('elasticsearch', ElasticsearchConfig),
+ sumologic: createIntegrationReducer('sumologic', SumoLogicConfig),
+ jira: createIntegrationReducer('jira', JiraConfig),
+ github: createIntegrationReducer('github', GithubConfig),
+ issues: createIntegrationReducer('issues', IssueTracker),
+ slack,
+ integrations,
};
export * from './actions';
diff --git a/frontend/app/duck/integrations/integrations.js b/frontend/app/duck/integrations/integrations.js
new file mode 100644
index 000000000..b2cceabac
--- /dev/null
+++ b/frontend/app/duck/integrations/integrations.js
@@ -0,0 +1,40 @@
+import { Map } from 'immutable';
+import withRequestState from 'Duck/requestStateCreator';
+import { fetchListType } from '../funcTools/types';
+
+const FETCH_LIST = fetchListType('integrations/FETCH_LIST');
+const SET_SITE_ID = 'integrations/SET_SITE_ID';
+const initialState = Map({
+ list: [],
+ siteId: null,
+});
+const reducer = (state = initialState, action = {}) => {
+ switch (action.type) {
+ case FETCH_LIST.success:
+ return state.set('list', action.data);
+ case SET_SITE_ID:
+ return state.set('siteId', action.siteId);
+ }
+ return state;
+};
+
+export default withRequestState(
+ {
+ fetchRequest: FETCH_LIST,
+ },
+ reducer
+);
+
+export function fetchIntegrationList(siteID) {
+ return {
+ types: FETCH_LIST.array,
+ call: (client) => client.get(`/${siteID}/integrations`),
+ };
+}
+
+export function setSiteId(siteId) {
+ return {
+ type: SET_SITE_ID,
+ siteId,
+ };
+}
diff --git a/frontend/app/duck/integrations/reducer.js b/frontend/app/duck/integrations/reducer.js
index 166bb661e..56c531610 100644
--- a/frontend/app/duck/integrations/reducer.js
+++ b/frontend/app/duck/integrations/reducer.js
@@ -1,48 +1,52 @@
import { List, Map } from 'immutable';
import { createRequestReducer } from '../funcTools/request';
-import { fetchListType, saveType, removeType, editType, initType } from '../funcTools/types';
+import { fetchListType, saveType, removeType, editType, initType, fetchType } from '../funcTools/types';
import { createItemInListUpdater } from '../funcTools/tools';
const idKey = 'siteId';
const itemInListUpdater = createItemInListUpdater(idKey);
export const createIntegrationReducer = (name, Config) => {
- const FETCH_LIST = fetchListType(name);
- const SAVE = saveType(name);
- const REMOVE = removeType(name);
- const EDIT = editType(name);
- const INIT = initType(name);
+ const FETCH_LIST = fetchListType(name);
+ const SAVE = saveType(name);
+ const REMOVE = removeType(name);
+ const EDIT = editType(name);
+ const INIT = initType(name);
+ const FETCH = fetchType(name);
- const initialState = Map({
- instance: Config(),
- list: List(),
- fetched: false,
- issuesFetched: false
- });
- const reducer = (state = initialState, action = {}) => {
- switch (action.type) {
- case FETCH_LIST.success:
- return state.set('list', Array.isArray(action.data) ?
- List(action.data).map(Config) : List([new Config(action.data)])).set(action.name + 'Fetched', true);
- case SAVE.success:
- const config = Config(action.data);
- return state
- .update('list', itemInListUpdater(config))
- .set('instance', config);
- case REMOVE.success:
- return state
- .update('list', list => list.filter(site => site.siteId !== action.siteId))
- .set('instance', Config())
- case EDIT:
- return state.mergeIn([ 'instance' ], action.instance);
- case INIT:
- return state.set('instance', Config(action.instance));
- }
- return state;
- };
- return createRequestReducer({
- fetchRequest: FETCH_LIST,
- saveRequest: SAVE,
- removeRequest: REMOVE,
- }, reducer);
-}
+ const initialState = Map({
+ instance: Config(),
+ list: List(),
+ fetched: false,
+ issuesFetched: false,
+ });
+ const reducer = (state = initialState, action = {}) => {
+ switch (action.type) {
+ case FETCH_LIST.success:
+ return state
+ .set('list', Array.isArray(action.data) ? List(action.data).map(Config) : List([new Config(action.data)]))
+ .set(action.name + 'Fetched', true);
+ case FETCH.success:
+ return state.set('instance', Config(action.data));
+ case SAVE.success:
+ const config = Config(action.data);
+ return state.update('list', itemInListUpdater(config)).set('instance', config);
+ case REMOVE.success:
+ return state.update('list', (list) => list.filter((site) => site.siteId !== action.siteId)).set('instance', Config());
+ case EDIT:
+ return state.mergeIn(['instance'], action.instance);
+ case INIT:
+ return state.set('instance', Config(action.instance));
+ }
+ return state;
+ };
+ return createRequestReducer(
+ {
+ // fetchRequest: FETCH_LIST,
+ fetchRequest: FETCH,
+ saveRequest: SAVE,
+ removeRequest: REMOVE,
+ },
+ reducer
+ );
+};
diff --git a/frontend/app/duck/integrations/slack.js b/frontend/app/duck/integrations/slack.js
index e4c2803ff..192bdd0cf 100644
--- a/frontend/app/duck/integrations/slack.js
+++ b/frontend/app/duck/integrations/slack.js
@@ -13,77 +13,76 @@ const idKey = 'webhookId';
const itemInListUpdater = createItemInListUpdater(idKey);
const initialState = Map({
- instance: Config(),
- list: List(),
+ instance: Config(),
+ list: List(),
});
const reducer = (state = initialState, action = {}) => {
- switch (action.type) {
- case FETCH_LIST.SUCCESS:
- return state.set('list', List(action.data).map(Config));
- case UPDATE.SUCCESS:
- case SAVE.SUCCESS:
- const config = Config(action.data);
- return state
- .update('list', itemInListUpdater(config))
- .set('instance', config);
- case REMOVE.SUCCESS:
- return state
- .update('list', list => list.filter(item => item.webhookId !== action.id))
- .set('instance', Config())
- case EDIT:
- return state.mergeIn([ 'instance' ], action.instance);
- case INIT:
- return state.set('instance', Config(action.instance));
- }
- return state;
+ switch (action.type) {
+ case FETCH_LIST.SUCCESS:
+ return state.set('list', List(action.data).map(Config));
+ case UPDATE.SUCCESS:
+ case SAVE.SUCCESS:
+ const config = Config(action.data);
+ return state.update('list', itemInListUpdater(config)).set('instance', config);
+ case REMOVE.SUCCESS:
+ return state.update('list', (list) => list.filter((item) => item.webhookId !== action.id)).set('instance', Config());
+ case EDIT:
+ return state.mergeIn(['instance'], action.instance);
+ case INIT:
+ return state.set('instance', Config(action.instance));
+ }
+ return state;
};
-export default withRequestState({
- fetchRequest: FETCH_LIST,
- saveRequest: SAVE,
- removeRequest: REMOVE,
-}, reducer);
+export default withRequestState(
+ {
+ fetchRequest: FETCH_LIST,
+ saveRequest: SAVE,
+ removeRequest: REMOVE,
+ },
+ reducer
+);
export function fetchList() {
- return {
- types: FETCH_LIST.toArray(),
- call: client => client.get('/integrations/slack/channels'),
- };
+ return {
+ types: FETCH_LIST.toArray(),
+ call: (client) => client.get('/integrations/slack/channels'),
+ };
}
export function save(instance) {
- return {
- types: SAVE.toArray(),
- call: client => client.post(`/integrations/slack`, instance.toData()),
- };
+ return {
+ types: SAVE.toArray(),
+ call: (client) => client.post(`/integrations/slack`, instance.toData()),
+ };
}
export function update(instance) {
- return {
- types: UPDATE.toArray(),
- call: client => client.put(`/integrations/slack/${instance.webhookId}`, instance.toData()),
- };
+ return {
+ types: UPDATE.toArray(),
+ call: (client) => client.put(`/integrations/slack/${instance.webhookId}`, instance.toData()),
+ };
}
export function edit(instance) {
- return {
- type: EDIT,
- instance,
- };
+ return {
+ type: EDIT,
+ instance,
+ };
}
export function init(instance) {
- return {
- type: INIT,
- instance,
- };
+ return {
+ type: INIT,
+ instance,
+ };
}
export function remove(id) {
- return {
- types: REMOVE.toArray(),
- call: client => client.delete(`/integrations/slack/${id}`),
- id,
- };
-}
\ No newline at end of file
+ return {
+ types: REMOVE.toArray(),
+ call: (client) => client.delete(`/integrations/slack/${id}`),
+ id,
+ };
+}
diff --git a/frontend/app/svg/icons/integrations/bugsnag.svg b/frontend/app/svg/icons/integrations/bugsnag.svg
index 26a3a13b8..cc97e195b 100644
--- a/frontend/app/svg/icons/integrations/bugsnag.svg
+++ b/frontend/app/svg/icons/integrations/bugsnag.svg
@@ -1 +1,4 @@
-
\ No newline at end of file
+
diff --git a/frontend/app/svg/icons/integrations/newrelic.svg b/frontend/app/svg/icons/integrations/newrelic.svg
index cc4aea514..061e7e0a3 100644
--- a/frontend/app/svg/icons/integrations/newrelic.svg
+++ b/frontend/app/svg/icons/integrations/newrelic.svg
@@ -1 +1,12 @@
-
\ No newline at end of file
+
diff --git a/frontend/app/svg/icons/integrations/rollbar.svg b/frontend/app/svg/icons/integrations/rollbar.svg
index 2f6538118..0d183182b 100644
--- a/frontend/app/svg/icons/integrations/rollbar.svg
+++ b/frontend/app/svg/icons/integrations/rollbar.svg
@@ -1,20 +1,10 @@
-
-
-