change(ui): create ms teams components
This commit is contained in:
parent
f784ba5351
commit
1687b5031a
9 changed files with 402 additions and 2 deletions
44
frontend/app/assets/integrations/teams.svg
Normal file
44
frontend/app/assets/integrations/teams.svg
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-334.32495 -518.3335 2897.4829 3110.001">
|
||||
<path
|
||||
d="M1554.637 777.5h575.713c54.391 0 98.483 44.092 98.483 98.483v524.398c0 199.901-162.051 361.952-361.952 361.952h-1.711c-199.901.028-361.975-162-362.004-361.901V828.971c.001-28.427 23.045-51.471 51.471-51.471z"
|
||||
fill="#5059C9" />
|
||||
<circle r="233.25" cy="440.583" cx="1943.75" fill="#5059C9" />
|
||||
<circle r="336.917" cy="336.917" cx="1218.083" fill="#7B83EB" />
|
||||
<path
|
||||
d="M1667.323 777.5H717.01c-53.743 1.33-96.257 45.931-95.01 99.676v598.105c-7.505 322.519 247.657 590.16 570.167 598.053 322.51-7.893 577.671-275.534 570.167-598.053V877.176c1.245-53.745-41.268-98.346-95.011-99.676z"
|
||||
fill="#7B83EB" />
|
||||
<path
|
||||
d="M1244 777.5v838.145c-.258 38.435-23.549 72.964-59.09 87.598a91.856 91.856 0 01-35.765 7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833a631.287 631.287 0 01-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z"
|
||||
opacity=".1" />
|
||||
<path
|
||||
d="M1192.167 777.5v889.978a91.802 91.802 0 01-7.257 35.765c-14.634 35.541-49.163 58.833-87.598 59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833-7.257-17.623-12.958-34.21-18.142-51.833a631.282 631.282 0 01-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z"
|
||||
opacity=".2" />
|
||||
<path
|
||||
d="M1192.167 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855h-447.84A631.282 631.282 0 01622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z"
|
||||
opacity=".2" />
|
||||
<path
|
||||
d="M1140.333 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855H649.472A631.282 631.282 0 01622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z"
|
||||
opacity=".2" />
|
||||
<path
|
||||
d="M1244 509.522v163.275c-8.812.518-17.105 1.037-25.917 1.037-8.812 0-17.105-.518-25.917-1.037a284.472 284.472 0 01-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003a288.02 288.02 0 01-16.587-51.833h258.648c52.305.198 94.657 42.549 94.856 94.854z"
|
||||
opacity=".1" />
|
||||
<path
|
||||
d="M1192.167 561.355v111.442a284.472 284.472 0 01-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z"
|
||||
opacity=".2" />
|
||||
<path
|
||||
d="M1192.167 561.355v111.442a284.472 284.472 0 01-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z"
|
||||
opacity=".2" />
|
||||
<path
|
||||
d="M1140.333 561.355v103.148c-104.963-24.857-191.679-98.469-233.25-198.003h138.395c52.305.199 94.656 42.551 94.855 94.855z"
|
||||
opacity=".2" />
|
||||
<linearGradient gradientTransform="matrix(1 0 0 -1 0 2075.333)" y2="394.261" x2="942.234" y1="1683.073" x1="198.099"
|
||||
gradientUnits="userSpaceOnUse" id="a">
|
||||
<stop offset="0" stop-color="#5a62c3" />
|
||||
<stop offset=".5" stop-color="#4d55bd" />
|
||||
<stop offset="1" stop-color="#3940ab" />
|
||||
</linearGradient>
|
||||
<path
|
||||
d="M95.01 466.5h950.312c52.473 0 95.01 42.538 95.01 95.01v950.312c0 52.473-42.538 95.01-95.01 95.01H95.01c-52.473 0-95.01-42.538-95.01-95.01V561.51c0-52.472 42.538-95.01 95.01-95.01z"
|
||||
fill="url(#a)" />
|
||||
<path d="M820.211 828.193h-189.97v517.297h-121.03V828.193H320.123V727.844h500.088z" fill="#FFF" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
|
|
@ -26,11 +26,12 @@ import FetchDoc from './FetchDoc';
|
|||
import ProfilerDoc from './ProfilerDoc';
|
||||
import AxiosDoc from './AxiosDoc';
|
||||
import AssistDoc from './AssistDoc';
|
||||
import { PageTitle, Loader } from 'UI';
|
||||
import { PageTitle } from 'UI';
|
||||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
import PiniaDoc from './PiniaDoc'
|
||||
import ZustandDoc from './ZustandDoc'
|
||||
import MSTeams from './Teams'
|
||||
|
||||
interface Props {
|
||||
fetch: (name: string, siteId: string) => void;
|
||||
|
|
@ -133,6 +134,7 @@ const integrations = [
|
|||
{ title: 'Jira', slug: 'jira', category: 'Errors', icon: 'integrations/jira', component: <JiraForm /> },
|
||||
{ title: 'Github', slug: 'github', category: 'Errors', icon: 'integrations/github', component: <GithubForm /> },
|
||||
{ title: 'Slack', category: 'Errors', icon: 'integrations/slack', component: <SlackForm /> },
|
||||
{ title: 'MS Teams', category: 'Errors', icon: 'integrations/teams', component: <MSTeams /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { edit, save, init, update, remove } from 'Duck/integrations/teams';
|
||||
import { Form, Input, Button, Message } from 'UI';
|
||||
import { confirm } from 'UI';
|
||||
|
||||
interface Props {
|
||||
edit: (inst: any) => void;
|
||||
save: (inst: any) => void;
|
||||
init: (inst: any) => void;
|
||||
update: (inst: any) => void;
|
||||
remove: (id: string) => void;
|
||||
instance: any;
|
||||
saving: boolean;
|
||||
errors: any;
|
||||
}
|
||||
|
||||
class TeamsAddForm extends React.PureComponent<Props> {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
write = ({ target: { name, value } }) => this.props.edit({ [name]: value });
|
||||
|
||||
render() {
|
||||
const { instance, saving, errors, onClose } = this.props;
|
||||
return (
|
||||
<div className="p-5" style={{ minWidth: '300px' }}>
|
||||
<Form>
|
||||
<Form.Field>
|
||||
<label>Name</label>
|
||||
<Input
|
||||
name="name"
|
||||
value={instance.name}
|
||||
onChange={this.write}
|
||||
placeholder="Enter any name"
|
||||
type="text"
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<label>URL</label>
|
||||
<Input
|
||||
name="endpoint"
|
||||
value={instance.endpoint}
|
||||
onChange={this.write}
|
||||
placeholder="Slack webhook URL"
|
||||
type="text"
|
||||
/>
|
||||
</Form.Field>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex">
|
||||
<Button
|
||||
onClick={this.save}
|
||||
disabled={!instance.validate()}
|
||||
loading={saving}
|
||||
variant="primary"
|
||||
className="float-left mr-2"
|
||||
>
|
||||
{instance.exists() ? 'Update' : 'Add'}
|
||||
</Button>
|
||||
|
||||
<Button onClick={onClose}>{'Cancel'}</Button>
|
||||
</div>
|
||||
|
||||
<Button onClick={() => this.remove(instance.webhookId)} disabled={!instance.exists()}>
|
||||
{'Delete'}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
{errors && (
|
||||
<div className="my-3">
|
||||
{errors.map((error) => (
|
||||
<Message visible={errors} size="mini" error key={error}>
|
||||
{error}
|
||||
</Message>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
instance: state.getIn(['slack', 'instance']),
|
||||
saving: state.getIn(['slack', 'saveRequest', 'loading']),
|
||||
errors: state.getIn(['slack', 'saveRequest', 'errors']),
|
||||
}),
|
||||
{ edit, save, init, remove, update }
|
||||
)(TeamsAddForm);
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { NoContent } from 'UI';
|
||||
import { remove, edit, init } from 'Duck/integrations/teams';
|
||||
import DocLink from 'Shared/DocLink/DocLink';
|
||||
|
||||
function TeamsChannelList(props: { list: any, edit: (inst: any) => any, onEdit: () => void }) {
|
||||
const { list } = props;
|
||||
|
||||
const onEdit = (instance: Record<string, any>) => {
|
||||
props.edit(instance);
|
||||
props.onEdit();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-6">
|
||||
<NoContent
|
||||
title={
|
||||
<div className="p-5 mb-4">
|
||||
<div className="text-base text-left">
|
||||
Integrate MS Teams with OpenReplay and share insights with the rest of the team, directly from the recording page.
|
||||
</div>
|
||||
<DocLink className="mt-4 text-base" label="Integrate Slack" url="https://docs.openreplay.com/integrations/slack" />
|
||||
</div>
|
||||
}
|
||||
size="small"
|
||||
show={list.size === 0}
|
||||
>
|
||||
{list.map((c: any) => (
|
||||
<div
|
||||
key={c.webhookId}
|
||||
className="border-t px-5 py-2 flex items-center justify-between cursor-pointer hover:bg-active-blue"
|
||||
onClick={() => onEdit(c)}
|
||||
>
|
||||
<div className="flex-grow-0" style={{ maxWidth: '90%' }}>
|
||||
<div>{c.name}</div>
|
||||
<div className="truncate test-xs color-gray-medium">{c.endpoint}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</NoContent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
list: state.getIn(['teams', 'list']),
|
||||
}),
|
||||
{ remove, edit, init }
|
||||
)(TeamsChannelList);
|
||||
55
frontend/app/components/Client/Integrations/Teams/index.tsx
Normal file
55
frontend/app/components/Client/Integrations/Teams/index.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import TeamsChannelList from './TeamsChannelList';
|
||||
import { fetchList, init } from 'Duck/integrations/teams';
|
||||
import { connect } from 'react-redux';
|
||||
import SlackAddForm from './SlackAddForm';
|
||||
import { Button } from 'UI';
|
||||
|
||||
interface Props {
|
||||
onEdit?: (integration: any) => void;
|
||||
istance: any;
|
||||
fetchList: any;
|
||||
init: any;
|
||||
}
|
||||
const MSTeams = (props: Props) => {
|
||||
const [active, setActive] = React.useState(false);
|
||||
|
||||
const onEdit = () => {
|
||||
setActive(true);
|
||||
};
|
||||
|
||||
const onNew = () => {
|
||||
setActive(true);
|
||||
props.init({});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
props.fetchList();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="bg-white h-screen overflow-y-auto flex items-start" style={{ width: active ? '700px' : '350px' }}>
|
||||
{active && (
|
||||
<div className="border-r h-full" style={{ width: '350px' }}>
|
||||
<SlackAddForm onClose={() => setActive(false)} />
|
||||
</div>
|
||||
)}
|
||||
<div className="shrink-0" style={{ width: '350px' }}>
|
||||
<div className="flex items-center p-5">
|
||||
<h3 className="text-2xl mr-3">Microsoft Teams</h3>
|
||||
<Button rounded={true} icon="plus" variant="outline" onClick={onNew}/>
|
||||
</div>
|
||||
<TeamsChannelList onEdit={onEdit} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
MSTeams.displayName = 'MSTeams';
|
||||
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
istance: state.getIn(['teams', 'instance']),
|
||||
}),
|
||||
{ fetchList, init }
|
||||
)(MSTeams);
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -12,6 +12,7 @@ import GithubConfig from 'Types/integrations/githubConfig';
|
|||
import IssueTracker from 'Types/integrations/issueTracker';
|
||||
import slack from './slack';
|
||||
import integrations from './integrations';
|
||||
import teams from './teams'
|
||||
|
||||
import { createIntegrationReducer } from './reducer';
|
||||
|
||||
|
|
@ -29,6 +30,7 @@ export default {
|
|||
github: createIntegrationReducer('github', GithubConfig),
|
||||
issues: createIntegrationReducer('issues', IssueTracker),
|
||||
slack,
|
||||
teams,
|
||||
integrations,
|
||||
};
|
||||
|
||||
|
|
|
|||
88
frontend/app/duck/integrations/teams.js
Normal file
88
frontend/app/duck/integrations/teams.js
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import { Map, List } from 'immutable';
|
||||
import withRequestState, { RequestTypes } from 'Duck/requestStateCreator';
|
||||
import Config from 'Types/integrations/slackConfig';
|
||||
import { createItemInListUpdater } from '../funcTools/tools';
|
||||
|
||||
const SAVE = new RequestTypes('msteams/SAVE');
|
||||
const UPDATE = new RequestTypes('msteams/UPDATE');
|
||||
const REMOVE = new RequestTypes('msteams/REMOVE');
|
||||
const FETCH_LIST = new RequestTypes('msteams/FETCH_LIST');
|
||||
const EDIT = 'msteams/EDIT';
|
||||
const INIT = 'msteams/INIT';
|
||||
const idKey = 'webhookId';
|
||||
const itemInListUpdater = createItemInListUpdater(idKey);
|
||||
|
||||
const initialState = Map({
|
||||
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;
|
||||
};
|
||||
|
||||
export default withRequestState(
|
||||
{
|
||||
fetchRequest: FETCH_LIST,
|
||||
saveRequest: SAVE,
|
||||
removeRequest: REMOVE,
|
||||
},
|
||||
reducer
|
||||
);
|
||||
|
||||
export function fetchList() {
|
||||
return {
|
||||
types: FETCH_LIST.toArray(),
|
||||
call: (client) => client.get('/integrations/msteams/channels'),
|
||||
};
|
||||
}
|
||||
|
||||
export function save(instance) {
|
||||
return {
|
||||
types: SAVE.toArray(),
|
||||
call: (client) => client.post(`/integrations/msteams`, instance.toData()),
|
||||
};
|
||||
}
|
||||
|
||||
export function update(instance) {
|
||||
return {
|
||||
types: UPDATE.toArray(),
|
||||
call: (client) => client.put(`/integrations/msteams/${instance.webhookId}`, instance.toData()),
|
||||
};
|
||||
}
|
||||
|
||||
export function edit(instance) {
|
||||
return {
|
||||
type: EDIT,
|
||||
instance,
|
||||
};
|
||||
}
|
||||
|
||||
export function init(instance) {
|
||||
return {
|
||||
type: INIT,
|
||||
instance,
|
||||
};
|
||||
}
|
||||
|
||||
export function remove(id) {
|
||||
return {
|
||||
types: REMOVE.toArray(),
|
||||
call: (client) => client.delete(`/integrations/msteams/${id}`),
|
||||
id,
|
||||
};
|
||||
}
|
||||
44
frontend/app/svg/icons/integrations/teams.svg
Normal file
44
frontend/app/svg/icons/integrations/teams.svg
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-334.32495 -518.3335 2897.4829 3110.001">
|
||||
<path
|
||||
d="M1554.637 777.5h575.713c54.391 0 98.483 44.092 98.483 98.483v524.398c0 199.901-162.051 361.952-361.952 361.952h-1.711c-199.901.028-361.975-162-362.004-361.901V828.971c.001-28.427 23.045-51.471 51.471-51.471z"
|
||||
fill="#5059C9" />
|
||||
<circle r="233.25" cy="440.583" cx="1943.75" fill="#5059C9" />
|
||||
<circle r="336.917" cy="336.917" cx="1218.083" fill="#7B83EB" />
|
||||
<path
|
||||
d="M1667.323 777.5H717.01c-53.743 1.33-96.257 45.931-95.01 99.676v598.105c-7.505 322.519 247.657 590.16 570.167 598.053 322.51-7.893 577.671-275.534 570.167-598.053V877.176c1.245-53.745-41.268-98.346-95.011-99.676z"
|
||||
fill="#7B83EB" />
|
||||
<path
|
||||
d="M1244 777.5v838.145c-.258 38.435-23.549 72.964-59.09 87.598a91.856 91.856 0 01-35.765 7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833a631.287 631.287 0 01-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z"
|
||||
opacity=".1" />
|
||||
<path
|
||||
d="M1192.167 777.5v889.978a91.802 91.802 0 01-7.257 35.765c-14.634 35.541-49.163 58.833-87.598 59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833-7.257-17.623-12.958-34.21-18.142-51.833a631.282 631.282 0 01-27.472-183.49V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z"
|
||||
opacity=".2" />
|
||||
<path
|
||||
d="M1192.167 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855h-447.84A631.282 631.282 0 01622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z"
|
||||
opacity=".2" />
|
||||
<path
|
||||
d="M1140.333 777.5v786.312c-.395 52.223-42.632 94.46-94.855 94.855H649.472A631.282 631.282 0 01622 1475.177V877.02c-1.246-53.659 41.198-98.19 94.855-99.52z"
|
||||
opacity=".2" />
|
||||
<path
|
||||
d="M1244 509.522v163.275c-8.812.518-17.105 1.037-25.917 1.037-8.812 0-17.105-.518-25.917-1.037a284.472 284.472 0 01-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003a288.02 288.02 0 01-16.587-51.833h258.648c52.305.198 94.657 42.549 94.856 94.854z"
|
||||
opacity=".1" />
|
||||
<path
|
||||
d="M1192.167 561.355v111.442a284.472 284.472 0 01-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z"
|
||||
opacity=".2" />
|
||||
<path
|
||||
d="M1192.167 561.355v111.442a284.472 284.472 0 01-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z"
|
||||
opacity=".2" />
|
||||
<path
|
||||
d="M1140.333 561.355v103.148c-104.963-24.857-191.679-98.469-233.25-198.003h138.395c52.305.199 94.656 42.551 94.855 94.855z"
|
||||
opacity=".2" />
|
||||
<linearGradient gradientTransform="matrix(1 0 0 -1 0 2075.333)" y2="394.261" x2="942.234" y1="1683.073" x1="198.099"
|
||||
gradientUnits="userSpaceOnUse" id="a">
|
||||
<stop offset="0" stop-color="#5a62c3" />
|
||||
<stop offset=".5" stop-color="#4d55bd" />
|
||||
<stop offset="1" stop-color="#3940ab" />
|
||||
</linearGradient>
|
||||
<path
|
||||
d="M95.01 466.5h950.312c52.473 0 95.01 42.538 95.01 95.01v950.312c0 52.473-42.538 95.01-95.01 95.01H95.01c-52.473 0-95.01-42.538-95.01-95.01V561.51c0-52.472 42.538-95.01 95.01-95.01z"
|
||||
fill="url(#a)" />
|
||||
<path d="M820.211 828.193h-189.97v517.297h-121.03V828.193H320.123V727.844h500.088z" fill="#FFF" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
Loading…
Add table
Reference in a new issue