fix(ui) - integration form

This commit is contained in:
Shekar Siri 2023-01-27 19:13:59 +01:00
parent 25ddb03b3f
commit 48145e5a70
3 changed files with 267 additions and 188 deletions

View file

@ -70,29 +70,18 @@ export default class IntegrationForm extends React.PureComponent {
remove = () => { remove = () => {
const { name, config, ignoreProject } = this.props; const { name, config, ignoreProject } = this.props;
this.props.remove(name, !ignoreProject ? config.projectId : null).then( this.props.remove(name, !ignoreProject ? config.projectId : null).then(() => {
function () { this.props.onClose();
this.props.onClose(); this.fetchList();
this.fetchList(); });
}.bind(this)
);
}; };
render() { render() {
const { config, saving, removing, formFields, name, loading, ignoreProject } = this.props; const { config, saving, removing, formFields, name, loading, integrated } = this.props;
// const { currentSiteId } = this.state;
return ( return (
<Loader loading={loading}> <Loader loading={loading}>
<div className="ph-20"> <div className="ph-20">
<Form> <Form>
{/* {!ignoreProject && (
<Form.Field>
<label>{'OpenReplay Project'}</label>
<SiteDropdown value={currentSiteId} onChange={this.onChangeSelect} />
</Form.Field>
)} */}
{formFields.map( {formFields.map(
({ ({
key, key,
@ -140,7 +129,7 @@ export default class IntegrationForm extends React.PureComponent {
{config.exists() ? 'Update' : 'Add'} {config.exists() ? 'Update' : 'Add'}
</Button> </Button>
{config.exists() && ( {integrated && (
<Button loading={removing} onClick={this.remove}> <Button loading={removing} onClick={this.remove}>
{'Delete'} {'Delete'}
</Button> </Button>

View file

@ -27,159 +27,244 @@ import AssistDoc from './AssistDoc';
import { PageTitle, Tooltip } from 'UI'; import { PageTitle, Tooltip } from 'UI';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import withPageTitle from 'HOCs/withPageTitle'; import withPageTitle from 'HOCs/withPageTitle';
import PiniaDoc from './PiniaDoc' import PiniaDoc from './PiniaDoc';
import ZustandDoc from './ZustandDoc' import ZustandDoc from './ZustandDoc';
import MSTeams from './Teams' import MSTeams from './Teams';
interface Props { interface Props {
fetch: (name: string, siteId: string) => void; fetch: (name: string, siteId: string) => void;
init: () => void; init: () => void;
fetchIntegrationList: (siteId: any) => void; fetchIntegrationList: (siteId: any) => void;
integratedList: any; integratedList: any;
initialSiteId: string; initialSiteId: string;
setSiteId: (siteId: string) => void; setSiteId: (siteId: string) => void;
siteId: string; siteId: string;
hideHeader?: boolean; hideHeader?: boolean;
loading?: boolean; loading?: boolean;
} }
function Integrations(props: Props) { function Integrations(props: Props) {
const { initialSiteId, hideHeader = false, loading = false } = props; const { initialSiteId, hideHeader = false, loading = false } = props;
const { showModal } = useModal(); const { showModal } = useModal();
const [integratedList, setIntegratedList] = React.useState([]); const [integratedList, setIntegratedList] = React.useState<any>([]);
useEffect(() => { useEffect(() => {
const list = props.integratedList.filter((item: any) => item.integrated).map((item: any) => item.name); const list = props.integratedList
setIntegratedList(list); .filter((item: any) => item.integrated)
}, [props.integratedList]); .map((item: any) => item.name);
setIntegratedList(list);
}, [props.integratedList]);
useEffect(() => { useEffect(() => {
if (!props.siteId) { if (!props.siteId) {
props.setSiteId(initialSiteId); props.setSiteId(initialSiteId);
props.fetchIntegrationList(initialSiteId); props.fetchIntegrationList(initialSiteId);
} else { } else {
props.fetchIntegrationList(props.siteId); props.fetchIntegrationList(props.siteId);
} }
}, []); }, []);
const onClick = (integration: any, width: number) => { const onClick = (integration: any, width: number) => {
if (integration.slug) { if (integration.slug) {
props.fetch(integration.slug, props.siteId); props.fetch(integration.slug, props.siteId);
} }
showModal(integration.component, { right: true, width });
};
const onChangeSelect = ({ value }: any) => { showModal(
props.setSiteId(value.value); React.cloneElement(integration.component, {
props.fetchIntegrationList(value.value); integrated: integratedList.includes(integration.slug),
}; }),
{ right: true, width }
return (
<div className="mb-4 p-5">
{!hideHeader && <PageTitle title={<div>Integrations</div>} />}
{integrations.map((cat: any) => (
<div className="mb-2 border-b last:border-none py-3" key={cat.key}>
<div className="flex items-center">
<h2 className="font-medium text-lg">{cat.title}</h2>
{cat.isProject && (
<div className="flex items-center">
<div className="flex flex-wrap mx-4">
<SiteDropdown value={props.siteId} onChange={onChangeSelect} />
</div>
{loading && cat.isProject && <AnimatedSVG name={ICONS.LOADER} size={20} />}
</div>
)}
</div>
<div className="">{cat.description}</div>
<div className="flex flex-wrap mt-4">
{cat.integrations.map((integration: any) => (
<React.Fragment key={integration.slug}>
<Tooltip
delay={50}
title="Global configuration, available to all team members."
disabled={!integration.shared}
placement={"bottom"}
>
<IntegrationItem
integrated={integratedList.includes(integration.slug)}
integration={integration}
onClick={() => onClick(integration, cat.title === "Plugins" ? 500 : 350)}
hide={
(integration.slug === 'github' && integratedList.includes('jira')) ||
(integration.slug === 'jira' && integratedList.includes('github'))
}
/>
</Tooltip>
</React.Fragment>
))}
</div>
</div>
))}
</div>
); );
};
const onChangeSelect = ({ value }: any) => {
props.setSiteId(value.value);
props.fetchIntegrationList(value.value);
};
return (
<div className="mb-4 p-5">
{!hideHeader && <PageTitle title={<div>Integrations</div>} />}
{integrations.map((cat: any) => (
<div className="mb-2 border-b last:border-none py-3" key={cat.key}>
<div className="flex items-center">
<h2 className="font-medium text-lg">{cat.title}</h2>
{cat.isProject && (
<div className="flex items-center">
<div className="flex flex-wrap mx-4">
<SiteDropdown value={props.siteId} onChange={onChangeSelect} />
</div>
{loading && cat.isProject && <AnimatedSVG name={ICONS.LOADER} size={20} />}
</div>
)}
</div>
<div className="">{cat.description}</div>
<div className="flex flex-wrap mt-4">
{cat.integrations.map((integration: any) => (
<React.Fragment key={integration.slug}>
<Tooltip
delay={50}
title="Global configuration, available to all team members."
disabled={!integration.shared}
placement={'bottom'}
>
<IntegrationItem
integrated={integratedList.includes(integration.slug)}
integration={integration}
onClick={() => onClick(integration, cat.title === 'Plugins' ? 500 : 350)}
hide={
(integration.slug === 'github' && integratedList.includes('jira')) ||
(integration.slug === 'jira' && integratedList.includes('github'))
}
/>
</Tooltip>
</React.Fragment>
))}
</div>
</div>
))}
</div>
);
} }
export default connect( export default connect(
(state: any) => ({ (state: any) => ({
initialSiteId: state.getIn(['site', 'siteId']), initialSiteId: state.getIn(['site', 'siteId']),
integratedList: state.getIn(['integrations', 'list']) || [], integratedList: state.getIn(['integrations', 'list']) || [],
loading: state.getIn(['integrations', 'fetchRequest', 'loading']), loading: state.getIn(['integrations', 'fetchRequest', 'loading']),
siteId: state.getIn(['integrations', 'siteId']), siteId: state.getIn(['integrations', 'siteId']),
}), }),
{ fetch, init, fetchIntegrationList, setSiteId } { fetch, init, fetchIntegrationList, setSiteId }
)(withPageTitle('Integrations - OpenReplay Preferences')(Integrations)); )(withPageTitle('Integrations - OpenReplay Preferences')(Integrations));
const integrations = [ const integrations = [
{ {
title: 'Issue Reporting and Collaborations', title: 'Issue Reporting and Collaborations',
key: 1, key: 1,
description: 'Seamlessly report issues or share issues with your team right from OpenReplay.', description: 'Seamlessly report issues or share issues with your team right from OpenReplay.',
isProject: false, isProject: false,
integrations: [ integrations: [
{ title: 'Jira', slug: 'jira', category: 'Errors', icon: 'integrations/jira', component: <JiraForm /> }, {
{ title: 'Github', slug: 'github', category: 'Errors', icon: 'integrations/github', component: <GithubForm /> }, title: 'Jira',
{ title: 'Slack', slug: 'slack', category: 'Errors', icon: 'integrations/slack', component: <SlackForm />, shared: true }, slug: 'jira',
{ title: 'MS Teams', slug: 'msteams', category: 'Errors', icon: 'integrations/teams', component: <MSTeams />, shared: true }, category: 'Errors',
], icon: 'integrations/jira',
}, component: <JiraForm />,
{ },
title: 'Backend Logging', {
key: 2, title: 'Github',
isProject: true, slug: 'github',
description: 'Sync your backend errors with sessions replays and see what happened front-to-back.', category: 'Errors',
integrations: [ icon: 'integrations/github',
{ title: 'Sentry', slug: 'sentry', icon: 'integrations/sentry', component: <SentryForm /> }, component: <GithubForm />,
{ title: 'Bugsnag', slug: 'bugsnag', icon: 'integrations/bugsnag', component: <BugsnagForm /> }, },
{ title: 'Rollbar', slug: 'rollbar', icon: 'integrations/rollbar', component: <RollbarForm /> }, {
{ title: 'Elasticsearch', slug: 'elasticsearch', icon: 'integrations/elasticsearch', component: <ElasticsearchForm /> }, title: 'Slack',
{ title: 'Datadog', slug: 'datadog', icon: 'integrations/datadog', component: <DatadogForm /> }, slug: 'slack',
{ title: 'Sumo Logic', slug: 'sumologic', icon: 'integrations/sumologic', component: <SumoLogicForm /> }, category: 'Errors',
{ icon: 'integrations/slack',
title: 'Stackdriver', component: <SlackForm />,
slug: 'stackdriver', shared: true,
icon: 'integrations/google-cloud', },
component: <StackdriverForm />, {
}, title: 'MS Teams',
{ title: 'CloudWatch', slug: 'cloudwatch', icon: 'integrations/aws', component: <CloudwatchForm /> }, slug: 'msteams',
{ title: 'Newrelic', slug: 'newrelic', icon: 'integrations/newrelic', component: <NewrelicForm /> }, category: 'Errors',
], icon: 'integrations/teams',
}, component: <MSTeams />,
{ shared: true,
title: 'Plugins', },
key: 3, ],
isProject: true, },
description: {
"Reproduce issues as if they happened in your own browser. Plugins help capture your application's store, HTTP requeets, GraphQL queries, and more.", title: 'Backend Logging',
integrations: [ key: 2,
{ title: 'Redux', slug: 'redux', icon: 'integrations/redux', component: <ReduxDoc /> }, isProject: true,
{ title: 'VueX', slug: 'vuex', icon: 'integrations/vuejs', component: <VueDoc /> }, description:
{ title: 'Pinia', slug: 'pinia', icon: 'integrations/pinia', component: <PiniaDoc /> }, 'Sync your backend errors with sessions replays and see what happened front-to-back.',
{ title: 'GraphQL', slug: 'graphql', icon: 'integrations/graphql', component: <GraphQLDoc /> }, integrations: [
{ title: 'NgRx', slug: 'ngrx', icon: 'integrations/ngrx', component: <NgRxDoc /> }, { title: 'Sentry', slug: 'sentry', icon: 'integrations/sentry', component: <SentryForm /> },
{ title: 'MobX', slug: 'mobx', icon: 'integrations/mobx', component: <MobxDoc /> }, {
{ title: 'Profiler', slug: 'profiler', icon: 'integrations/openreplay', component: <ProfilerDoc /> }, title: 'Bugsnag',
{ title: 'Assist', slug: 'assist', icon: 'integrations/openreplay', component: <AssistDoc /> }, slug: 'bugsnag',
{ title: 'Zustand', slug: 'zustand', icon: '', header: '🐻', component: <ZustandDoc /> } icon: 'integrations/bugsnag',
], component: <BugsnagForm />,
}, },
{
title: 'Rollbar',
slug: 'rollbar',
icon: 'integrations/rollbar',
component: <RollbarForm />,
},
{
title: 'Elasticsearch',
slug: 'elasticsearch',
icon: 'integrations/elasticsearch',
component: <ElasticsearchForm />,
},
{
title: 'Datadog',
slug: 'datadog',
icon: 'integrations/datadog',
component: <DatadogForm />,
},
{
title: 'Sumo Logic',
slug: 'sumologic',
icon: 'integrations/sumologic',
component: <SumoLogicForm />,
},
{
title: 'Stackdriver',
slug: 'stackdriver',
icon: 'integrations/google-cloud',
component: <StackdriverForm />,
},
{
title: 'CloudWatch',
slug: 'cloudwatch',
icon: 'integrations/aws',
component: <CloudwatchForm />,
},
{
title: 'Newrelic',
slug: 'newrelic',
icon: 'integrations/newrelic',
component: <NewrelicForm />,
},
],
},
{
title: 'Plugins',
key: 3,
isProject: true,
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: 'redux', icon: 'integrations/redux', component: <ReduxDoc /> },
{ title: 'VueX', slug: 'vuex', icon: 'integrations/vuejs', component: <VueDoc /> },
{ title: 'Pinia', slug: 'pinia', icon: 'integrations/pinia', component: <PiniaDoc /> },
{
title: 'GraphQL',
slug: 'graphql',
icon: 'integrations/graphql',
component: <GraphQLDoc />,
},
{ title: 'NgRx', slug: 'ngrx', icon: 'integrations/ngrx', component: <NgRxDoc /> },
{ title: 'MobX', slug: 'mobx', icon: 'integrations/mobx', component: <MobxDoc /> },
{
title: 'Profiler',
slug: 'profiler',
icon: 'integrations/openreplay',
component: <ProfilerDoc />,
},
{
title: 'Assist',
slug: 'assist',
icon: 'integrations/openreplay',
component: <AssistDoc />,
},
{ title: 'Zustand', slug: 'zustand', icon: '', header: '🐻', component: <ZustandDoc /> },
],
},
]; ];

View file

@ -1,40 +1,45 @@
import React from 'react'; import React from 'react';
import IntegrationForm from '../IntegrationForm'; import IntegrationForm from '../IntegrationForm';
import DocLink from 'Shared/DocLink/DocLink'; import DocLink from 'Shared/DocLink/DocLink';
import { useModal } from 'App/components/Modal';
const JiraForm = (props) => ( const JiraForm = (props) => {
<div className="bg-white h-screen overflow-y-auto" style={{ width: '350px' }}> const { hideModal } = useModal();
<h3 className="p-5 text-2xl">Jira</h3> return (
<div className="p-5 border-b mb-4"> <div className="bg-white h-screen overflow-y-auto" style={{ width: '350px' }}>
<div>How to integrate Jira Cloud with OpenReplay.</div> <h3 className="p-5 text-2xl">Jira</h3>
<div className="mt-8"> <div className="p-5 border-b mb-4">
<DocLink className="mt-4" label="Integrate Jira Cloud" url="https://docs.openreplay.com/integrations/jira" /> <div>How to integrate Jira Cloud with OpenReplay.</div>
<div className="mt-8">
<DocLink className="mt-4" label="Integrate Jira Cloud" url="https://docs.openreplay.com/integrations/jira" />
</div>
</div> </div>
<IntegrationForm
{...props}
ignoreProject={true}
name="jira"
customPath="jira"
onClose={hideModal}
formFields={[
{
key: 'username',
label: 'Username',
autoFocus: true,
},
{
key: 'token',
label: 'API Token',
},
{
key: 'url',
label: 'JIRA URL',
placeholder: 'E.x. https://myjira.atlassian.net',
},
]}
/>
</div> </div>
<IntegrationForm )
{...props} };
ignoreProject={true}
name="jira"
customPath="jira"
formFields={[
{
key: 'username',
label: 'Username',
autoFocus: true,
},
{
key: 'token',
label: 'API Token',
},
{
key: 'url',
label: 'JIRA URL',
placeholder: 'E.x. https://myjira.atlassian.net',
},
]}
/>
</div>
);
JiraForm.displayName = 'JiraForm'; JiraForm.displayName = 'JiraForm';