Merge pull request #866 from mattermost/mm-12205-session-expiry-notification

MM-12205: session expiry notification
This commit is contained in:
Jesse Hallam 2018-10-12 10:05:39 -04:00 committed by GitHub
commit 4b2684baa5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 85 additions and 43 deletions

View file

@ -39,6 +39,7 @@ export default class MainPage extends React.Component {
this.state = { this.state = {
key, key,
sessionsExpired: new Array(this.props.teams.length),
unreadCounts: new Array(this.props.teams.length), unreadCounts: new Array(this.props.teams.length),
mentionCounts: new Array(this.props.teams.length), mentionCounts: new Array(this.props.teams.length),
unreadAtActive: new Array(this.props.teams.length), unreadAtActive: new Array(this.props.teams.length),
@ -56,8 +57,6 @@ export default class MainPage extends React.Component {
this.handleOnTeamFocused = this.handleOnTeamFocused.bind(this); this.handleOnTeamFocused = this.handleOnTeamFocused.bind(this);
this.handleSelect = this.handleSelect.bind(this); this.handleSelect = this.handleSelect.bind(this);
this.handleTargetURLChange = this.handleTargetURLChange.bind(this); this.handleTargetURLChange = this.handleTargetURLChange.bind(this);
this.handleUnreadCountChange = this.handleUnreadCountChange.bind(this);
this.handleUnreadCountTotalChange = this.handleUnreadCountTotalChange.bind(this);
this.inputBlur = this.inputBlur.bind(this); this.inputBlur = this.inputBlur.bind(this);
this.markReadAtActive = this.markReadAtActive.bind(this); this.markReadAtActive = this.markReadAtActive.bind(this);
} }
@ -173,11 +172,13 @@ export default class MainPage extends React.Component {
this.handleOnTeamFocused(newKey); this.handleOnTeamFocused(newKey);
} }
handleUnreadCountChange(index, unreadCount, mentionCount, isUnread, isMentioned) { handleBadgeChange = (index, sessionExpired, unreadCount, mentionCount, isUnread, isMentioned) => {
const sessionsExpired = this.state.sessionsExpired;
const unreadCounts = this.state.unreadCounts; const unreadCounts = this.state.unreadCounts;
const mentionCounts = this.state.mentionCounts; const mentionCounts = this.state.mentionCounts;
const unreadAtActive = this.state.unreadAtActive; const unreadAtActive = this.state.unreadAtActive;
const mentionAtActiveCounts = this.state.mentionAtActiveCounts; const mentionAtActiveCounts = this.state.mentionAtActiveCounts;
sessionsExpired[index] = sessionExpired;
unreadCounts[index] = unreadCount; unreadCounts[index] = unreadCount;
mentionCounts[index] = mentionCount; mentionCounts[index] = mentionCount;
@ -189,12 +190,13 @@ export default class MainPage extends React.Component {
} }
} }
this.setState({ this.setState({
sessionsExpired,
unreadCounts, unreadCounts,
mentionCounts, mentionCounts,
unreadAtActive, unreadAtActive,
mentionAtActiveCounts, mentionAtActiveCounts,
}); });
this.handleUnreadCountTotalChange(); this.handleBadgesChange();
} }
markReadAtActive(index) { markReadAtActive(index) {
@ -206,11 +208,13 @@ export default class MainPage extends React.Component {
unreadAtActive, unreadAtActive,
mentionAtActiveCounts, mentionAtActiveCounts,
}); });
this.handleUnreadCountTotalChange(); this.handleBadgesChange();
} }
handleUnreadCountTotalChange() { handleBadgesChange = () => {
if (this.props.onUnreadCountChange) { if (this.props.onBadgeChange) {
const someSessionsExpired = this.state.sessionsExpired.some((sessionExpired) => sessionExpired);
let allUnreadCount = this.state.unreadCounts.reduce((prev, curr) => { let allUnreadCount = this.state.unreadCounts.reduce((prev, curr) => {
return prev + curr; return prev + curr;
}, 0); }, 0);
@ -219,13 +223,15 @@ export default class MainPage extends React.Component {
allUnreadCount += 1; allUnreadCount += 1;
} }
}); });
let allMentionCount = this.state.mentionCounts.reduce((prev, curr) => { let allMentionCount = this.state.mentionCounts.reduce((prev, curr) => {
return prev + curr; return prev + curr;
}, 0); }, 0);
this.state.mentionAtActiveCounts.forEach((count) => { this.state.mentionAtActiveCounts.forEach((count) => {
allMentionCount += count; allMentionCount += count;
}); });
this.props.onUnreadCountChange(allUnreadCount, allMentionCount);
this.props.onBadgeChange(someSessionsExpired, allUnreadCount, allMentionCount);
} }
} }
@ -299,6 +305,7 @@ export default class MainPage extends React.Component {
<TabBar <TabBar
id='tabBar' id='tabBar'
teams={this.props.teams} teams={this.props.teams}
sessionsExpired={this.state.sessionsExpired}
unreadCounts={this.state.unreadCounts} unreadCounts={this.state.unreadCounts}
mentionCounts={this.state.mentionCounts} mentionCounts={this.state.mentionCounts}
unreadAtActive={this.state.unreadAtActive} unreadAtActive={this.state.unreadAtActive}
@ -315,8 +322,8 @@ export default class MainPage extends React.Component {
} }
const views = this.props.teams.map((team, index) => { const views = this.props.teams.map((team, index) => {
function handleUnreadCountChange(unreadCount, mentionCount, isUnread, isMentioned) { function handleBadgeChange(sessionExpired, unreadCount, mentionCount, isUnread, isMentioned) {
self.handleUnreadCountChange(index, unreadCount, mentionCount, isUnread, isMentioned); self.handleBadgeChange(index, sessionExpired, unreadCount, mentionCount, isUnread, isMentioned);
} }
function handleNotificationClick() { function handleNotificationClick() {
self.handleSelect(index); self.handleSelect(index);
@ -340,7 +347,7 @@ export default class MainPage extends React.Component {
src={teamUrl} src={teamUrl}
name={team.name} name={team.name}
onTargetURLChange={self.handleTargetURLChange} onTargetURLChange={self.handleTargetURLChange}
onUnreadCountChange={handleUnreadCountChange} onBadgeChange={handleBadgeChange}
onNotificationClick={handleNotificationClick} onNotificationClick={handleNotificationClick}
ref={id} ref={id}
active={isActive} active={isActive}
@ -437,7 +444,7 @@ export default class MainPage extends React.Component {
} }
MainPage.propTypes = { MainPage.propTypes = {
onUnreadCountChange: PropTypes.func.isRequired, onBadgeChange: PropTypes.func.isRequired,
teams: PropTypes.array.isRequired, teams: PropTypes.array.isRequired,
onTeamConfigChange: PropTypes.func.isRequired, onTeamConfigChange: PropTypes.func.isRequired,
initialIndex: PropTypes.number.isRequired, initialIndex: PropTypes.number.isRequired,

View file

@ -66,9 +66,9 @@ export default class MattermostView extends React.Component {
this.webviewRef = React.createRef(); this.webviewRef = React.createRef();
} }
handleUnreadCountChange(unreadCount, mentionCount, isUnread, isMentioned) { handleUnreadCountChange(sessionExpired, unreadCount, mentionCount, isUnread, isMentioned) {
if (this.props.onUnreadCountChange) { if (this.props.onBadgeChange) {
this.props.onUnreadCountChange(unreadCount, mentionCount, isUnread, isMentioned); this.props.onBadgeChange(sessionExpired, unreadCount, mentionCount, isUnread, isMentioned);
} }
} }
@ -158,14 +158,13 @@ export default class MattermostView extends React.Component {
isLoaded: true, isLoaded: true,
}); });
break; break;
case 'onUnreadCountChange': { case 'onBadgeChange': {
const unreadCount = event.args[0]; const sessionExpired = event.args[0];
const mentionCount = event.args[1]; const unreadCount = event.args[1];
const mentionCount = event.args[2];
// isUnread and isMentioned is pulse flag. const isUnread = event.args[3];
const isUnread = event.args[2]; const isMentioned = event.args[4];
const isMentioned = event.args[3]; self.handleUnreadCountChange(sessionExpired, unreadCount, mentionCount, isUnread, isMentioned);
self.handleUnreadCountChange(unreadCount, mentionCount, isUnread, isMentioned);
break; break;
} }
@ -326,7 +325,7 @@ MattermostView.propTypes = {
name: PropTypes.string, name: PropTypes.string,
id: PropTypes.string, id: PropTypes.string,
onTargetURLChange: PropTypes.func, onTargetURLChange: PropTypes.func,
onUnreadCountChange: PropTypes.func, onBadgeChange: PropTypes.func,
src: PropTypes.string, src: PropTypes.string,
active: PropTypes.bool, active: PropTypes.bool,
withTab: PropTypes.bool, withTab: PropTypes.bool,

View file

@ -10,6 +10,8 @@ import PermissionRequestDialog from './PermissionRequestDialog.jsx';
export default class TabBar extends React.Component { // need "this" export default class TabBar extends React.Component { // need "this"
render() { render() {
const tabs = this.props.teams.map((team, index) => { const tabs = this.props.teams.map((team, index) => {
const sessionExpired = this.props.sessionsExpired[index];
let unreadCount = 0; let unreadCount = 0;
if (this.props.unreadCounts[index] > 0) { if (this.props.unreadCounts[index] > 0) {
unreadCount = this.props.unreadCounts[index]; unreadCount = this.props.unreadCounts[index];
@ -27,11 +29,16 @@ export default class TabBar extends React.Component { // need "this"
} }
let badgeDiv; let badgeDiv;
if (mentionCount !== 0) { if (sessionExpired) {
badgeDiv = (
<div className='TabBar-badge TabBar-badge-nomention'>{'•'}</div>
);
} else if (mentionCount !== 0) {
badgeDiv = ( badgeDiv = (
<div className='TabBar-badge'> <div className='TabBar-badge'>
{mentionCount} {mentionCount}
</div>); </div>
);
} }
const id = 'teamTabItem' + index; const id = 'teamTabItem' + index;
const requestingPermission = this.props.requestingPermission[index]; const requestingPermission = this.props.requestingPermission[index];
@ -114,6 +121,7 @@ TabBar.propTypes = {
id: PropTypes.string, id: PropTypes.string,
onSelect: PropTypes.func, onSelect: PropTypes.func,
teams: PropTypes.array, teams: PropTypes.array,
sessionsExpired: PropTypes.array,
unreadCounts: PropTypes.array, unreadCounts: PropTypes.array,
unreadAtActive: PropTypes.array, unreadAtActive: PropTypes.array,
mentionCounts: PropTypes.array, mentionCounts: PropTypes.array,

View file

@ -53,6 +53,11 @@
border-radius: 50%; border-radius: 50%;
} }
.TabBar .TabBar-badge.TabBar-badge-nomention {
font-size: 22pt;
line-height: 18px;
}
.TabBar .teamTabItem-unread { .TabBar .teamTabItem-unread {
font-weight: bold; font-weight: bold;
} }

View file

@ -31,19 +31,23 @@ if (teams.length === 0) {
window.location = 'settings.html'; window.location = 'settings.html';
} }
function showUnreadBadgeWindows(unreadCount, mentionCount) { function showBadgeWindows(sessionExpired, unreadCount, mentionCount) {
function sendBadge(dataURL, description) { function sendBadge(dataURL, description) {
// window.setOverlayIcon() does't work with NativeImage across remote boundaries. // window.setOverlayIcon() does't work with NativeImage across remote boundaries.
// https://github.com/atom/electron/issues/4011 // https://github.com/atom/electron/issues/4011
ipcRenderer.send('update-unread', { ipcRenderer.send('update-unread', {
overlayDataURL: dataURL, overlayDataURL: dataURL,
description, description,
sessionExpired,
unreadCount, unreadCount,
mentionCount, mentionCount,
}); });
} }
if (mentionCount > 0) { if (sessionExpired) {
const dataURL = createBadgeDataURL('•');
sendBadge(dataURL, 'Session Expired: Please sign in to continue receiving notifications.');
} else if (mentionCount > 0) {
const dataURL = createBadgeDataURL(mentionCount.toString()); const dataURL = createBadgeDataURL(mentionCount.toString());
sendBadge(dataURL, 'You have unread mentions (' + mentionCount + ')'); sendBadge(dataURL, 'You have unread mentions (' + mentionCount + ')');
} else if (unreadCount > 0 && AppConfig.data.showUnreadBadge) { } else if (unreadCount > 0 && AppConfig.data.showUnreadBadge) {
@ -54,8 +58,10 @@ function showUnreadBadgeWindows(unreadCount, mentionCount) {
} }
} }
function showUnreadBadgeOSX(unreadCount, mentionCount) { function showBadgeOSX(sessionExpired, unreadCount, mentionCount) {
if (mentionCount > 0) { if (sessionExpired) {
remote.app.dock.setBadge('•');
} else if (mentionCount > 0) {
remote.app.dock.setBadge(mentionCount.toString()); remote.app.dock.setBadge(mentionCount.toString());
} else if (unreadCount > 0 && AppConfig.data.showUnreadBadge) { } else if (unreadCount > 0 && AppConfig.data.showUnreadBadge) {
remote.app.dock.setBadge('•'); remote.app.dock.setBadge('•');
@ -64,34 +70,39 @@ function showUnreadBadgeOSX(unreadCount, mentionCount) {
} }
ipcRenderer.send('update-unread', { ipcRenderer.send('update-unread', {
sessionExpired,
unreadCount, unreadCount,
mentionCount, mentionCount,
}); });
} }
function showUnreadBadgeLinux(unreadCount, mentionCount) { function showBadgeLinux(sessionExpired, unreadCount, mentionCount) {
if (remote.app.isUnityRunning()) { if (remote.app.isUnityRunning()) {
if (sessionExpired) {
remote.app.setBadgeCount(mentionCount + 1);
} else {
remote.app.setBadgeCount(mentionCount); remote.app.setBadgeCount(mentionCount);
} }
}
ipcRenderer.send('update-unread', { ipcRenderer.send('update-unread', {
sessionExpired,
unreadCount, unreadCount,
mentionCount, mentionCount,
}); });
} }
function showUnreadBadge(unreadCount, mentionCount) { function showBadge(sessionExpired, unreadCount, mentionCount) {
switch (process.platform) { switch (process.platform) {
case 'win32': case 'win32':
showUnreadBadgeWindows(unreadCount, mentionCount); showBadgeWindows(sessionExpired, unreadCount, mentionCount);
break; break;
case 'darwin': case 'darwin':
showUnreadBadgeOSX(unreadCount, mentionCount); showBadgeOSX(sessionExpired, unreadCount, mentionCount);
break; break;
case 'linux': case 'linux':
showUnreadBadgeLinux(unreadCount, mentionCount); showBadgeLinux(sessionExpired, unreadCount, mentionCount);
break; break;
default:
} }
} }
@ -168,7 +179,7 @@ ReactDOM.render(
<MainPage <MainPage
teams={teams} teams={teams}
initialIndex={initialIndex} initialIndex={initialIndex}
onUnreadCountChange={showUnreadBadge} onBadgeChange={showBadge}
onTeamConfigChange={teamConfigChange} onTeamConfigChange={teamConfigChange}
useSpellChecker={AppConfig.data.useSpellChecker} useSpellChecker={AppConfig.data.useSpellChecker}
onSelectSpellCheckerLocale={handleSelectSpellCheckerLocale} onSelectSpellCheckerLocale={handleSelectSpellCheckerLocale}

View file

@ -66,9 +66,13 @@ function getUnreadCount() {
this.mentionCount = 0; this.mentionCount = 0;
} }
// LHS not found => Log out => Count should be 0. // LHS not found => Log out => Count should be 0, but session may be expired.
if (document.getElementById('sidebar-left') === null) { if (document.getElementById('sidebar-left') === null) {
ipcRenderer.sendToHost('onUnreadCountChange', 0, 0, false, false); const extraParam = (new URLSearchParams(window.location.search)).get('extra');
const sessionExpired = extraParam === 'expired';
ipcRenderer.sendToHost('onBadgeChange', sessionExpired, 0, 0, false, false);
this.sessionExpired = sessionExpired;
this.unreadCount = 0; this.unreadCount = 0;
this.mentionCount = 0; this.mentionCount = 0;
setTimeout(getUnreadCount, UNREAD_COUNT_INTERVAL); setTimeout(getUnreadCount, UNREAD_COUNT_INTERVAL);
@ -152,11 +156,12 @@ function getUnreadCount() {
} }
} }
if (this.unreadCount !== unreadCount || this.mentionCount !== mentionCount || isUnread || isMentioned) { if (this.sessionExpired || this.unreadCount !== unreadCount || this.mentionCount !== mentionCount || isUnread || isMentioned) {
ipcRenderer.sendToHost('onUnreadCountChange', unreadCount, mentionCount, isUnread, isMentioned); ipcRenderer.sendToHost('onBadgeChange', false, unreadCount, mentionCount, isUnread, isMentioned);
} }
this.unreadCount = unreadCount; this.unreadCount = unreadCount;
this.mentionCount = mentionCount; this.mentionCount = mentionCount;
this.sessionExpired = false;
setTimeout(getUnreadCount, UNREAD_COUNT_INTERVAL); setTimeout(getUnreadCount, UNREAD_COUNT_INTERVAL);
} }
setTimeout(getUnreadCount, UNREAD_COUNT_INTERVAL); setTimeout(getUnreadCount, UNREAD_COUNT_INTERVAL);

View file

@ -528,7 +528,14 @@ app.on('ready', () => {
} }
if (trayIcon && !trayIcon.isDestroyed()) { if (trayIcon && !trayIcon.isDestroyed()) {
if (arg.mentionCount > 0) { if (arg.sessionExpired) {
// reuse the mention icon when the session is expired
trayIcon.setImage(trayImages.mention);
if (process.platform === 'darwin') {
trayIcon.setPressedImage(trayImages.clicked.mention);
}
trayIcon.setToolTip('Session Expired: Please sign in to continue receiving notifications.');
} else if (arg.mentionCount > 0) {
trayIcon.setImage(trayImages.mention); trayIcon.setImage(trayImages.mention);
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
trayIcon.setPressedImage(trayImages.clicked.mention); trayIcon.setPressedImage(trayImages.clicked.mention);