[MM-61066] Add support for disabled notifications in desktop (#28739)

Этот коммит содержится в:
M-ZubairAhmed
2024-11-27 06:32:23 +00:00
коммит произвёл GitHub
родитель 48c14af280
Коммит fe5756225c
10 изменённых файлов: 190 добавлений и 1 удалений

Просмотреть файл

@@ -8,6 +8,7 @@ import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import NotificationPermissionNeverGrantedBar from 'components/announcement_bar/notification_permission_bar/notification_permission_never_granted_bar';
import NotificationPermissionUnsupportedBar from 'components/announcement_bar/notification_permission_bar/notification_permission_unsupported_bar';
import {useDesktopAppNotificationPermission} from 'components/common/hooks/use_desktop_notification_permission';
import {
isNotificationAPISupported,
@@ -19,6 +20,8 @@ import {
export default function NotificationPermissionBar() {
const isLoggedIn = Boolean(useSelector(getCurrentUserId));
useDesktopAppNotificationPermission();
if (!isLoggedIn) {
return null;
}

Просмотреть файл

@@ -0,0 +1,52 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useCallback, useEffect, useState} from 'react';
import type {NotificationPermissionNeverGranted} from 'utils/notifications';
import {isNotificationAPISupported} from 'utils/notifications';
import {isDesktopApp} from 'utils/user_agent';
export type DesktopNotificationPermission = Exclude<NotificationPermission, typeof NotificationPermissionNeverGranted> | undefined;
// We store the permission state globally here to avoid calling requestPermission() multiple times
let desktopNotificationPermissionGlobalState: DesktopNotificationPermission | undefined;
// This is used to request notification permission for desktop app
// it also returns the permission state. We use this as a workaround for bug with Electron - https://github.com/electron/electron/issues/11221
// tl;dr Electron always show 'granted' when queries for Notification.permission, hence this workaround
export function useDesktopAppNotificationPermission(): [DesktopNotificationPermission, () => Promise<NotificationPermission>] {
const [desktopNotificationPermission, setDesktopNotificationPermission] = useState<DesktopNotificationPermission>(undefined);
const isDesktop = isDesktopApp();
const isSupported = isNotificationAPISupported();
const requestDesktopNotificationPermission = useCallback(async () => {
// Based on Electron's notification permission it will have following states
// - allowed - No further action needed
// - denied permanently - No further action needed
// - denied (temporary) - In this case, electron notification permission dialog is shown with requestPermission()
const permission = await Notification.requestPermission();
// Update the global state
desktopNotificationPermissionGlobalState = permission as DesktopNotificationPermission;
// Update the local state
setDesktopNotificationPermission(permission as DesktopNotificationPermission);
return permission;
}, []);
useEffect(() => {
if (!isDesktop || !isSupported) {
setDesktopNotificationPermission(undefined);
} else if (desktopNotificationPermissionGlobalState === undefined) {
// We are in initial state, we need to request permission now
requestDesktopNotificationPermission();
} else if (desktopNotificationPermissionGlobalState !== undefined) {
setDesktopNotificationPermission(desktopNotificationPermissionGlobalState);
}
}, [isDesktop, isSupported, requestDesktopNotificationPermission]);
return [desktopNotificationPermission, requestDesktopNotificationPermission];
}

Просмотреть файл

@@ -21,6 +21,7 @@ const SectionNoticeButton = ({
<button
onClick={button.onClick}
className={classNames('btn btn-sm sectionNoticeButton', buttonClass)}
disabled={button.disabled}
>
{button.loading && (<i className='icon fa fa-pulse fa-spinner'/>)}
{leading}

Просмотреть файл

@@ -7,4 +7,5 @@ export type SectionNoticeButtonProp = {
trailingIcon?: string;
leadingIcon?: string;
loading?: boolean;
disabled?: boolean;
}

Просмотреть файл

@@ -3,6 +3,9 @@
import React from 'react';
import * as useDesktopAppNotificationPermission from 'components/common/hooks/use_desktop_notification_permission';
import type {DesktopNotificationPermission} from 'components/common/hooks/use_desktop_notification_permission';
import {renderWithContext, screen} from 'tests/react_testing_utils';
import * as utilsNotifications from 'utils/notifications';
@@ -47,4 +50,22 @@ describe('NotificationPermissionSectionNotice', () => {
expect(container).toBeEmptyDOMElement();
});
test('should render "Desktop denied" notice when desktop permission is denied', () => {
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
jest.spyOn(useDesktopAppNotificationPermission, 'useDesktopAppNotificationPermission').mockReturnValue([utilsNotifications.NotificationPermissionDenied as DesktopNotificationPermission, jest.fn()]);
renderWithContext(<NotificationPermissionSectionNotice/>);
expect(screen.getByText('Desktop notifications permission required')).toBeInTheDocument();
});
test('should render nothing when desktop permission is granted', () => {
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
jest.spyOn(useDesktopAppNotificationPermission, 'useDesktopAppNotificationPermission').mockReturnValue([utilsNotifications.NotificationPermissionGranted as DesktopNotificationPermission, jest.fn()]);
const {container} = renderWithContext(<NotificationPermissionSectionNotice/>);
expect(container).toBeEmptyDOMElement();
});
});

Просмотреть файл

@@ -3,17 +3,22 @@
import React, {useState} from 'react';
import {useDesktopAppNotificationPermission} from 'components/common/hooks/use_desktop_notification_permission';
import NotificationPermissionDeniedNotice from 'components/user_settings/notifications/desktop_and_mobile_notification_setting/notification_permission_section_notice/notification_permission_denied_section_notice';
import NotificationPermissionNeverGrantedNotice from 'components/user_settings/notifications/desktop_and_mobile_notification_setting/notification_permission_section_notice/notification_permission_never_granted_section_notice';
import NotificationPermissionUnsupportedSectionNotice from 'components/user_settings/notifications/desktop_and_mobile_notification_setting/notification_permission_section_notice/notification_permission_unsupported_section_notice';
import {getNotificationPermission, isNotificationAPISupported, NotificationPermissionDenied, NotificationPermissionNeverGranted} from 'utils/notifications';
import NotificationPermissionDesktopDeniedSectionNotice from './notification_permission_desktop_denied_section_notice';
export default function NotificationPermissionSectionNotice() {
const isNotificationSupported = isNotificationAPISupported();
const [notificationPermission, setNotificationPermission] = useState(getNotificationPermission());
const [desktopNotificationPermission, requestDesktopNotificationPermission] = useDesktopAppNotificationPermission();
function handleRequestNotificationClicked(permission: NotificationPermission) {
setNotificationPermission(permission);
}
@@ -22,6 +27,10 @@ export default function NotificationPermissionSectionNotice() {
return <NotificationPermissionUnsupportedSectionNotice/>;
}
if (desktopNotificationPermission === NotificationPermissionDenied) {
return <NotificationPermissionDesktopDeniedSectionNotice requestDesktopNotificationPermission={requestDesktopNotificationPermission}/>;
}
if (isNotificationSupported && notificationPermission === NotificationPermissionNeverGranted) {
return <NotificationPermissionNeverGrantedNotice onCtaButtonClick={handleRequestNotificationClicked}/>;
}

Просмотреть файл

@@ -0,0 +1,71 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useState} from 'react';
import {useIntl} from 'react-intl';
import SectionNotice from 'components/section_notice';
import {NotificationPermissionDenied} from 'utils/notifications';
interface Props {
requestDesktopNotificationPermission: () => Promise<NotificationPermission>;
}
export default function NotificationPermissionDesktopDeniedSectionNotice(props: Props) {
const intl = useIntl();
const [checkedPermissionDenied, setCheckedPermissionDenied] = useState(false);
async function handleCheckPermissionButtonClick() {
const permission = await props.requestDesktopNotificationPermission();
if (permission === NotificationPermissionDenied) {
setCheckedPermissionDenied(true);
}
}
const handleInstructionButtonClick = useCallback(() => {
window.open('https://mattermost.com/pl/manage-notifications', '_blank', 'noopener,noreferrer');
}, []);
const title = checkedPermissionDenied ? intl.formatMessage({
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionDeniedDesktop.titleDenied',
defaultMessage: 'Desktop notifications permission was denied',
}) : intl.formatMessage({
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionDeniedDesktop.title',
defaultMessage: 'Desktop notifications permission required',
});
const text = checkedPermissionDenied ? intl.formatMessage({
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionDeniedDesktop.messageDenied',
defaultMessage: 'Notifications for this Mattermost server are blocked. To receive notifications, please enable them manually.',
}) : intl.formatMessage({
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionDeniedDesktop.message',
defaultMessage: "You're missing important message and call notifications from Mattermost. To start receiving them, please enable them manually.",
});
return (
<div className='extraContentBeforeSettingList'>
<SectionNotice
type='danger'
title={title}
text={text}
primaryButton={{
text: intl.formatMessage({
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionDeniedDesktop.checkPermissionButton',
defaultMessage: 'Check permission',
}),
onClick: handleCheckPermissionButtonClick,
disabled: checkedPermissionDenied,
}}
tertiaryButton={{
text: intl.formatMessage({
id: 'user.settings.notifications.desktopAndMobile.notificationSection.permissionDenied.instructionButton',
defaultMessage: 'How to enable notifications',
}),
onClick: handleInstructionButtonClick,
}}
/>
</div>
);
}

Просмотреть файл

@@ -3,6 +3,9 @@
import React from 'react';
import * as useDesktopAppNotificationPermission from 'components/common/hooks/use_desktop_notification_permission';
import type {DesktopNotificationPermission} from 'components/common/hooks/use_desktop_notification_permission';
import {renderWithContext, screen} from 'tests/react_testing_utils';
import * as utilsNotifications from 'utils/notifications';
@@ -47,4 +50,22 @@ describe('NotificationPermissionTitleTag', () => {
expect(container).toBeEmptyDOMElement();
});
test('should render "Permission required" tag when desktop permission is denied', () => {
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
jest.spyOn(useDesktopAppNotificationPermission, 'useDesktopAppNotificationPermission').mockReturnValue([utilsNotifications.NotificationPermissionDenied as DesktopNotificationPermission, jest.fn()]);
renderWithContext(<NotificationPermissionTitleTag/>);
expect(screen.queryByText('Permission required')).toBeInTheDocument();
});
test('should render nothing when desktop permission is granted', () => {
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
jest.spyOn(useDesktopAppNotificationPermission, 'useDesktopAppNotificationPermission').mockReturnValue([utilsNotifications.NotificationPermissionGranted as DesktopNotificationPermission, jest.fn()]);
const {container} = renderWithContext(<NotificationPermissionTitleTag/>);
expect(container).toBeEmptyDOMElement();
});
});

Просмотреть файл

@@ -4,6 +4,7 @@
import React from 'react';
import {useIntl} from 'react-intl';
import {useDesktopAppNotificationPermission} from 'components/common/hooks/use_desktop_notification_permission';
import Tag from 'components/widgets/tag/tag';
import {
@@ -16,6 +17,8 @@ import {
export default function NotificationPermissionTitleTag() {
const {formatMessage} = useIntl();
const [desktopNotificationPermission] = useDesktopAppNotificationPermission();
if (!isNotificationAPISupported()) {
return (
<Tag
@@ -32,7 +35,8 @@ export default function NotificationPermissionTitleTag() {
if (
getNotificationPermission() === NotificationPermissionNeverGranted ||
getNotificationPermission() === NotificationPermissionDenied
getNotificationPermission() === NotificationPermissionDenied ||
desktopNotificationPermission === NotificationPermissionDenied
) {
return (
<Tag

Просмотреть файл

@@ -5723,8 +5723,14 @@
"user.settings.notifications.desktopAndMobile.nothing": "Nothing",
"user.settings.notifications.desktopAndMobile.notificationSection.noPermissionIssueTag": "Not supported",
"user.settings.notifications.desktopAndMobile.notificationSection.permissionDenied.button": "How to enable notifications",
"user.settings.notifications.desktopAndMobile.notificationSection.permissionDenied.instructionButton": "How to enable notifications",
"user.settings.notifications.desktopAndMobile.notificationSection.permissionDenied.message": "You're missing important message and call notifications from Mattermost. To start receiving notifications, please enable notifications for Mattermost in your browser settings.",
"user.settings.notifications.desktopAndMobile.notificationSection.permissionDenied.title": "Browser notification permission was denied",
"user.settings.notifications.desktopAndMobile.notificationSection.permissionDeniedDesktop.checkPermissionButton": "Check permission",
"user.settings.notifications.desktopAndMobile.notificationSection.permissionDeniedDesktop.message": "You're missing important message and call notifications from Mattermost. To start receiving them, please enable them manually.",
"user.settings.notifications.desktopAndMobile.notificationSection.permissionDeniedDesktop.messageDenied": "Notifications for this Mattermost server are blocked. To receive notifications, please enable them manually.",
"user.settings.notifications.desktopAndMobile.notificationSection.permissionDeniedDesktop.title": "Desktop notifications permission required",
"user.settings.notifications.desktopAndMobile.notificationSection.permissionDeniedDesktop.titleDenied": "Desktop notifications permission was denied",
"user.settings.notifications.desktopAndMobile.notificationSection.permissionIssueTag": "Permission required",
"user.settings.notifications.desktopAndMobile.notificationSection.permissionNeverGranted.button": "Enable notifications",
"user.settings.notifications.desktopAndMobile.notificationSection.permissionNeverGranted.message": "You're missing important message and call notifications from Mattermost. Mattermost notifications are disabled by this browser.",