[MM-30432]: Allow users to specify different desktop notification sounds per channel (#21671)

This commit is contained in:
KyeongSoo Kim
2023-06-05 20:58:05 +09:00
committed by GitHub
parent 951456c780
commit fbd6b5efe1
25 changed files with 633 additions and 135 deletions

View File

@@ -245,7 +245,7 @@ function setCRTDesktopNotification(type) {
cy.get('#desktopTitle').
scrollIntoView().
should('be.visible').
and('contain', 'Send desktop notifications').click();
and('contain', 'Desktop notifications').click();
// # Select mentions category for messages.
cy.get('#channelNotificationMentions').scrollIntoView().check();

View File

@@ -53,7 +53,7 @@ describe('Desktop notifications', () => {
// # Set channel notifications to show on mention only
cy.uiOpenChannelMenu('Notification Preferences');
cy.findByText('Send desktop notifications').click();
cy.findByText('Desktop notifications').click();
cy.findByRole('radio', {name: 'Only for mentions'}).click();
cy.uiSaveAndClose();

View File

@@ -1334,6 +1334,14 @@ func (a *App) UpdateChannelMemberNotifyProps(c request.CTX, data map[string]stri
filteredProps[model.DesktopNotifyProp] = desktop
}
if desktop_sound, exists := data[model.DesktopSoundNotifyProp]; exists {
filteredProps[model.DesktopSoundNotifyProp] = desktop_sound
}
if desktop_notification_sound, exists := data["desktop_notification_sound"]; exists {
filteredProps["desktop_notification_sound"] = desktop_notification_sound
}
if desktop_threads, exists := data[model.DesktopThreadsNotifyProp]; exists {
filteredProps[model.DesktopThreadsNotifyProp] = desktop_threads
}

View File

@@ -28,6 +28,22 @@ const NOTIFY_TEXT_MAX_LENGTH = 50;
// windows notification length is based windows chrome which supports 128 characters and is the lowest length of windows browsers
const WINDOWS_NOTIFY_TEXT_MAX_LENGTH = 120;
const getSoundFromChannelMemberAndUser = (member, user) => {
if (member?.notify_props?.desktop_sound) {
return member.notify_props.desktop_sound === 'on';
}
return !user.notify_props || user.notify_props.desktop_sound === 'true';
};
const getNotificationSoundFromChannelMemberAndUser = (member, user) => {
if (member?.notify_props?.desktop_notification_sound) {
return member.notify_props.desktop_notification_sound;
}
return user.notify_props?.desktop_notification_sound ? user.notify_props.desktop_notification_sound : 'Bing';
};
export function sendDesktopNotification(post, msgProps) {
return async (dispatch, getState) => {
const state = getState();
@@ -159,7 +175,7 @@ export function sendDesktopNotification(post, msgProps) {
}
//Play a sound if explicitly set in settings
const sound = !user.notify_props || user.notify_props.desktop_sound === 'true';
const sound = getSoundFromChannelMemberAndUser(member, user);
// Notify if you're not looking in the right channel or when
// the window itself is not active
@@ -174,7 +190,7 @@ export function sendDesktopNotification(post, msgProps) {
}
notify = notify || !state.views.browser.focused;
const soundName = user.notify_props !== undefined && user.notify_props.desktop_notification_sound !== undefined ? user.notify_props.desktop_notification_sound : 'Bing';
const soundName = getNotificationSoundFromChannelMemberAndUser(member, user);
if (notify) {
const updatedState = getState();

View File

@@ -97,10 +97,17 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
<NotificationSection
expand={false}
globalNotificationLevel="all"
globalNotificationSound="Bing"
isNotificationsSettingSameAsGlobal={true}
memberDesktopNotificationSound="Bing"
memberDesktopSound="on"
memberNotificationLevel="all"
memberThreadsNotificationLevel="all"
onChange={[Function]}
onChangeDesktopSound={[Function]}
onChangeNotificationSound={[Function]}
onChangeThreads={[Function]}
onReset={[Function]}
onSubmit={[Function]}
onUpdateSection={[Function]}
section="desktop"
@@ -111,10 +118,12 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
/>
<NotificationSection
expand={false}
memberNotificationLevel="default"
memberThreadsNotificationLevel="default"
isNotificationsSettingSameAsGlobal={true}
memberNotificationLevel="all"
memberThreadsNotificationLevel="all"
onChange={[Function]}
onChangeThreads={[Function]}
onReset={[Function]}
onSubmit={[Function]}
onUpdateSection={[Function]}
section="push"

View File

@@ -4,7 +4,7 @@
import React, {ComponentProps} from 'react';
import {shallow} from 'enzyme';
import {ChannelAutoFollowThreads, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
import {ChannelAutoFollowThreads, DesktopSound, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
import {TestHelper} from 'utils/test_helper';
import ChannelNotificationsModal from 'components/channel_notifications_modal/channel_notifications_modal';
@@ -22,6 +22,8 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
channelMember: {
notify_props: {
desktop: NotificationLevels.ALL,
desktop_sound: DesktopSound.ON,
desktop_notification_sound: 'Bing',
mark_unread: NotificationLevels.ALL,
push: NotificationLevels.DEFAULT,
ignore_channel_mentions: IgnoreChannelMentions.DEFAULT,
@@ -59,9 +61,11 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
/>,
);
expect(wrapper.state('desktopNotifyLevel')).toEqual(NotificationLevels.DEFAULT);
expect(wrapper.state('desktopNotifyLevel')).toEqual(NotificationLevels.ALL);
expect(wrapper.state('desktopSound')).toEqual(DesktopSound.ON);
expect(wrapper.state('desktopNotifySound')).toEqual('Bing');
expect(wrapper.state('markUnreadNotifyLevel')).toEqual(NotificationLevels.ALL);
expect(wrapper.state('pushNotifyLevel')).toEqual(NotificationLevels.DEFAULT);
expect(wrapper.state('pushNotifyLevel')).toEqual(NotificationLevels.ALL);
expect(wrapper.state('ignoreChannelMentions')).toEqual(IgnoreChannelMentions.OFF);
expect(wrapper.state('channelAutoFollowThreads')).toEqual(ChannelAutoFollowThreads.OFF);
});
@@ -186,7 +190,7 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
wrapper.instance().handleExit();
expect(baseProps.onExited).toHaveBeenCalledTimes(3);
expect(wrapper.state('activeSection')).toEqual(NotificationSections.NONE);
expect(wrapper.state('pushNotifyLevel')).toEqual(NotificationLevels.DEFAULT);
expect(wrapper.state('pushNotifyLevel')).toEqual(NotificationLevels.ALL);
});
test('should match state on updateSection', () => {
@@ -214,7 +218,7 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
expect(wrapper.state('desktopNotifyLevel')).toEqual(baseProps.channelMember?.notify_props.desktop);
});
test('should match state on handleSubmitDesktopNotifyLevel', () => {
test('should match state on handleSubmitDesktopNotification', () => {
const wrapper = shallow<ChannelNotificationsModal>(
<ChannelNotificationsModal {...baseProps}/>,
);
@@ -223,12 +227,12 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
instance.handleUpdateChannelNotifyProps = jest.fn();
instance.updateSection = jest.fn();
wrapper.setState({desktopNotifyLevel: NotificationLevels.DEFAULT});
instance.handleSubmitDesktopNotifyLevel();
wrapper.setState({desktopNotifyLevel: NotificationLevels.MENTION});
instance.handleSubmitDesktopNotification();
expect(instance.handleUpdateChannelNotifyProps).toHaveBeenCalledTimes(1);
wrapper.setState({desktopNotifyLevel: NotificationLevels.ALL});
instance.handleSubmitDesktopNotifyLevel();
instance.handleSubmitDesktopNotification();
expect(instance.updateSection).toHaveBeenCalledTimes(1);
expect(instance.updateSection).toBeCalledWith('');
});

View File

@@ -6,9 +6,11 @@ import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import * as NotificationSounds from 'utils/notification_sounds';
import {isChannelMuted} from 'mattermost-redux/utils/channel_utils';
import {ChannelAutoFollowThreads, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
import {ChannelAutoFollowThreads, DesktopSound, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
import NotificationSection from 'components/channel_notifications_modal/components/notification_section.jsx';
@@ -42,6 +44,8 @@ type State = {
activeSection: string;
serverError: string | null;
desktopNotifyLevel: ChannelNotifyProps['desktop'];
desktopSound: ChannelNotifyProps['desktop_sound'];
desktopNotifySound: ChannelNotifyProps['desktop_notification_sound'];
desktopThreadsNotifyLevel: UserNotifyProps['desktop_threads'];
markUnreadNotifyLevel: ChannelNotifyProps['mark_unread'];
pushNotifyLevel: ChannelNotifyProps['push'];
@@ -50,6 +54,59 @@ type State = {
channelAutoFollowThreads: ChannelNotifyProps['channel_auto_follow_threads'];
};
export type DesktopNotificationProps = Pick<State, 'desktopNotifyLevel' | 'desktopNotifySound' | 'desktopSound' | 'desktopThreadsNotifyLevel'>
export type PushNotificationProps = Pick<State, 'pushNotifyLevel' | 'pushThreadsNotifyLevel'>
const getDefaultDesktopNotificationLevel = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['desktop'], undefined> => {
if (currentUserNotifyProps?.desktop) {
if (currentUserNotifyProps.desktop === 'default') {
return NotificationLevels.ALL;
}
return currentUserNotifyProps.desktop;
}
return NotificationLevels.ALL;
};
const getDefaultDesktopSound = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['desktop_sound'], undefined> => {
if (currentUserNotifyProps?.desktop_sound) {
return currentUserNotifyProps.desktop_sound === 'true' ? DesktopSound.ON : DesktopSound.OFF;
}
return DesktopSound.ON;
};
const getDefaultDesktopNotificationSound = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['desktop_notification_sound'], undefined> => {
if (currentUserNotifyProps?.desktop_notification_sound) {
return currentUserNotifyProps.desktop_notification_sound;
}
return 'Bing';
};
const getDefaultDesktopThreadsNotifyLevel = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['desktop_threads'], undefined> => {
if (currentUserNotifyProps?.desktop_threads) {
return currentUserNotifyProps.desktop_threads;
}
return NotificationLevels.ALL;
};
const getDefaultPushNotifyLevel = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['push'], undefined> => {
if (currentUserNotifyProps?.push) {
if (currentUserNotifyProps.push === 'default') {
return NotificationLevels.ALL;
}
return currentUserNotifyProps.push;
}
return NotificationLevels.ALL;
};
const getDefaultPushThreadsNotifyLevel = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['push_threads'], undefined> => {
if (currentUserNotifyProps?.push_threads) {
if (currentUserNotifyProps.push_threads === 'default') {
return NotificationLevels.ALL;
}
return currentUserNotifyProps.push_threads;
}
return NotificationLevels.ALL;
};
export default class ChannelNotificationsModal extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
@@ -77,9 +134,63 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
this.setState(this.getStateFromNotifyProps(currentUserNotifyProps, channelMemberNotifyProps));
}
verifyNotificationsSettingSameAsGlobal({
desktopNotifyLevel,
desktopNotifySound,
desktopSound,
desktopThreadsNotifyLevel,
}: DesktopNotificationProps) {
const currentUserNotifyProps = this.props.currentUser.notify_props;
if (
desktopNotifyLevel === getDefaultDesktopNotificationLevel(currentUserNotifyProps) &&
desktopNotifySound === getDefaultDesktopNotificationSound(currentUserNotifyProps) &&
desktopSound === getDefaultDesktopSound(currentUserNotifyProps) &&
desktopThreadsNotifyLevel === getDefaultDesktopThreadsNotifyLevel(currentUserNotifyProps)
) {
return true;
}
return false;
}
verifyPushNotificationsSettingSameAsGlobal({
pushNotifyLevel,
pushThreadsNotifyLevel,
}: PushNotificationProps) {
const currentUserNotifyProps = this.props.currentUser.notify_props;
if (
pushNotifyLevel === getDefaultPushNotifyLevel(currentUserNotifyProps) &&
pushThreadsNotifyLevel === getDefaultPushThreadsNotifyLevel(currentUserNotifyProps)
) {
return true;
}
return false;
}
getStateFromNotifyProps(currentUserNotifyProps: UserNotifyProps, channelMemberNotifyProps?: ChannelMemberNotifyProps) {
let ignoreChannelMentionsDefault: ChannelNotifyProps['ignore_channel_mentions'] = IgnoreChannelMentions.OFF;
let desktopNotifyLevelDefault: ChannelNotifyProps['desktop'] = getDefaultDesktopNotificationLevel(currentUserNotifyProps);
let pushNotifyLevelDefault: ChannelMemberNotifyProps['push'] = getDefaultPushNotifyLevel(currentUserNotifyProps);
let pushThreadsNotifyLevelDefault: ChannelMemberNotifyProps['push_threads'] = getDefaultPushThreadsNotifyLevel(currentUserNotifyProps);
if (channelMemberNotifyProps?.desktop) {
if (channelMemberNotifyProps.desktop !== 'default') {
desktopNotifyLevelDefault = channelMemberNotifyProps.desktop;
}
}
if (channelMemberNotifyProps?.push) {
if (channelMemberNotifyProps.push !== 'default') {
pushNotifyLevelDefault = channelMemberNotifyProps.push;
}
}
if (channelMemberNotifyProps?.push_threads) {
if (channelMemberNotifyProps.push_threads !== 'default') {
pushThreadsNotifyLevelDefault = channelMemberNotifyProps.push_threads;
}
}
if (channelMemberNotifyProps?.mark_unread === NotificationLevels.MENTION || (currentUserNotifyProps.channel && currentUserNotifyProps.channel === 'false')) {
ignoreChannelMentionsDefault = IgnoreChannelMentions.ON;
}
@@ -90,11 +201,13 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
}
return {
desktopNotifyLevel: channelMemberNotifyProps?.desktop || NotificationLevels.DEFAULT,
desktopThreadsNotifyLevel: channelMemberNotifyProps?.desktop_threads || NotificationLevels.ALL,
desktopNotifyLevel: desktopNotifyLevelDefault,
desktopSound: channelMemberNotifyProps?.desktop_sound || getDefaultDesktopSound(currentUserNotifyProps),
desktopNotifySound: channelMemberNotifyProps?.desktop_notification_sound || getDefaultDesktopNotificationSound(currentUserNotifyProps),
desktopThreadsNotifyLevel: channelMemberNotifyProps?.desktop_threads || getDefaultDesktopThreadsNotifyLevel(currentUserNotifyProps),
markUnreadNotifyLevel: channelMemberNotifyProps?.mark_unread || NotificationLevels.ALL,
pushNotifyLevel: channelMemberNotifyProps?.push || NotificationLevels.DEFAULT,
pushThreadsNotifyLevel: channelMemberNotifyProps?.push_threads || NotificationLevels.ALL,
pushNotifyLevel: pushNotifyLevelDefault,
pushThreadsNotifyLevel: pushThreadsNotifyLevelDefault,
ignoreChannelMentions,
channelAutoFollowThreads: channelMemberNotifyProps?.channel_auto_follow_threads || ChannelAutoFollowThreads.OFF,
};
@@ -130,20 +243,45 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
this.updateSection(NotificationSections.NONE);
}
};
handleResetDesktopNotification = () => {
const currentUserNotifyProps = this.props.currentUser.notify_props;
handleSubmitDesktopNotifyLevel = () => {
const userDesktopNotificationDefaults = {
desktopNotifyLevel: getDefaultDesktopNotificationLevel(currentUserNotifyProps),
desktopSound: getDefaultDesktopSound(currentUserNotifyProps),
desktopNotifySound: getDefaultDesktopNotificationSound(currentUserNotifyProps),
desktopThreadsNotifyLevel: getDefaultDesktopThreadsNotifyLevel(currentUserNotifyProps),
};
this.setState(userDesktopNotificationDefaults);
};
handleResetPushNotification = () => {
const currentUserNotifyProps = this.props.currentUser.notify_props;
const userPushNotificationDefaults = {
pushNotifyLevel: getDefaultPushNotifyLevel(currentUserNotifyProps),
pushThreadsNotifyLevel: getDefaultPushThreadsNotifyLevel(currentUserNotifyProps),
};
this.setState(userPushNotificationDefaults);
};
handleSubmitDesktopNotification = () => {
const channelNotifyProps = this.props.channelMember && this.props.channelMember.notify_props as ChannelMemberNotifyProps;
const {desktopNotifyLevel, desktopThreadsNotifyLevel} = this.state;
const {desktopNotifyLevel, desktopNotifySound, desktopSound, desktopThreadsNotifyLevel} = this.state;
if (
channelNotifyProps?.desktop === desktopNotifyLevel &&
channelNotifyProps?.desktop_threads === desktopThreadsNotifyLevel
channelNotifyProps?.desktop_threads === desktopThreadsNotifyLevel &&
channelNotifyProps?.desktop_sound === desktopSound &&
channelNotifyProps?.desktop_notification_sound === desktopNotifySound
) {
this.updateSection(NotificationSections.NONE);
return;
}
const props = {desktop: desktopNotifyLevel, desktop_threads: desktopThreadsNotifyLevel};
const props = {desktop: desktopNotifyLevel, desktop_threads: desktopThreadsNotifyLevel, desktop_sound: desktopSound, desktop_notification_sound: desktopNotifySound};
this.handleUpdateChannelNotifyProps(props);
};
@@ -152,6 +290,15 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
handleUpdateDesktopThreadsNotifyLevel = (desktopThreadsNotifyLevel: UserNotifyProps['desktop_threads']) => this.setState({desktopThreadsNotifyLevel});
handleUpdateDesktopSound = (desktopSound: ChannelNotifyProps['desktop_sound']) => this.setState({desktopSound});
handleUpdateDesktopNotifySound = (desktopNotifySound: ChannelNotifyProps['desktop_notification_sound']) => {
if (desktopNotifySound) {
NotificationSounds.tryNotificationSound(desktopNotifySound);
}
this.setState({desktopNotifySound});
};
handleSubmitMarkUnreadLevel = () => {
const channelNotifyProps = this.props.channelMember && this.props.channelMember.notify_props;
const {markUnreadNotifyLevel} = this.state;
@@ -220,6 +367,8 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
activeSection,
desktopNotifyLevel,
desktopThreadsNotifyLevel,
desktopSound,
desktopNotifySound,
markUnreadNotifyLevel,
pushNotifyLevel,
pushThreadsNotifyLevel,
@@ -235,6 +384,18 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
sendPushNotifications,
} = this.props;
const isNotificationsSettingSameAsGlobal = this.verifyNotificationsSettingSameAsGlobal({
desktopNotifyLevel,
desktopNotifySound,
desktopSound,
desktopThreadsNotifyLevel,
});
const isPushNotificationsSettingSameAsGlobal = this.verifyPushNotificationsSettingSameAsGlobal({
pushNotifyLevel,
pushThreadsNotifyLevel,
});
let serverErrorTag = null;
if (serverError) {
serverErrorTag = <div className='form-group has-error'><label className='control-label'>{serverError}</label></div>;
@@ -295,10 +456,17 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
expand={activeSection === NotificationSections.DESKTOP}
memberNotificationLevel={desktopNotifyLevel}
memberThreadsNotificationLevel={desktopThreadsNotifyLevel}
memberDesktopSound={desktopSound}
memberDesktopNotificationSound={desktopNotifySound}
globalNotificationLevel={currentUser.notify_props ? currentUser.notify_props.desktop : NotificationLevels.ALL}
globalNotificationSound={(currentUser.notify_props && currentUser.notify_props.desktop_notification_sound) ? currentUser.notify_props.desktop_notification_sound : 'Bing'}
isNotificationsSettingSameAsGlobal={isNotificationsSettingSameAsGlobal}
onChange={this.handleUpdateDesktopNotifyLevel}
onChangeThreads={this.handleUpdateDesktopThreadsNotifyLevel}
onSubmit={this.handleSubmitDesktopNotifyLevel}
onChangeDesktopSound={this.handleUpdateDesktopSound}
onChangeNotificationSound={this.handleUpdateDesktopNotifySound}
onReset={this.handleResetDesktopNotification}
onSubmit={this.handleSubmitDesktopNotification}
onUpdateSection={this.updateSection}
serverError={serverError}
/>
@@ -310,7 +478,9 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
memberNotificationLevel={pushNotifyLevel}
memberThreadsNotificationLevel={pushThreadsNotifyLevel}
globalNotificationLevel={currentUser.notify_props ? currentUser.notify_props.push : NotificationLevels.ALL}
isNotificationsSettingSameAsGlobal={isPushNotificationsSettingSameAsGlobal}
onChange={this.handleUpdatePushNotificationLevel}
onReset={this.handleResetPushNotification}
onChangeThreads={this.handleUpdatePushThreadsNotificationLevel}
onSubmit={this.handleSubmitPushNotificationLevel}
onUpdateSection={this.updateSection}

View File

@@ -2,8 +2,13 @@
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on DESKTOP/PUSH & ALL 1`] = `
<MemoizedFormattedMessage
defaultMessage="For all activity"
defaultMessage="For all activity ({isDefault})"
id="channel_notifications.allActivity"
values={
Object {
"isDefault": <React.Fragment />,
}
}
/>
`;
@@ -16,15 +21,25 @@ exports[`components/channel_notifications_modal/NotificationSection should match
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on MENTION 1`] = `
<MemoizedFormattedMessage
defaultMessage="Only for mentions"
defaultMessage="Only for mentions ({isDefault})"
id="channel_notifications.onlyMentions"
values={
Object {
"isDefault": <React.Fragment />,
}
}
/>
`;
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on NONE 1`] = `
<MemoizedFormattedMessage
defaultMessage="Never"
defaultMessage="Never ({isDefault})"
id="channel_notifications.never"
values={
Object {
"isDefault": <React.Fragment />,
}
}
/>
`;

View File

@@ -8,27 +8,14 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
Array [
<div>
<fieldset>
<div
className="radio"
<legend
className="form-legend"
>
<label
className=""
>
<input
checked={false}
id="channelNotificationGlobalDefault"
name="channelDesktopNotifications"
onChange={[MockFunction]}
type="radio"
value="default"
/>
<Describe
globalNotifyLevel="default"
memberNotifyLevel="default"
section="desktop"
/>
</label>
</div>
<Memo(MemoizedFormattedMessage)
defaultMessage="Send desktop notifications"
id="channel_notifications.sendDesktop"
/>
</legend>
<div
className="radio"
>
@@ -38,12 +25,13 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
<input
checked={true}
id="channelNotificationAllActivity"
name="channelDesktopNotifications"
name="channelNotifications"
onChange={[MockFunction]}
type="radio"
value="all"
/>
<Describe
globalNotifyLevel="default"
memberNotifyLevel="all"
section="desktop"
/>
@@ -58,12 +46,13 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
<input
checked={false}
id="channelNotificationMentions"
name="channelDesktopNotifications"
name="channelNotifications"
onChange={[MockFunction]}
type="radio"
value="mention"
/>
<Describe
globalNotifyLevel="default"
memberNotifyLevel="mention"
section="desktop"
/>
@@ -76,12 +65,13 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
<input
checked={false}
id="channelNotificationNever"
name="channelDesktopNotifications"
name="channelNotifications"
onChange={[MockFunction]}
type="radio"
value="none"
/>
<Describe
globalNotifyLevel="default"
memberNotifyLevel="none"
section="desktop"
/>
@@ -95,6 +85,63 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
section="desktop"
/>
</div>
<React.Fragment>
<hr />
<fieldset>
<legend
className="form-legend"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="Notification sound"
id="channel_notifications.sound"
/>
</legend>
<div
className="radio"
>
<label
className=""
>
<input
checked={false}
id="channelDesktopSoundOn"
name="channelDesktopSound"
type="radio"
value="on"
/>
<Memo(MemoizedFormattedMessage)
defaultMessage="On"
id="channel_notifications.sound.on.title"
/>
</label>
</div>
<div
className="radio"
>
<label>
<input
checked={false}
id="channelDesktopSoundOff"
name="channelDesktopSound"
type="radio"
value="off"
/>
<Memo(MemoizedFormattedMessage)
defaultMessage="Off"
id="channel_notifications.sound.off.title"
/>
</label>
</div>
<div
className="mt-5"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="Notification sounds are available on Firefox, Edge, Safari, Chrome and Mattermost Desktop Apps."
id="channel_notifications.sound_info"
/>
</div>
</fieldset>
</React.Fragment>
</div>,
]
}
@@ -104,6 +151,8 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
submit={[MockFunction]}
title={
<SectionTitle
isExpanded={true}
onClickResetButton={[MockFunction]}
section="desktop"
/>
}
@@ -176,6 +225,8 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
submit={[MockFunction]}
title={
<SectionTitle
isExpanded={true}
onClickResetButton={[MockFunction]}
section="markUnread"
/>
}
@@ -191,27 +242,14 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
Array [
<div>
<fieldset>
<div
className="radio"
<legend
className="form-legend"
>
<label
className=""
>
<input
checked={false}
id="channelNotificationGlobalDefault"
name="channelDesktopNotifications"
onChange={[MockFunction]}
type="radio"
value="default"
/>
<Describe
globalNotifyLevel="default"
memberNotifyLevel="default"
section="push"
/>
</label>
</div>
<Memo(MemoizedFormattedMessage)
defaultMessage="Send mobile push notifications"
id="channel_notifications.sendMobilePush"
/>
</legend>
<div
className="radio"
>
@@ -221,12 +259,13 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
<input
checked={true}
id="channelNotificationAllActivity"
name="channelDesktopNotifications"
name="channelNotifications"
onChange={[MockFunction]}
type="radio"
value="all"
/>
<Describe
globalNotifyLevel="default"
memberNotifyLevel="all"
section="push"
/>
@@ -241,12 +280,13 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
<input
checked={false}
id="channelNotificationMentions"
name="channelDesktopNotifications"
name="channelNotifications"
onChange={[MockFunction]}
type="radio"
value="mention"
/>
<Describe
globalNotifyLevel="default"
memberNotifyLevel="mention"
section="push"
/>
@@ -259,12 +299,13 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
<input
checked={false}
id="channelNotificationNever"
name="channelDesktopNotifications"
name="channelNotifications"
onChange={[MockFunction]}
type="radio"
value="none"
/>
<Describe
globalNotifyLevel="default"
memberNotifyLevel="none"
section="push"
/>
@@ -287,6 +328,8 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
submit={[MockFunction]}
title={
<SectionTitle
isExpanded={true}
onClickResetButton={[MockFunction]}
section="push"
/>
}

View File

@@ -3,7 +3,7 @@
exports[`components/channel_notifications_modal/ExtraInfo should match snapshot, on DESKTOP 1`] = `
<span>
<MemoizedFormattedMessage
defaultMessage="Selecting an option other than \\"Default\\" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome."
defaultMessage="Selecting an option other than the \\"default\\" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome."
id="channel_notifications.override"
/>
</span>
@@ -21,7 +21,7 @@ exports[`components/channel_notifications_modal/ExtraInfo should match snapshot,
exports[`components/channel_notifications_modal/ExtraInfo should match snapshot, on PUSH 1`] = `
<span>
<MemoizedFormattedMessage
defaultMessage="Selecting an option other than \\"Global default\\" will override the global notification settings for mobile push notifications in Settings. Push notifications must be enabled by the System Admin."
defaultMessage="Selecting an option other than the \\"default\\" will override the global notification settings for mobile push notifications."
id="channel_notifications.overridePush"
/>
</span>

View File

@@ -24,8 +24,11 @@ exports[`components/channel_notifications_modal/NotificationSection should match
memberNotifyLevel="all"
memberThreadsNotifyLevel="all"
onChange={[Function]}
onChangeDesktopSound={[Function]}
onChangeNotificationSound={[Function]}
onChangeThreads={[Function]}
onCollapseSection={[Function]}
onReset={[Function]}
onSubmit={[Function]}
section="desktop"
serverError=""
@@ -47,8 +50,11 @@ exports[`components/channel_notifications_modal/NotificationSection should match
memberNotifyLevel="all"
memberThreadsNotifyLevel="all"
onChange={[Function]}
onChangeDesktopSound={[Function]}
onChangeNotificationSound={[Function]}
onChangeThreads={[Function]}
onCollapseSection={[Function]}
onReset={[Function]}
onSubmit={[Function]}
section="markUnread"
serverError=""
@@ -70,8 +76,11 @@ exports[`components/channel_notifications_modal/NotificationSection should match
memberNotifyLevel="all"
memberThreadsNotifyLevel="all"
onChange={[Function]}
onChangeDesktopSound={[Function]}
onChangeNotificationSound={[Function]}
onChangeThreads={[Function]}
onCollapseSection={[Function]}
onReset={[Function]}
onSubmit={[Function]}
section="push"
serverError=""

View File

@@ -1,10 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/channel_notifications_modal/ExtraInfo should match snapshot, on DESKTOP 1`] = `
<MemoizedFormattedMessage
defaultMessage="Send desktop notifications"
id="channel_notifications.sendDesktop"
/>
<div
className="SectionTitle__wrapper"
>
<MemoizedFormattedMessage
defaultMessage="Desktop notifications"
id="channel_notifications.desktopNotifications"
/>
</div>
`;
exports[`components/channel_notifications_modal/ExtraInfo should match snapshot, on MARK_UNREAD 1`] = `
@@ -15,8 +19,12 @@ exports[`components/channel_notifications_modal/ExtraInfo should match snapshot,
`;
exports[`components/channel_notifications_modal/ExtraInfo should match snapshot, on PUSH 1`] = `
<MemoizedFormattedMessage
defaultMessage="Send mobile push notifications"
id="channel_notifications.push"
/>
<div
className="SectionTitle__wrapper"
>
<MemoizedFormattedMessage
defaultMessage="Mobile push notifications"
id="channel_notifications.push"
/>
</div>
`;

View File

@@ -16,6 +16,13 @@ type Props = {
isCollapsed?: boolean;
}
const defaultOption = (
<FormattedMessage
id='channel_notifications.defaultOption'
defaultMessage='(default)'
/>
);
export default function Describe({section, isCollapsed, memberNotifyLevel, globalNotifyLevel, ignoreChannelMentions, channelAutoFollowThreads}: Props) {
if (memberNotifyLevel === NotificationLevels.DEFAULT && globalNotifyLevel) {
t('channel_notifications.levels.default');
@@ -95,7 +102,8 @@ export default function Describe({section, isCollapsed, memberNotifyLevel, globa
return (
<FormattedMessage
id='channel_notifications.onlyMentions'
defaultMessage='Only for mentions'
defaultMessage='Only for mentions ({isDefault})'
values={{isDefault: globalNotifyLevel === NotificationLevels.MENTION ? defaultOption : <></>}}
/>
);
} else if (
@@ -105,7 +113,8 @@ export default function Describe({section, isCollapsed, memberNotifyLevel, globa
return (
<FormattedMessage
id='channel_notifications.allActivity'
defaultMessage='For all activity'
defaultMessage='For all activity ({isDefault})'
values={{isDefault: globalNotifyLevel === NotificationLevels.ALL ? defaultOption : <></>}}
/>
);
} else if (
@@ -123,7 +132,8 @@ export default function Describe({section, isCollapsed, memberNotifyLevel, globa
return (
<FormattedMessage
id='channel_notifications.never'
defaultMessage='Never'
defaultMessage='Never ({isDefault})'
values={{isDefault: globalNotifyLevel === NotificationLevels.NONE ? defaultOption : <></>}}
/>
);
}

View File

@@ -24,6 +24,7 @@ describe('components/channel_notifications_modal/ExpandView', () => {
onChangeThreads: jest.fn(),
onCollapseSection: jest.fn(),
onSubmit: jest.fn(),
onReset: jest.fn(),
};
test('should match snapshot, DESKTOP on expanded view', () => {

View File

@@ -1,41 +1,74 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {ChangeEvent} from 'react';
import React, {ChangeEvent, useMemo, useRef} from 'react';
import {FormattedMessage} from 'react-intl';
import {useSelector} from 'react-redux';
import ReactSelect, {ValueType} from 'react-select';
import {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
import {ChannelAutoFollowThreads, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
import {ChannelAutoFollowThreads, DesktopSound, IgnoreChannelMentions, NotificationLevels, NotificationSections} from 'utils/constants';
import SettingItemMax from 'components/setting_item_max';
import {ChannelNotifyProps} from '@mattermost/types/channels';
import {notificationSounds} from 'utils/notification_sounds';
import Describe from './describe';
import ExtraInfo from './extra_info';
import SectionTitle from './section_title';
type SelectedOption = {
label: string;
value: string;
};
type Props = {
ignoreChannelMentions?: string;
channelAutoFollowThreads?: string;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
onChangeThreads?: (e: ChangeEvent<HTMLInputElement>) => void;
onChangeDesktopSound?: (e: ChangeEvent<HTMLInputElement>) => void;
onChangeNotificationSound?: (selectedOption: ValueType<SelectedOption>) => void;
onCollapseSection: (section: string) => void;
onReset: () => void;
onSubmit: (setting?: string) => void;
isNotificationsSettingSameAsGlobal?: boolean;
globalNotifyLevel?: string;
globalNotificationSound?: ChannelNotifyProps['desktop_notification_sound'];
memberNotifyLevel: string;
memberThreadsNotifyLevel?: string;
memberDesktopSound?: string;
memberDesktopNotificationSound?: string;
section: string;
serverError?: string;
}
const sounds = Array.from(notificationSounds.keys());
const makeDefaultOptionLabel = (option: string) => `${option} (default)`;
const makeReactSelectValue = (option: string, isDefault: boolean) => {
return {value: option, label: isDefault ? makeDefaultOptionLabel(option) : option};
};
export default function ExpandView({
section,
memberNotifyLevel,
memberThreadsNotifyLevel,
memberDesktopSound,
memberDesktopNotificationSound,
globalNotifyLevel,
globalNotificationSound,
isNotificationsSettingSameAsGlobal,
onChange,
onChangeThreads,
onChangeDesktopSound,
onChangeNotificationSound,
onReset,
onSubmit,
serverError,
onCollapseSection,
@@ -44,32 +77,33 @@ export default function ExpandView({
}: Props) {
const isCRTEnabled = useSelector(isCollapsedThreadsEnabled);
const soundOptions = useMemo(() => sounds.map((sound) => {
return {value: sound, label: sound === globalNotificationSound ? makeDefaultOptionLabel(sound) : sound};
}), [globalNotificationSound]);
const dropdownSoundRef = useRef<ReactSelect>(null);
const inputs = [(
<div key='channel-notification-level-radio'>
{(section === NotificationSections.DESKTOP || section === NotificationSections.PUSH) &&
<fieldset>
<div className='radio'>
<label className=''>
<input
id='channelNotificationGlobalDefault'
name='channelDesktopNotifications'
type='radio'
value={NotificationLevels.DEFAULT}
checked={memberNotifyLevel === NotificationLevels.DEFAULT}
onChange={onChange}
/>
<Describe
section={section}
memberNotifyLevel={NotificationLevels.DEFAULT}
globalNotifyLevel={globalNotifyLevel}
/>
</label>
</div>
{ section === NotificationSections.DESKTOP && <legend className='form-legend'>
<FormattedMessage
id='channel_notifications.sendDesktop'
defaultMessage='Send desktop notifications'
/>
</legend>}
{ section === NotificationSections.PUSH && <legend className='form-legend'>
<FormattedMessage
id='channel_notifications.sendMobilePush'
defaultMessage='Send mobile push notifications'
/>
</legend>}
<div className='radio'>
<label className=''>
<input
id='channelNotificationAllActivity'
name='channelDesktopNotifications'
name='channelNotifications'
type='radio'
value={NotificationLevels.ALL}
checked={memberNotifyLevel === NotificationLevels.ALL}
@@ -78,6 +112,7 @@ export default function ExpandView({
<Describe
section={section}
memberNotifyLevel={NotificationLevels.ALL}
globalNotifyLevel={globalNotifyLevel}
/>
</label>
</div>
@@ -85,7 +120,7 @@ export default function ExpandView({
<label className=''>
<input
id='channelNotificationMentions'
name='channelDesktopNotifications'
name='channelNotifications'
type='radio'
value={NotificationLevels.MENTION}
checked={memberNotifyLevel === NotificationLevels.MENTION}
@@ -94,6 +129,7 @@ export default function ExpandView({
<Describe
section={section}
memberNotifyLevel={NotificationLevels.MENTION}
globalNotifyLevel={globalNotifyLevel}
/>
</label>
</div>
@@ -101,7 +137,7 @@ export default function ExpandView({
<label>
<input
id='channelNotificationNever'
name='channelDesktopNotifications'
name='channelNotifications'
type='radio'
value={NotificationLevels.NONE}
checked={memberNotifyLevel === NotificationLevels.NONE}
@@ -110,6 +146,7 @@ export default function ExpandView({
<Describe
section={section}
memberNotifyLevel={NotificationLevels.NONE}
globalNotifyLevel={globalNotifyLevel}
/>
</label>
</div>
@@ -272,6 +309,72 @@ export default function ExpandView({
</fieldset>
</>
}
{(section === NotificationSections.DESKTOP) && memberNotifyLevel !== NotificationLevels.NONE &&
<>
<hr/>
<fieldset>
<legend className='form-legend'>
<FormattedMessage
id='channel_notifications.sound'
defaultMessage='Notification sound'
/>
</legend>
<div className='radio'>
<label className=''>
<input
id='channelDesktopSoundOn'
name='channelDesktopSound'
type='radio'
value={DesktopSound.ON}
checked={memberDesktopSound === DesktopSound.ON}
onChange={onChangeDesktopSound}
/>
<FormattedMessage
id='channel_notifications.sound.on.title'
defaultMessage='On'
/>
</label>
</div>
<div className='radio'>
<label>
<input
id='channelDesktopSoundOff'
name='channelDesktopSound'
type='radio'
value={DesktopSound.OFF}
checked={memberDesktopSound === DesktopSound.OFF}
onChange={onChangeDesktopSound}
/>
<FormattedMessage
id='channel_notifications.sound.off.title'
defaultMessage='Off'
/>
</label>
</div>
{memberDesktopSound === DesktopSound.ON &&
<div className='pt-2'>
<ReactSelect
className='react-select notification-sound-dropdown'
classNamePrefix='react-select'
id='channelSoundNotification'
options={soundOptions}
clearable={false}
onChange={onChangeNotificationSound}
value={makeReactSelectValue(memberDesktopNotificationSound ?? '', memberDesktopNotificationSound === globalNotificationSound)}
isSearchable={false}
ref={dropdownSoundRef}
/>
</div>}
<div className='mt-5'>
<FormattedMessage
id='channel_notifications.sound_info'
defaultMessage='Notification sounds are available on Firefox, Edge, Safari, Chrome and Mattermost Desktop Apps.'
/>
</div>
</fieldset>
</>
}
{isCRTEnabled &&
section === NotificationSections.PUSH &&
memberNotifyLevel === NotificationLevels.MENTION &&
@@ -314,7 +417,13 @@ export default function ExpandView({
return (
<SettingItemMax
title={<SectionTitle section={section}/>}
title={
<SectionTitle
section={section}
isExpanded={true}
isNotificationsSettingSameAsGlobal={isNotificationsSettingSameAsGlobal}
onClickResetButton={onReset}
/>}
inputs={inputs}
submit={onSubmit}
serverError={serverError}

View File

@@ -17,7 +17,7 @@ export default function ExtraInfo({section}: Props) {
<span>
<FormattedMessage
id='channel_notifications.override'
defaultMessage='Selecting an option other than "Default" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.'
defaultMessage='Selecting an option other than the "default" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.'
/>
</span>
);
@@ -26,7 +26,7 @@ export default function ExtraInfo({section}: Props) {
<span>
<FormattedMessage
id='channel_notifications.overridePush'
defaultMessage='Selecting an option other than "Global default" will override the global notification settings for mobile push notifications in Settings. Push notifications must be enabled by the System Admin.'
defaultMessage='Selecting an option other than the "default" will override the global notification settings for mobile push notifications.'
/>
</span>
);

View File

@@ -27,6 +27,10 @@ export default class NotificationSection extends React.PureComponent {
*/
memberNotificationLevel: PropTypes.string.isRequired,
memberDesktopSound: PropTypes.string,
memberDesktopNotificationSound: PropTypes.string,
/**
* Member's desktop_threads notification level
*/
@@ -47,6 +51,11 @@ export default class NotificationSection extends React.PureComponent {
*/
globalNotificationLevel: PropTypes.string,
/**
* User's global notification sound
*/
globalNotificationSound: PropTypes.string,
/**
* onChange handles update of desktop notification level
*/
@@ -57,6 +66,14 @@ export default class NotificationSection extends React.PureComponent {
*/
onChangeThreads: PropTypes.func,
onChangeDesktopSound: PropTypes.func,
onChangeNotificationSound: PropTypes.func,
onReset: PropTypes.func,
isNotificationsSettingSameAsGlobal: PropTypes.bool,
/**
* Submit function to save notification level
*/
@@ -83,6 +100,16 @@ export default class NotificationSection extends React.PureComponent {
this.props.onChangeThreads(value);
};
handleOnChangeDesktopSound = (e) => {
this.props.onChangeDesktopSound(e.target.value);
};
handleOnChangeNotificationSound = (selectedOption) => {
if (selectedOption && 'value' in selectedOption) {
this.props.onChangeNotificationSound(selectedOption.value);
}
};
handleExpandSection = () => {
this.props.onUpdateSection(this.props.section);
};
@@ -95,11 +122,16 @@ export default class NotificationSection extends React.PureComponent {
const {
expand,
globalNotificationLevel,
globalNotificationSound,
memberNotificationLevel,
memberThreadsNotificationLevel,
memberDesktopSound,
memberDesktopNotificationSound,
ignoreChannelMentions,
isNotificationsSettingSameAsGlobal,
channelAutoFollowThreads,
onSubmit,
onReset,
section,
serverError,
} = this.props;
@@ -110,11 +142,18 @@ export default class NotificationSection extends React.PureComponent {
section={section}
memberNotifyLevel={memberNotificationLevel}
memberThreadsNotifyLevel={memberThreadsNotificationLevel}
memberDesktopSound={memberDesktopSound}
memberDesktopNotificationSound={memberDesktopNotificationSound}
globalNotifyLevel={globalNotificationLevel}
globalNotificationSound={globalNotificationSound}
ignoreChannelMentions={ignoreChannelMentions}
isNotificationsSettingSameAsGlobal={isNotificationsSettingSameAsGlobal}
channelAutoFollowThreads={channelAutoFollowThreads}
onChange={this.handleOnChange}
onReset={onReset}
onChangeThreads={this.handleOnChangeThreads}
onChangeDesktopSound={this.handleOnChangeDesktopSound}
onChangeNotificationSound={this.handleOnChangeNotificationSound}
onSubmit={onSubmit}
serverError={serverError}
onCollapseSection={this.handleCollapseSection}

View File

@@ -17,6 +17,7 @@ describe('components/channel_notifications_modal/NotificationSection', () => {
globalNotificationLevel: NotificationLevels.DEFAULT,
onChange: () => {}, //eslint-disable-line no-empty-function
onChangeThreads: () => {}, //eslint-disable-line no-empty-function
onReset: () => {},
onSubmit: () => {}, //eslint-disable-line no-empty-function
onUpdateSection: () => {}, //eslint-disable-line no-empty-function
serverError: '',

View File

@@ -0,0 +1,15 @@
@import 'sass/utils/mixins';
.SectionTitle {
&__wrapper {
display: flex;
justify-content: space-between;
}
&__resetButton {
@include button-style--none;
display: flex;
align-items: center;
}
}

View File

@@ -6,24 +6,43 @@ import {FormattedMessage} from 'react-intl';
import {NotificationSections} from 'utils/constants';
import './section_title.scss';
type Props = {
section: string;
isExpanded?: boolean;
isNotificationsSettingSameAsGlobal?: boolean;
onClickResetButton?: () => void;
}
export default function SectionTitle({section}: Props) {
if (section === NotificationSections.DESKTOP) {
export default function SectionTitle({section, isExpanded, isNotificationsSettingSameAsGlobal, onClickResetButton}: Props) {
if (section === NotificationSections.DESKTOP || section === NotificationSections.PUSH) {
return (
<FormattedMessage
id='channel_notifications.sendDesktop'
defaultMessage='Send desktop notifications'
/>
);
} else if (section === NotificationSections.PUSH) {
return (
<FormattedMessage
id='channel_notifications.push'
defaultMessage='Send mobile push notifications'
/>
<div className='SectionTitle__wrapper'>
{section === NotificationSections.DESKTOP &&
<FormattedMessage
id='channel_notifications.desktopNotifications'
defaultMessage='Desktop notifications'
/>}
{section === NotificationSections.PUSH &&
<FormattedMessage
id='channel_notifications.push'
defaultMessage='Mobile push notifications'
/>}
{isExpanded && !isNotificationsSettingSameAsGlobal &&
<button
className='SectionTitle__resetButton color--link'
onClick={onClickResetButton}
>
<i className='icon icon-refresh'/>
<FormattedMessage
id='channel_notifications.resetToDefaults'
defaultMessage='Reset to defaults'
/>
</button>
}
</div>
);
} else if (section === NotificationSections.MARK_UNREAD) {
return (

View File

@@ -31,11 +31,17 @@ jest.mock('actions/global_actions', () => ({
redirectUserToDefaultTeam: jest.fn(),
}));
jest.mock('utils/utils', () => ({
localizeMessage: () => {},
applyTheme: jest.fn(),
makeIsEligibleForClick: jest.fn(),
}));
jest.mock('utils/utils', () => {
const original = jest.requireActual('utils/utils');
return {
...original,
localizeMessage: () => {},
applyTheme: jest.fn(),
makeIsEligibleForClick: jest.fn(),
};
});
jest.mock('mattermost-redux/actions/general', () => ({
setUrl: () => {},

View File

@@ -2995,6 +2995,8 @@
"channel_notifications.channelAutoFollowThreads.help": "When enabled, you will auto-follow all new threads created in this channel unless you unfollow a thread explicitly.",
"channel_notifications.channelAutoFollowThreads.off.title": "Off",
"channel_notifications.channelAutoFollowThreads.on.title": "On",
"channel_notifications.defaultOption": "(default)",
"channel_notifications.desktopNotifications": "Desktop notifications",
"channel_notifications.globalDefault": "Global default ({notifyLevel})",
"channel_notifications.ignoreChannelMentions": "Ignore mentions for @channel, @here and @all",
"channel_notifications.ignoreChannelMentions.help": "When enabled, @channel, @here and @all will not trigger mentions or mention notifications in this channel.",
@@ -3009,13 +3011,19 @@
"channel_notifications.muteChannel.on.title": "On",
"channel_notifications.muteChannel.on.title.collapse": "Mute is enabled. Desktop, email and push notifications will not be sent for this channel.",
"channel_notifications.muteChannel.settings": "Mute channel",
"channel_notifications.never": "Never",
"channel_notifications.onlyMentions": "Only for mentions",
"channel_notifications.override": "Selecting an option other than \"Default\" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.",
"channel_notifications.overridePush": "Selecting an option other than \"Global default\" will override the global notification settings for mobile push notifications in settings. Push notifications must be enabled by the System Admin.",
"channel_notifications.never": "Never {isDefault}",
"channel_notifications.onlyMentions": "Only for mentions {isDefault}",
"channel_notifications.override": "Selecting an option other than the \"default\" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.",
"channel_notifications.overridePush": "Selecting an option other than the \"default\" will override the global notification settings for mobile push notifications. ",
"channel_notifications.preferences": "Notification Preferences for ",
"channel_notifications.push": "Send mobile push notifications",
"channel_notifications.sendDesktop": "Send desktop notifications",
"channel_notifications.push": "Mobile push notifications",
"channel_notifications.resetToDefaults": "Reset to defaults",
"channel_notifications.sendDesktop": "Send Desktop notifications",
"channel_notifications.sendMobilePush": "Send mobile push notifications",
"channel_notifications.sound": "Notification sound",
"channel_notifications.sound_info": "Notification sounds are available on Firefox, Edge, Safari, Chrome and Mattermost Desktop Apps.",
"channel_notifications.sound.off.title": "Off",
"channel_notifications.sound.on.title": "On",
"channel_select.placeholder": "--- Select a channel ---",
"channel_switch_modal.deactivated": "Deactivated",
"channel_toggle_button.private": "Private",

View File

@@ -449,6 +449,7 @@ class TestHelper {
fakeChannelNotifyProps = (override: Partial<ChannelNotifyProps>): ChannelNotifyProps => {
return {
desktop: 'default',
desktop_sound: 'off',
email: 'default',
mark_unread: 'mention',
push: 'default',

View File

@@ -998,6 +998,11 @@ export const NotificationLevels = {
NONE: 'none',
} as const;
export const DesktopSound = {
ON: 'on',
OFF: 'off',
} as const;
export const IgnoreChannelMentions = {
ON: 'on',
OFF: 'off',

View File

@@ -21,6 +21,8 @@ export type ChannelStats = {
export type ChannelNotifyProps = {
desktop: 'default' | 'all' | 'mention' | 'none';
desktop_sound: 'on' | 'off';
desktop_notification_sound?: 'Bing' | 'Crackle' | 'Down' | 'Hello' | 'Ripple' | 'Upstairs';
email: 'default' | 'all' | 'mention' | 'none';
mark_unread: 'all' | 'mention';
push: 'default' | 'all' | 'mention' | 'none';