-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
336 additions
and
18 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,49 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { react2angular } from 'react2angular'; | ||
import { currentUser, clientConfig } from '@/services/auth'; | ||
import cx from 'classnames'; | ||
import { clientConfig, currentUser } from '@/services/auth'; | ||
import Tooltip from 'antd/lib/tooltip'; | ||
import Alert from 'antd/lib/alert'; | ||
import { HelpTrigger } from '@/components/HelpTrigger'; | ||
|
||
export function EmailSettingsWarning({ featureName }) { | ||
return (clientConfig.mailSettingsMissing && currentUser.isAdmin) ? ( | ||
<p className="alert alert-danger"> | ||
{`It looks like your mail server isn't configured. Make sure to configure it for the ${featureName} to work.`} | ||
</p> | ||
) : null; | ||
export default function EmailSettingsWarning({ featureName, className, mode, adminOnly }) { | ||
if (!clientConfig.mailSettingsMissing) { | ||
return null; | ||
} | ||
|
||
if (adminOnly && !currentUser.isAdmin) { | ||
return null; | ||
} | ||
|
||
const message = ( | ||
<span> | ||
Your mail server isn't configured correctly, and is needed for {featureName} to work.{' '} | ||
<HelpTrigger type="MAIL_CONFIG" className="f-inherit" /> | ||
</span> | ||
); | ||
|
||
if (mode === 'icon') { | ||
return ( | ||
<Tooltip title={message}> | ||
<i className={cx('fa fa-exclamation-triangle', className)} /> | ||
</Tooltip> | ||
); | ||
} | ||
|
||
return ( | ||
<Alert message={message} type="error" className={className} /> | ||
); | ||
} | ||
|
||
EmailSettingsWarning.propTypes = { | ||
featureName: PropTypes.string.isRequired, | ||
className: PropTypes.string, | ||
mode: PropTypes.oneOf(['alert', 'icon']), | ||
adminOnly: PropTypes.bool, | ||
}; | ||
|
||
export default function init(ngModule) { | ||
ngModule.component('emailSettingsWarning', react2angular(EmailSettingsWarning)); | ||
} | ||
|
||
init.init = true; | ||
EmailSettingsWarning.defaultProps = { | ||
className: null, | ||
mode: 'alert', | ||
adminOnly: false, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
210 changes: 210 additions & 0 deletions
210
client/app/pages/alert/components/AlertDestinations.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { without, find, isEmpty, includes, map } from 'lodash'; | ||
|
||
import SelectItemsDialog from '@/components/SelectItemsDialog'; | ||
import { Destination as DestinationType, UserProfile as UserType } from '@/components/proptypes'; | ||
|
||
import { Destination as DestinationService, IMG_ROOT } from '@/services/destination'; | ||
import { AlertSubscription } from '@/services/alert-subscription'; | ||
import { $q } from '@/services/ng'; | ||
import { clientConfig, currentUser } from '@/services/auth'; | ||
import notification from '@/services/notification'; | ||
import ListItemAddon from '@/components/groups/ListItemAddon'; | ||
import EmailSettingsWarning from '@/components/EmailSettingsWarning'; | ||
|
||
import Icon from 'antd/lib/icon'; | ||
import Tooltip from 'antd/lib/tooltip'; | ||
import Switch from 'antd/lib/switch'; | ||
import Button from 'antd/lib/button'; | ||
|
||
import './AlertDestinations.less'; | ||
|
||
const USER_EMAIL_DEST_ID = -1; | ||
|
||
function normalizeSub(sub) { | ||
if (!sub.destination) { | ||
sub.destination = { | ||
id: USER_EMAIL_DEST_ID, | ||
name: sub.user.email, | ||
icon: 'DEPRECATED', | ||
type: 'email', | ||
}; | ||
} | ||
return sub; | ||
} | ||
|
||
function ListItem({ destination: { name, type }, user, unsubscribe }) { | ||
const canUnsubscribe = currentUser.isAdmin || currentUser.id === user.id; | ||
|
||
return ( | ||
<li className="destination-wrapper"> | ||
<img src={`${IMG_ROOT}/${type}.png`} className="destination-icon" alt={name} /> | ||
<span className="flex-fill">{name}</span> | ||
{type === 'email' && <EmailSettingsWarning className="destination-warning" featureName="alert emails" mode="icon" />} | ||
{canUnsubscribe && ( | ||
<Tooltip title="Remove" mouseEnterDelay={0.5}> | ||
<Icon type="close" className="remove-button" onClick={unsubscribe} /> | ||
</Tooltip> | ||
)} | ||
</li> | ||
); | ||
} | ||
|
||
ListItem.propTypes = { | ||
destination: DestinationType.isRequired, | ||
user: UserType.isRequired, | ||
unsubscribe: PropTypes.func.isRequired, | ||
}; | ||
|
||
|
||
export default class AlertDestinations extends React.Component { | ||
static propTypes = { | ||
alertId: PropTypes.number.isRequired, | ||
} | ||
|
||
state = { | ||
dests: [], | ||
subs: null, | ||
} | ||
|
||
componentDidMount() { | ||
const { alertId } = this.props; | ||
$q | ||
.all([ | ||
DestinationService.query().$promise, // get all destinations | ||
AlertSubscription.query({ alertId }).$promise, // get subcriptions per alert | ||
]) | ||
.then(([dests, subs]) => { | ||
subs = subs.map(normalizeSub); | ||
this.setState({ dests, subs }); | ||
}); | ||
} | ||
|
||
showAddAlertSubDialog = () => { | ||
const { dests, subs } = this.state; | ||
|
||
SelectItemsDialog.showModal({ | ||
dialogTitle: 'Add Existing Alert Destinations', | ||
selectedItemsTitle: 'Pending Destinations', | ||
inputPlaceholder: 'Search destinations...', | ||
searchItems: (searchTerm) => { | ||
searchTerm = searchTerm.toLowerCase(); | ||
const filtered = dests.filter(d => isEmpty(searchTerm) || includes(d.name.toLowerCase(), searchTerm)); | ||
return Promise.resolve(filtered); | ||
}, | ||
renderItem: (item, { isSelected }) => { | ||
const alreadyInGroup = !!find(subs, s => s.destination.id === item.id); | ||
|
||
return { | ||
content: ( | ||
<div className="destination-wrapper"> | ||
<img src={`${IMG_ROOT}/${item.type}.png`} className="destination-icon" alt={name} /> | ||
<span className="flex-fill">{item.name}</span> | ||
<ListItemAddon isSelected={isSelected} alreadyInGroup={alreadyInGroup} /> | ||
</div> | ||
), | ||
isDisabled: alreadyInGroup, | ||
className: isSelected || alreadyInGroup ? 'selected' : '', | ||
}; | ||
}, | ||
renderStagedItem: item => ({ | ||
content: ( | ||
<div className="destination-wrapper"> | ||
<img src={`${IMG_ROOT}/${item.type}.png`} className="destination-icon" alt={name} /> | ||
<span className="flex-fill">{item.name}</span> | ||
<ListItemAddon isStaged /> | ||
</div> | ||
), | ||
}), | ||
save: (items) => { | ||
const promises = map(items, item => this.subscribe(item)); | ||
return Promise.all(promises).then(() => { | ||
notification.success('Subscribed.'); | ||
}).catch(() => { | ||
notification.error('Failed saving subscription.'); | ||
}); | ||
}, | ||
}); | ||
} | ||
|
||
onUserEmailToggle = (sub) => { | ||
if (sub) { | ||
this.unsubscribe(sub); | ||
} else { | ||
this.subscribe(); | ||
} | ||
} | ||
|
||
subscribe = (dest) => { | ||
const { alertId } = this.props; | ||
|
||
const sub = new AlertSubscription({ alert_id: alertId }); | ||
if (dest) { | ||
sub.destination_id = dest.id; | ||
} | ||
|
||
return sub.$save(() => { | ||
const { subs } = this.state; | ||
this.setState({ | ||
subs: [...subs, normalizeSub(sub)], | ||
}); | ||
}); | ||
} | ||
|
||
unsubscribe = (sub) => { | ||
sub.$delete( | ||
() => { | ||
// not showing subscribe notification cause it's redundant here | ||
const { subs } = this.state; | ||
this.setState({ | ||
subs: without(subs, sub), | ||
}); | ||
}, | ||
() => { | ||
notification.error('Failed unsubscribing.'); | ||
}, | ||
); | ||
}; | ||
|
||
render() { | ||
if (!this.props.alertId) { | ||
return null; | ||
} | ||
|
||
const { subs } = this.state; | ||
const currentUserEmailSub = find(subs, { | ||
destination: { id: USER_EMAIL_DEST_ID }, | ||
user: { id: currentUser.id }, | ||
}); | ||
const filteredSubs = without(subs, currentUserEmailSub); | ||
const { mailSettingsMissing } = clientConfig; | ||
|
||
return ( | ||
<div className="alert-destinations"> | ||
<Tooltip title="Click to add an existing "Alert Destination"" mouseEnterDelay={0.5}> | ||
<Button type="primary" size="small" className="add-button" onClick={this.showAddAlertSubDialog}> | ||
<i className="fa fa-plus f-12 m-r-5" /> Add | ||
</Button> | ||
</Tooltip> | ||
<ul> | ||
<li className="destination-wrapper"> | ||
<i className="destination-icon fa fa-envelope" /> | ||
<span className="flex-fill">{ currentUser.email }</span> | ||
<EmailSettingsWarning className="destination-warning" featureName="alert emails" mode="icon" /> | ||
{!mailSettingsMissing && ( | ||
<Switch | ||
size="small" | ||
className="toggle-button" | ||
checked={!!currentUserEmailSub} | ||
loading={!subs} | ||
onChange={() => this.onUserEmailToggle(currentUserEmailSub)} | ||
/> | ||
)} | ||
</li> | ||
{filteredSubs.map(s => <ListItem key={s.id} unsubscribe={() => this.unsubscribe(s)} {...s} />)} | ||
</ul> | ||
</div> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
.alert-destinations { | ||
ul { | ||
list-style: none; | ||
padding: 0; | ||
margin-top: 15px; | ||
|
||
li { | ||
color: rgba(0, 0, 0, 0.85); | ||
height: 46px; | ||
border-bottom: 1px solid #e8e8e8; | ||
|
||
.remove-button { | ||
cursor: pointer; | ||
height: 40px; | ||
width: 40px; | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
} | ||
|
||
.toggle-button { | ||
margin: 0 7px; | ||
} | ||
|
||
.destination-warning { | ||
color: #f5222d; | ||
|
||
&:last-child { | ||
margin-right: 14px; | ||
} | ||
} | ||
} | ||
} | ||
|
||
.add-button { | ||
position: absolute; | ||
right: 14px; | ||
top: 9px; | ||
} | ||
} | ||
|
||
.destination-wrapper { | ||
padding-left: 8px; | ||
display: flex; | ||
align-items: center; | ||
min-height: 38px; | ||
width: 100%; | ||
|
||
.destination-icon { | ||
height: 25px; | ||
width: 25px; | ||
margin: 2px 5px 0 0; | ||
filter: grayscale(1); | ||
|
||
&.fa { | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
font-size: 12px; | ||
} | ||
} | ||
} |
Oops, something went wrong.