change(ui): set up msteams for share popup and note creation
This commit is contained in:
parent
1687b5031a
commit
02027da02b
21 changed files with 328 additions and 116 deletions
|
|
@ -195,6 +195,12 @@ class Router extends React.Component {
|
|||
state: tenantId,
|
||||
});
|
||||
break;
|
||||
case '/integrations/msteams':
|
||||
client.post('integrations/msteams/add', {
|
||||
code: location.search.split('=')[1],
|
||||
state: tenantId,
|
||||
});
|
||||
break;
|
||||
}
|
||||
return <Redirect to={CLIENT_PATH} />;
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ const siteIdRequiredPaths = [
|
|||
'/metadata',
|
||||
'/integrations/sentry/events',
|
||||
'/integrations/slack/notify',
|
||||
'/integrations/msteams/notify',
|
||||
'/assignments',
|
||||
'/integration/sources',
|
||||
'/issue_types',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { Button, Form, Input, SegmentSelection, Checkbox, Message, Link, Icon } from 'UI';
|
||||
import { alertMetrics as metrics } from 'App/constants';
|
||||
import { Button, Form, Input, SegmentSelection, Checkbox, Icon } from 'UI';
|
||||
import { alertConditions as conditions } from 'App/constants';
|
||||
import { client, CLIENT_TABS } from 'App/routes';
|
||||
import { connect } from 'react-redux';
|
||||
|
|
@ -47,12 +46,12 @@ const AlertForm = (props) => {
|
|||
const {
|
||||
instance,
|
||||
slackChannels,
|
||||
msTeamsChannels,
|
||||
webhooks,
|
||||
loading,
|
||||
onDelete,
|
||||
deleting,
|
||||
triggerOptions,
|
||||
metricId,
|
||||
style = { width: '580px', height: '100vh' },
|
||||
} = props;
|
||||
const write = ({ target: { value, name } }) => props.edit({ [name]: value });
|
||||
|
|
@ -241,6 +240,14 @@ const AlertForm = (props) => {
|
|||
onClick={onChangeCheck}
|
||||
label="Slack"
|
||||
/>
|
||||
<Checkbox
|
||||
name="msteams"
|
||||
className="mr-8"
|
||||
type="checkbox"
|
||||
checked={instance.msteams}
|
||||
onClick={onChangeCheck}
|
||||
label="MS Teams"
|
||||
/>
|
||||
<Checkbox
|
||||
name="email"
|
||||
type="checkbox"
|
||||
|
|
@ -266,6 +273,20 @@ const AlertForm = (props) => {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
{instance.msteams && (
|
||||
<div className="flex items-start my-4">
|
||||
<label className="w-2/6 flex-shrink-0 font-normal pt-2">{'MS Teams'}</label>
|
||||
<div className="w-4/6">
|
||||
<DropdownChips
|
||||
fluid
|
||||
selected={instance.msTeamsInput}
|
||||
options={msTeamsChannels}
|
||||
placeholder="Select Channel"
|
||||
onChange={(selected) => props.edit({ msTeamsInput: selected })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{instance.email && (
|
||||
<div className="flex items-start my-4">
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ const AlertItem = props => {
|
|||
|
||||
const getNotifyChannel = alert => {
|
||||
let str = '';
|
||||
if (alert.msteams)
|
||||
str = 'MS Teams'
|
||||
if (alert.slack)
|
||||
str = 'Slack';
|
||||
if (alert.email)
|
||||
|
|
@ -36,7 +38,7 @@ const AlertItem = props => {
|
|||
className={cn(stl.wrapper, 'p-4 py-6 relative group cursor-pointer', { [stl.active]: active })}
|
||||
onClick={onEdit}
|
||||
id="alert-item"
|
||||
>
|
||||
>
|
||||
<AlertTypeLabel type={alert.detectionMethod} />
|
||||
<div className="capitalize font-medium">{alert.name}</div>
|
||||
<div className="mt-2 text-sm color-gray-medium">
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ interface Props {
|
|||
init: (inst: any) => void;
|
||||
update: (inst: any) => void;
|
||||
remove: (id: string) => void;
|
||||
onClose: () => void;
|
||||
instance: any;
|
||||
saving: boolean;
|
||||
errors: any;
|
||||
|
|
@ -29,7 +30,7 @@ class TeamsAddForm extends React.PureComponent<Props> {
|
|||
}
|
||||
};
|
||||
|
||||
remove = async (id) => {
|
||||
remove = async (id: string) => {
|
||||
if (
|
||||
await confirm({
|
||||
header: 'Confirm',
|
||||
|
|
@ -41,7 +42,7 @@ class TeamsAddForm extends React.PureComponent<Props> {
|
|||
}
|
||||
};
|
||||
|
||||
write = ({ target: { name, value } }) => this.props.edit({ [name]: value });
|
||||
write = ({ target: { name, value } }: { target: { name: string, value: string }}) => this.props.edit({ [name]: value });
|
||||
|
||||
render() {
|
||||
const { instance, saving, errors, onClose } = this.props;
|
||||
|
|
@ -91,8 +92,8 @@ class TeamsAddForm extends React.PureComponent<Props> {
|
|||
|
||||
{errors && (
|
||||
<div className="my-3">
|
||||
{errors.map((error) => (
|
||||
<Message visible={errors} size="mini" error key={error}>
|
||||
{errors.map((error: any) => (
|
||||
<Message visible={errors} key={error}>
|
||||
{error}
|
||||
</Message>
|
||||
))}
|
||||
|
|
@ -105,9 +106,9 @@ class TeamsAddForm extends React.PureComponent<Props> {
|
|||
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
instance: state.getIn(['slack', 'instance']),
|
||||
saving: state.getIn(['slack', 'saveRequest', 'loading']),
|
||||
errors: state.getIn(['slack', 'saveRequest', 'errors']),
|
||||
instance: state.getIn(['teams', 'instance']),
|
||||
saving: state.getIn(['teams', 'saveRequest', 'loading']),
|
||||
errors: state.getIn(['teams', 'saveRequest', 'errors']),
|
||||
}),
|
||||
{ edit, save, init, remove, update }
|
||||
)(TeamsAddForm);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ 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 TeamsAddForm from './TeamsAddForm';
|
||||
import { Button } from 'UI';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -31,7 +31,7 @@ const MSTeams = (props: Props) => {
|
|||
<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)} />
|
||||
<TeamsAddForm onClose={() => setActive(false)} />
|
||||
</div>
|
||||
)}
|
||||
<div className="shrink-0" style={{ width: '350px' }}>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ function Webhooks(props) {
|
|||
const { webhooks, loading } = props;
|
||||
const { showModal, hideModal } = useModal();
|
||||
|
||||
const noSlackWebhooks = webhooks.filter((hook) => hook.type !== 'slack');
|
||||
const noSlackWebhooks = webhooks.filter((hook) => hook.type === 'webhook');
|
||||
useEffect(() => {
|
||||
props.fetchList();
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ interface INotifyHooks {
|
|||
instance: Alert;
|
||||
onChangeCheck: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
slackChannels: Array<any>;
|
||||
msTeamsChannels: Array<any>;
|
||||
validateEmail: (value: string) => boolean;
|
||||
edit: (data: any) => void;
|
||||
hooks: Array<any>;
|
||||
|
|
@ -16,6 +17,7 @@ function NotifyHooks({
|
|||
onChangeCheck,
|
||||
slackChannels,
|
||||
validateEmail,
|
||||
msTeamsChannels,
|
||||
hooks,
|
||||
edit,
|
||||
}: INotifyHooks) {
|
||||
|
|
@ -49,7 +51,7 @@ function NotifyHooks({
|
|||
|
||||
{instance.slack && (
|
||||
<div className="flex items-start my-4">
|
||||
<label className="w-1/6 flex-shrink-0 font-normal pt-2">{'Slack'}</label>
|
||||
<label className="w-1/6 flex-shrink-0 font-normal pt-2">Slack</label>
|
||||
<div className="w-2/6">
|
||||
<DropdownChips
|
||||
fluid
|
||||
|
|
@ -63,9 +65,25 @@ function NotifyHooks({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{instance.msteams && (
|
||||
<div className="flex items-start my-4">
|
||||
<label className="w-1/6 flex-shrink-0 font-normal pt-2">MS Teams</label>
|
||||
<div className="w-2/6">
|
||||
<DropdownChips
|
||||
fluid
|
||||
selected={instance.msteamsInput}
|
||||
options={msTeamsChannels}
|
||||
placeholder="Select Channel"
|
||||
// @ts-ignore
|
||||
onChange={(selected) => edit({ msteamsInput: selected })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{instance.email && (
|
||||
<div className="flex items-start my-4">
|
||||
<label className="w-1/6 flex-shrink-0 font-normal pt-2">{'Email'}</label>
|
||||
<label className="w-1/6 flex-shrink-0 font-normal pt-2">Email</label>
|
||||
<div className="w-2/6">
|
||||
<DropdownChips
|
||||
textFiled
|
||||
|
|
@ -81,7 +99,7 @@ function NotifyHooks({
|
|||
|
||||
{instance.webhook && (
|
||||
<div className="flex items-start my-4">
|
||||
<label className="w-1/6 flex-shrink-0 font-normal pt-2">{'Webhook'}</label>
|
||||
<label className="w-1/6 flex-shrink-0 font-normal pt-2">Webhook</label>
|
||||
<div className="w-2/6">
|
||||
<DropdownChips
|
||||
fluid
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ const getNotifyChannel = (alert: Record<string, any>, webhooks: Array<any>) => {
|
|||
str = 'Slack';
|
||||
str += alert.slackInput.length > 0 ? getSlackChannels() : '';
|
||||
}
|
||||
if (alert.msteams) {
|
||||
str = 'MS Teams'
|
||||
}
|
||||
if (alert.email) {
|
||||
str += (str === '' ? '' : ' and ') + (alert.emailInput.length > 1 ? 'Emails' : 'Email');
|
||||
str += alert.emailInput.length > 0 ? ' (' + alert.emailInput.join(', ') + ')' : '';
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ import stl from './styles.module.css';
|
|||
import { useStore } from 'App/mstore';
|
||||
import { toast } from 'react-toastify';
|
||||
import { fetchList as fetchSlack } from 'Duck/integrations/slack';
|
||||
import { fetchList as fetchTeams } from 'Duck/integrations/teams';
|
||||
|
||||
import Select from 'Shared/Select';
|
||||
import { TeamBadge } from 'Shared/SessionListContainer/components/Notes'
|
||||
import { TeamBadge } from 'Shared/SessionListContainer/components/Notes';
|
||||
import { List } from 'immutable';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -22,7 +24,9 @@ interface Props {
|
|||
isEdit: string;
|
||||
editNote: WriteNote;
|
||||
slackChannels: List<Record<string, any>>;
|
||||
teamsChannels: List<Record<string, any>>;
|
||||
fetchSlack: () => void;
|
||||
fetchTeams: () => void;
|
||||
}
|
||||
|
||||
function CreateNote({
|
||||
|
|
@ -36,12 +40,18 @@ function CreateNote({
|
|||
updateNote,
|
||||
slackChannels,
|
||||
fetchSlack,
|
||||
teamsChannels,
|
||||
fetchTeams,
|
||||
}: Props) {
|
||||
const [text, setText] = React.useState('');
|
||||
const [channel, setChannel] = React.useState('');
|
||||
const [slackChannel, setSlackChannel] = React.useState('');
|
||||
const [teamsChannel, setTeamsChannel] = React.useState('');
|
||||
const [isPublic, setPublic] = React.useState(false);
|
||||
const [tag, setTag] = React.useState<iTag>(TAGS[0]);
|
||||
const [useTimestamp, setUseTs] = React.useState(true);
|
||||
const [useSlack, setSlack] = React.useState(false);
|
||||
const [useTeams, setTeams] = React.useState(false);
|
||||
|
||||
const inputRef = React.createRef<HTMLTextAreaElement>();
|
||||
const { notesStore } = useStore();
|
||||
|
||||
|
|
@ -59,6 +69,7 @@ function CreateNote({
|
|||
React.useEffect(() => {
|
||||
if (inputRef.current && isVisible) {
|
||||
fetchSlack();
|
||||
fetchTeams();
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [isVisible]);
|
||||
|
|
@ -75,17 +86,20 @@ function CreateNote({
|
|||
isPublic,
|
||||
};
|
||||
const onSuccess = (noteId: string) => {
|
||||
if (channel) {
|
||||
notesStore.sendSlackNotification(noteId, channel)
|
||||
if (slackChannel) {
|
||||
notesStore.sendSlackNotification(noteId, slackChannel);
|
||||
}
|
||||
}
|
||||
if (teamsChannel) {
|
||||
notesStore.sendMsTeamsNotification(noteId, teamsChannel);
|
||||
}
|
||||
};
|
||||
if (isEdit) {
|
||||
return notesStore
|
||||
.updateNote(editNote.noteId, note)
|
||||
.then((r) => {
|
||||
toast.success('Note updated');
|
||||
notesStore.fetchSessionNotes(sessionId).then((notes) => {
|
||||
onSuccess(editNote.noteId)
|
||||
onSuccess(editNote.noteId);
|
||||
updateNote(r);
|
||||
});
|
||||
})
|
||||
|
|
@ -103,7 +117,7 @@ function CreateNote({
|
|||
return notesStore
|
||||
.addNote(sessionId, note)
|
||||
.then((r) => {
|
||||
onSuccess(r.noteId as unknown as string)
|
||||
onSuccess(r.noteId as unknown as string);
|
||||
toast.success('Note added');
|
||||
notesStore.fetchSessionNotes(sessionId).then((notes) => {
|
||||
addNote(r);
|
||||
|
|
@ -130,27 +144,41 @@ function CreateNote({
|
|||
setTag(tag);
|
||||
};
|
||||
|
||||
const slackChannelsOptions = slackChannels.map(({ webhookId, name }) => ({
|
||||
value: webhookId,
|
||||
label: name,
|
||||
})).toJS() as unknown as { value: string, label: string }[]
|
||||
const slackChannelsOptions = slackChannels
|
||||
.map(({ webhookId, name }) => ({
|
||||
value: webhookId,
|
||||
label: name,
|
||||
}))
|
||||
.toJS() as unknown as { value: string; label: string }[];
|
||||
const teamsChannelsOptions = teamsChannels
|
||||
.map(({ webhookId, name }) => ({
|
||||
value: webhookId,
|
||||
label: name,
|
||||
}))
|
||||
.toJS() as unknown as { value: string; label: string }[];
|
||||
|
||||
slackChannelsOptions.unshift({ value: null, label: 'Share to slack?' })
|
||||
slackChannelsOptions.unshift({ value: null, label: 'Pick a channel' });
|
||||
teamsChannelsOptions.unshift({ value: null, label: 'Pick a channel' });
|
||||
|
||||
const changeChannel = ({ value, name }: { value: Record<string, string>; name: string }) => {
|
||||
setChannel(value.value);
|
||||
const changeSlackChannel = ({ value, name }: { value: Record<string, string>; name: string }) => {
|
||||
setSlackChannel(value.value);
|
||||
};
|
||||
|
||||
const changeTeamsChannel = ({ value, name }: { value: Record<string, string>; name: string }) => {
|
||||
setTeamsChannel(value.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={stl.noteTooltip}
|
||||
style={{
|
||||
top: slackChannelsOptions.length > 0 ? -310 : 255,
|
||||
width: 350,
|
||||
left: 'calc(50% - 175px)',
|
||||
display: isVisible ? 'flex' : 'none',
|
||||
flexDirection: 'column',
|
||||
gap: '1rem',
|
||||
bottom: '15vh',
|
||||
zIndex: 110,
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
|
|
@ -206,15 +234,44 @@ function CreateNote({
|
|||
</div>
|
||||
|
||||
{slackChannelsOptions.length > 0 ? (
|
||||
<div>
|
||||
<Select
|
||||
options={slackChannelsOptions}
|
||||
// @ts-ignore
|
||||
defaultValue
|
||||
// @ts-ignore
|
||||
onChange={changeChannel}
|
||||
className="mr-4"
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center cursor-pointer" onClick={() => setSlack(!useSlack)}>
|
||||
<Checkbox checked={useSlack} />
|
||||
<span className="ml-1 mr-3"> Send to slack? </span>
|
||||
</div>
|
||||
|
||||
{useSlack && (
|
||||
<div>
|
||||
<Select
|
||||
options={slackChannelsOptions}
|
||||
// @ts-ignore
|
||||
defaultValue
|
||||
// @ts-ignore
|
||||
onChange={changeSlackChannel}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{teamsChannelsOptions.length > 0 ? (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center cursor-pointer" onClick={() => setTeams(!useTeams)}>
|
||||
<Checkbox checked={useTeams} />
|
||||
<span className="ml-1 mr-3"> Send to teams? </span>
|
||||
</div>
|
||||
|
||||
{useTeams && (
|
||||
<div>
|
||||
<Select
|
||||
options={teamsChannelsOptions}
|
||||
// @ts-ignore
|
||||
defaultValue
|
||||
// @ts-ignore
|
||||
onChange={changeTeamsChannel}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
|
@ -232,19 +289,17 @@ function CreateNote({
|
|||
}
|
||||
|
||||
export default connect(
|
||||
(state) => {
|
||||
(state: any) => {
|
||||
const {
|
||||
isVisible,
|
||||
time = 0,
|
||||
isEdit,
|
||||
note: editNote,
|
||||
// @ts-ignore
|
||||
} = state.getIn(['sessions', 'createNoteTooltip']);
|
||||
// @ts-ignore
|
||||
const slackChannels = state.getIn(['slack', 'list']);
|
||||
// @ts-ignore
|
||||
const teamsChannels = state.getIn(['teams', 'list']);
|
||||
const sessionId = state.getIn(['sessions', 'current', 'sessionId']);
|
||||
return { isVisible, time, sessionId, isEdit, editNote, slackChannels };
|
||||
return { isVisible, time, sessionId, isEdit, editNote, slackChannels, teamsChannels };
|
||||
},
|
||||
{ setCreateNoteTooltip, addNote, updateNote, fetchSlack }
|
||||
{ setCreateNoteTooltip, addNote, updateNote, fetchSlack, fetchTeams }
|
||||
)(CreateNote);
|
||||
|
|
|
|||
|
|
@ -25,14 +25,13 @@
|
|||
}
|
||||
|
||||
.noteTooltip {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
padding: 1rem;
|
||||
border-radius: 0.25rem;
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
background: #F5F5F5;
|
||||
top: -35px;
|
||||
color: black;
|
||||
cursor: default;
|
||||
box-shadow: 0 4px 20px 4px rgb(0 20 60 / 10%), 0 4px 80px -8px rgb(0 20 60 / 20%);
|
||||
|
|
|
|||
|
|
@ -1,26 +1,29 @@
|
|||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { IconButton } from 'UI'
|
||||
import { Button, Icon } from 'UI'
|
||||
import { CLIENT_TABS, client as clientRoute } from 'App/routes';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
function IntegrateSlackButton({ history, tenantId }) {
|
||||
function IntegrateSlackTeamsButton({ history, tenantId }) {
|
||||
const gotoPreferencesIntegrations = () => {
|
||||
history.push(clientRoute(CLIENT_TABS.INTEGRATIONS));
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<IconButton
|
||||
className="my-auto mt-2 mb-2"
|
||||
icon="integrations/slack"
|
||||
label="Integrate Slack"
|
||||
<Button
|
||||
className="my-auto mt-2 mb-2 flex items-center gap-2"
|
||||
onClick={gotoPreferencesIntegrations}
|
||||
/>
|
||||
>
|
||||
<Icon name="integrations/slack" size={16} />
|
||||
<Icon name="integrations/teams" size={18} className="mr-2" />
|
||||
|
||||
<span>Integrate Slack/MS Teams</span>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default withRouter(connect(state => ({
|
||||
tenantId: state.getIn([ 'user', 'account', 'tenantId' ]),
|
||||
}))(IntegrateSlackButton))
|
||||
}))(IntegrateSlackTeamsButton))
|
||||
|
|
|
|||
|
|
@ -1,45 +1,68 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { toast } from 'react-toastify';
|
||||
import withRequest from 'HOCs/withRequest';
|
||||
import { Icon, Button, Popover } from 'UI';
|
||||
import styles from './sharePopup.module.css';
|
||||
import IntegrateSlackButton from '../IntegrateSlackButton/IntegrateSlackButton';
|
||||
import SessionCopyLink from './SessionCopyLink';
|
||||
import Select from 'Shared/Select';
|
||||
import cn from 'classnames';
|
||||
import { fetchList } from 'Duck/integrations/slack';
|
||||
import { fetchList as fetchSlack, sendSlackMsg } from 'Duck/integrations/slack';
|
||||
import { fetchList as fetchTeams, sendMsTeamsMsg } from 'Duck/integrations/teams';
|
||||
|
||||
@connect(
|
||||
(state) => ({
|
||||
sessionId: state.getIn(['sessions', 'current', 'sessionId']),
|
||||
channels: state.getIn(['slack', 'list']),
|
||||
msTeamsChannels: state.getIn(['teams', 'list']),
|
||||
tenantId: state.getIn(['user', 'account', 'tenantId']),
|
||||
}),
|
||||
{ fetchList }
|
||||
{ fetchSlack, fetchTeams, sendSlackMsg, sendMsTeamsMsg }
|
||||
)
|
||||
@withRequest({
|
||||
endpoint: ({ id, entity }, integrationId) =>
|
||||
`/integrations/slack/notify/${integrationId}/${entity}/${id}`,
|
||||
method: 'POST',
|
||||
})
|
||||
export default class SharePopup extends React.PureComponent {
|
||||
state = {
|
||||
comment: '',
|
||||
isOpen: false,
|
||||
channelId: this.props.channels.getIn([0, 'webhookId']),
|
||||
teamsChannel: this.props.msTeamsChannels.getIn([0, 'webhookId']),
|
||||
loading: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.channels.size === 0) {
|
||||
this.props.fetchList();
|
||||
this.props.fetchSlack();
|
||||
}
|
||||
if (this.props.msTeamsChannels.size === 0) {
|
||||
this.props.fetchTeams();
|
||||
}
|
||||
}
|
||||
|
||||
editMessage = (e) => this.setState({ comment: e.target.value });
|
||||
share = () =>
|
||||
this.props
|
||||
.request({ comment: this.state.comment }, this.state.channelId)
|
||||
.then(this.handleSuccess);
|
||||
shareToSlack = () => {
|
||||
this.setState({ loading: true }, () => {
|
||||
this.props
|
||||
.sendSlackMsg({
|
||||
integrationId: this.state.channelId,
|
||||
entity: 'sessions',
|
||||
entityId: this.props.sessionId,
|
||||
data: { comment: this.state.comment },
|
||||
})
|
||||
.then(() => this.handleSuccess('Slack'));
|
||||
});
|
||||
};
|
||||
|
||||
shareToMSTeams = () => {
|
||||
this.setState({ loading: true }, () => {
|
||||
this.props
|
||||
.sendMsTeamsMsg({
|
||||
integrationId: this.state.teamsChannel,
|
||||
entity: 'sessions',
|
||||
entityId: this.props.sessionId,
|
||||
data: { comment: this.state.comment },
|
||||
})
|
||||
.then(() => this.handleSuccess('MS Teams'));
|
||||
});
|
||||
};
|
||||
|
||||
handleOpen = () => {
|
||||
setTimeout(function () {
|
||||
|
|
@ -51,22 +74,28 @@ export default class SharePopup extends React.PureComponent {
|
|||
this.setState({ comment: '' });
|
||||
};
|
||||
|
||||
handleSuccess = () => {
|
||||
this.setState({ isOpen: false, comment: '' });
|
||||
toast.success('Sent to Slack.');
|
||||
handleSuccess = (endpoint) => {
|
||||
this.setState({ isOpen: false, comment: '', loading: false });
|
||||
toast.success(`Sent to ${endpoint}.`);
|
||||
};
|
||||
|
||||
changeChannel = ({ value }) => this.setState({ channelId: value.value });
|
||||
changeSlackChannel = ({ value }) => this.setState({ channelId: value.value });
|
||||
|
||||
changeTeamsChannel = ({ value }) => this.setState({ teamsChannel: value.value });
|
||||
|
||||
onClickHandler = () => {
|
||||
this.setState({ isOpen: true });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { trigger, loading, channels, showCopyLink = false } = this.props;
|
||||
const { comment, channelId, isOpen } = this.state;
|
||||
const { trigger, channels, msTeamsChannels, showCopyLink = false } = this.props;
|
||||
const { comment, channelId, teamsChannel, loading } = this.state;
|
||||
|
||||
const options = channels
|
||||
const slackOptions = channels
|
||||
.map(({ webhookId, name }) => ({ value: webhookId, label: name }))
|
||||
.toJS();
|
||||
|
||||
const msTeamsOptions = msTeamsChannels
|
||||
.map(({ webhookId, name }) => ({ value: webhookId, label: name }))
|
||||
.toJS();
|
||||
|
||||
|
|
@ -75,20 +104,11 @@ export default class SharePopup extends React.PureComponent {
|
|||
render={() => (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.header}>
|
||||
<div className={cn(styles.title, 'text-lg')}>Share this session link to Slack</div>
|
||||
<div className={cn(styles.title, 'text-lg')}>
|
||||
Share this session link to Slack/MS Teams
|
||||
</div>
|
||||
</div>
|
||||
{options.length === 0 ? (
|
||||
<>
|
||||
<div className={styles.body}>
|
||||
<IntegrateSlackButton />
|
||||
</div>
|
||||
{showCopyLink && (
|
||||
<div className={styles.footer}>
|
||||
<SessionCopyLink />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
{slackOptions.length > 0 || msTeamsOptions.length > 0 ? (
|
||||
<div>
|
||||
<div className={styles.body}>
|
||||
<textarea
|
||||
|
|
@ -100,30 +120,72 @@ export default class SharePopup extends React.PureComponent {
|
|||
onChange={this.editMessage}
|
||||
value={comment}
|
||||
placeholder="Add Message (Optional)"
|
||||
className="p-4"
|
||||
className="p-4 text-figmaColors-text-primary text-base"
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Select
|
||||
options={options}
|
||||
defaultValue={channelId}
|
||||
onChange={this.changeChannel}
|
||||
className="mr-4"
|
||||
/>
|
||||
<div>
|
||||
<Button onClick={this.share} variant="primary">
|
||||
<div className="flex items-center">
|
||||
<Icon name="integrations/slack-bw" size="18" marginRight="10" />
|
||||
{loading ? 'Sending...' : 'Send'}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{slackOptions.length > 0 && (
|
||||
<>
|
||||
<span>Share to slack</span>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Select
|
||||
options={slackOptions}
|
||||
defaultValue={channelId}
|
||||
onChange={this.changeSlackChannel}
|
||||
className="mr-4"
|
||||
/>
|
||||
{this.state.channelId && (
|
||||
<Button onClick={this.shareToSlack} variant="primary">
|
||||
<div className="flex items-center">
|
||||
<Icon name="integrations/slack-bw" color="white" size="18" marginRight="10" />
|
||||
{loading ? 'Sending...' : 'Send'}
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{msTeamsOptions.length > 0 && (
|
||||
<>
|
||||
<span>Share to MS Teams</span>
|
||||
<div className="flex items-center justify-between">
|
||||
<Select
|
||||
options={msTeamsOptions}
|
||||
defaultValue={teamsChannel}
|
||||
onChange={this.changeTeamsChannel}
|
||||
className="mr-4"
|
||||
/>
|
||||
{this.state.teamsChannel && (
|
||||
<Button onClick={this.shareToMSTeams} variant="primary">
|
||||
<div className="flex items-center">
|
||||
<Icon
|
||||
name="integrations/teams-white"
|
||||
color="white"
|
||||
size="18"
|
||||
marginRight="10"
|
||||
/>
|
||||
{loading ? 'Sending...' : 'Send'}
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
<SessionCopyLink />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className={styles.body}>
|
||||
<IntegrateSlackButton />
|
||||
</div>
|
||||
{showCopyLink && (
|
||||
<div className={styles.footer}>
|
||||
<SessionCopyLink />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -39,14 +39,9 @@
|
|||
}
|
||||
|
||||
.footer {
|
||||
/* display: flex; */
|
||||
/* align-items: center; */
|
||||
/* justify-content: space-between; */
|
||||
/* padding: 10px 0; */
|
||||
border-top: solid thin $gray-light;
|
||||
margin: 0 -14px;
|
||||
margin: 0 -8px;
|
||||
padding: 0 14px;
|
||||
/* border-bottom: solid thin $gray-light; */
|
||||
}
|
||||
|
||||
textarea {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const Message = ({
|
|||
inline = false,
|
||||
success = false,
|
||||
info = true,
|
||||
text,
|
||||
text = undefined,
|
||||
}) =>
|
||||
visible || !hidden ? (
|
||||
<div className={cn(styles.message, 'flex items-center')} data-inline={inline}>
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -7,6 +7,7 @@ const SAVE = new RequestTypes('slack/SAVE');
|
|||
const UPDATE = new RequestTypes('slack/UPDATE');
|
||||
const REMOVE = new RequestTypes('slack/REMOVE');
|
||||
const FETCH_LIST = new RequestTypes('slack/FETCH_LIST');
|
||||
const SEND_MSG = new RequestTypes('slack/SEND_MSG');
|
||||
const EDIT = 'slack/EDIT';
|
||||
const INIT = 'slack/INIT';
|
||||
const idKey = 'webhookId';
|
||||
|
|
@ -61,7 +62,7 @@ export function save(instance) {
|
|||
export function update(instance) {
|
||||
return {
|
||||
types: UPDATE.toArray(),
|
||||
call: (client) => client.put(`/integrations/slack/${instance.webhookId}`, instance.toData()),
|
||||
call: (client) => client.post(`/integrations/slack/${instance.webhookId}`, instance.toData()),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -86,3 +87,12 @@ export function remove(id) {
|
|||
id,
|
||||
};
|
||||
}
|
||||
|
||||
// https://api.openreplay.com/5587/integrations/slack/notify/315/sessions/7856803626558104
|
||||
//
|
||||
export function sendSlackMsg({ integrationId, entity, entityId, data }) {
|
||||
return {
|
||||
types: SEND_MSG.toArray(),
|
||||
call: (client) => client.post(`/integrations/slack/notify/${integrationId}/${entity}/${entityId}`, data)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ 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 SEND_MSG = new RequestTypes('msteams/SEND_MSG');
|
||||
|
||||
const EDIT = 'msteams/EDIT';
|
||||
const INIT = 'msteams/INIT';
|
||||
const idKey = 'webhookId';
|
||||
|
|
@ -86,3 +88,12 @@ export function remove(id) {
|
|||
id,
|
||||
};
|
||||
}
|
||||
|
||||
// https://api.openreplay.com/5587/integrations/msteams/notify/315/sessions/7856803626558104
|
||||
//
|
||||
export function sendMsTeamsMsg({ integrationId, entity, entityId, data }) {
|
||||
return {
|
||||
types: SEND_MSG.toArray(),
|
||||
call: (client) => client.post(`/integrations/msteams/notify/${integrationId}/${entity}/${entityId}`, data)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,4 +130,13 @@ export default class NotesStore {
|
|||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
async sendMsTeamsNotification(noteId: string, webhook: string) {
|
||||
try {
|
||||
const resp = await notesService.sendMsTeamsNotification(noteId, webhook)
|
||||
return resp
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,4 +119,15 @@ export default class NotesService {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
sendMsTeamsNotification(noteId: string, webhook: string) {
|
||||
return this.client.get(`/notes/${noteId}/msteams/${webhook}`)
|
||||
.then(r => {
|
||||
if (r.ok) {
|
||||
return r.json().then(r => r.data)
|
||||
} else {
|
||||
throw new Error('Error sending slack notif: ' + r.status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
frontend/app/svg/icons/integrations/teams-white.svg
Normal file
4
frontend/app/svg/icons/integrations/teams-white.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path d="M9.186 4.797a2.42 2.42 0 1 0-2.86-2.448h1.178c.929 0 1.682.753 1.682 1.682v.766Zm-4.295 7.738h2.613c.929 0 1.682-.753 1.682-1.682V5.58h2.783a.7.7 0 0 1 .682.716v4.294a4.197 4.197 0 0 1-4.093 4.293c-1.618-.04-3-.99-3.667-2.35Zm10.737-9.372a1.674 1.674 0 1 1-3.349 0 1.674 1.674 0 0 1 3.349 0Zm-2.238 9.488c-.04 0-.08 0-.12-.002a5.19 5.19 0 0 0 .381-2.07V6.306a1.692 1.692 0 0 0-.15-.725h1.792c.39 0 .707.317.707.707v3.765a2.598 2.598 0 0 1-2.598 2.598h-.013Z"/>
|
||||
<path d="M.682 3.349h6.822c.377 0 .682.305.682.682v6.822a.682.682 0 0 1-.682.682H.682A.682.682 0 0 1 0 10.853V4.03c0-.377.305-.682.682-.682Zm5.206 2.596v-.72h-3.59v.72h1.357V9.66h.87V5.945h1.363Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 741 B |
Loading…
Add table
Reference in a new issue