From fbeae84a98841731cbc1e602a808bb6a80b54946 Mon Sep 17 00:00:00 2001 From: damencho Date: Fri, 7 Feb 2025 14:36:46 -0600 Subject: [PATCH] feat(notifications): Make all error notifications sticky. There are many cases where the error disappears and users easily miss the information. --- conference.js | 4 ++-- react/features/base/conference/middleware.any.ts | 10 +++++----- react/features/base/tracks/actions.any.ts | 2 +- react/features/base/tracks/actions.web.ts | 12 +++++------- .../add-people-dialog/AbstractAddPeopleDialog.tsx | 2 +- react/features/noise-suppression/actions.ts | 3 +-- react/features/notifications/actions.ts | 2 +- .../features/old-client-notification/middleware.tsx | 3 +-- react/features/prejoin/actions.web.ts | 9 ++++----- react/features/recording/actions.any.ts | 4 ++-- .../Recording/AbstractStartRecordingDialog.ts | 3 +-- react/features/recording/middleware.ts | 2 +- react/features/room-lock/middleware.ts | 2 +- react/features/transcribing/middleware.ts | 3 +-- react/features/videosipgw/middleware.ts | 6 +++--- 15 files changed, 30 insertions(+), 37 deletions(-) diff --git a/conference.js b/conference.js index fd92ebc464fb..d7670f9df809 100644 --- a/conference.js +++ b/conference.js @@ -287,7 +287,7 @@ class ConferenceConnector { }, descriptionKey: 'dialog.reservationErrorMsg', titleKey: 'dialog.reservationError' - }, NOTIFICATION_TIMEOUT_TYPE.LONG)); + })); break; } @@ -295,7 +295,7 @@ class ConferenceConnector { APP.store.dispatch(showErrorNotification({ descriptionKey: 'dialog.gracefulShutdown', titleKey: 'dialog.serviceUnavailable' - }, NOTIFICATION_TIMEOUT_TYPE.LONG)); + })); break; // FIXME FOCUS_DISCONNECTED is a confusing event name. diff --git a/react/features/base/conference/middleware.any.ts b/react/features/base/conference/middleware.any.ts index 191016f92d7d..faf394efb47d 100644 --- a/react/features/base/conference/middleware.any.ts +++ b/react/features/base/conference/middleware.any.ts @@ -177,7 +177,7 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio dispatch(showErrorNotification({ description: 'Restart initiated because of a bridge failure', titleKey: 'dialog.sessionRestarted' - }, NOTIFICATION_TIMEOUT_TYPE.LONG)); + })); } break; @@ -190,7 +190,7 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio descriptionArguments: { msg }, descriptionKey: msg ? 'dialog.connectErrorWithMsg' : 'dialog.connectError', titleKey: 'connection.CONNFAIL' - }, NOTIFICATION_TIMEOUT_TYPE.LONG)); + })); break; } @@ -199,7 +199,7 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio hideErrorSupportLink: true, descriptionKey: 'dialog.maxUsersLimitReached', titleKey: 'dialog.maxUsersLimitReachedTitle' - }, NOTIFICATION_TIMEOUT_TYPE.LONG)); + })); // In case of max users(it can be from a visitor node), let's restore // oldConfig if any as we will be back to the main prosody. @@ -236,7 +236,7 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio descriptionKey, hideErrorSupportLink: true, titleKey - }, NOTIFICATION_TIMEOUT_TYPE.STICKY)); + })); sendAnalytics(createNotAllowedErrorEvent(type, msg)); @@ -416,7 +416,7 @@ function _connectionFailed({ dispatch, getState }: IStore, next: Function, actio descriptionKey: errors ? 'dialog.tokenAuthFailedWithReasons' : 'dialog.tokenAuthFailed', descriptionArguments: { reason: errors }, titleKey: 'dialog.tokenAuthFailedTitle' - }, NOTIFICATION_TIMEOUT_TYPE.STICKY)); + })); } } diff --git a/react/features/base/tracks/actions.any.ts b/react/features/base/tracks/actions.any.ts index e6a335bcb746..cde9700814c3 100644 --- a/react/features/base/tracks/actions.any.ts +++ b/react/features/base/tracks/actions.any.ts @@ -291,7 +291,7 @@ export function showNoDataFromSourceVideoError(jitsiTrack: any) { const notificationAction = dispatch(showErrorNotification({ descriptionKey: 'dialog.cameraNotSendingData', titleKey: 'dialog.cameraNotSendingDataTitle' - }, NOTIFICATION_TIMEOUT_TYPE.LONG)); + })); notificationInfo = { uid: notificationAction?.uid diff --git a/react/features/base/tracks/actions.web.ts b/react/features/base/tracks/actions.web.ts index 25320aa5007a..6377067e30ad 100644 --- a/react/features/base/tracks/actions.web.ts +++ b/react/features/base/tracks/actions.web.ts @@ -157,7 +157,7 @@ async function _toggleScreenSharing( try { tracks = await createLocalTracksF(options) as any[]; } catch (error) { - dispatch(handleScreenSharingError(error, NOTIFICATION_TIMEOUT_TYPE.MEDIUM)); + dispatch(handleScreenSharingError(error)); throw error; } @@ -171,7 +171,7 @@ async function _toggleScreenSharing( desktopVideoTrack.dispose(); if (!desktopAudioTrack) { - dispatch(handleScreenSharingError(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK, NOTIFICATION_TIMEOUT_TYPE.MEDIUM)); + dispatch(handleScreenSharingError(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK)); throw new Error(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK); } @@ -457,7 +457,7 @@ export function displayErrorsForCreateInitialLocalTracks(errors: IInitialTracksE } = errors; if (screenSharingError) { - dispatch(handleScreenSharingError(screenSharingError, NOTIFICATION_TIMEOUT_TYPE.LONG)); + dispatch(handleScreenSharingError(screenSharingError)); } if (audioOnlyError || videoOnlyError) { if (audioOnlyError) { @@ -476,12 +476,10 @@ export function displayErrorsForCreateInitialLocalTracks(errors: IInitialTracksE * * @private * @param {Error | AUDIO_ONLY_SCREEN_SHARE_NO_TRACK} error - The error. - * @param {NOTIFICATION_TIMEOUT_TYPE} timeout - The time for showing the notification. * @returns {Function} */ export function handleScreenSharingError( - error: Error | AUDIO_ONLY_SCREEN_SHARE_NO_TRACK, - timeout: NOTIFICATION_TIMEOUT_TYPE) { + error: Error | AUDIO_ONLY_SCREEN_SHARE_NO_TRACK) { return (dispatch: IStore['dispatch']) => { logger.error('failed to share local desktop', error); @@ -508,6 +506,6 @@ export function handleScreenSharingError( dispatch(showErrorNotification({ descriptionKey, titleKey - }, timeout)); + })); }; } diff --git a/react/features/invite/components/add-people-dialog/AbstractAddPeopleDialog.tsx b/react/features/invite/components/add-people-dialog/AbstractAddPeopleDialog.tsx index 98b0fd8f82b1..fa66da985e8e 100644 --- a/react/features/invite/components/add-people-dialog/AbstractAddPeopleDialog.tsx +++ b/react/features/invite/components/add-people-dialog/AbstractAddPeopleDialog.tsx @@ -190,7 +190,7 @@ export default class AbstractAddPeopleDialog

})); dispatch(showErrorNotification({ titleKey: 'addPeople.failedToAdd' - }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM)); + })); } else if (!_callFlowsEnabled) { const invitedCount = invitees.length; let notificationProps: INotificationProps | undefined; diff --git a/react/features/noise-suppression/actions.ts b/react/features/noise-suppression/actions.ts index 40ff51dd5221..e2e6aafd6aa7 100644 --- a/react/features/noise-suppression/actions.ts +++ b/react/features/noise-suppression/actions.ts @@ -1,7 +1,6 @@ import { IStore } from '../app/types'; import { getLocalJitsiAudioTrack } from '../base/tracks/functions'; import { showErrorNotification } from '../notifications/actions'; -import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants'; import { NoiseSuppressionEffect } from '../stream-effects/noise-suppression/NoiseSuppressionEffect'; import { SET_NOISE_SUPPRESSION_ENABLED } from './actionTypes'; @@ -93,7 +92,7 @@ export function setNoiseSuppressionEnabled(enabled: boolean): any { dispatch(showErrorNotification({ titleKey: 'notify.noiseSuppressionFailedTitle' - }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM)); + })); } }; } diff --git a/react/features/notifications/actions.ts b/react/features/notifications/actions.ts index a92752ec8372..adebd8281c4a 100644 --- a/react/features/notifications/actions.ts +++ b/react/features/notifications/actions.ts @@ -97,7 +97,7 @@ export function setNotificationsEnabled(enabled: boolean) { * @param {string} type - Notification type. * @returns {Object} */ -export function showErrorNotification(props: INotificationProps, type?: string) { +export function showErrorNotification(props: INotificationProps, type = NOTIFICATION_TIMEOUT_TYPE.STICKY) { return showNotification({ ...props, appearance: NOTIFICATION_TYPE.ERROR diff --git a/react/features/old-client-notification/middleware.tsx b/react/features/old-client-notification/middleware.tsx index 1b97763ee663..48da757f9368 100644 --- a/react/features/old-client-notification/middleware.tsx +++ b/react/features/old-client-notification/middleware.tsx @@ -5,7 +5,6 @@ import { IStore } from '../app/types'; import { APP_WILL_MOUNT } from '../base/app/actionTypes'; import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; import { showErrorNotification } from '../notifications/actions'; -import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants'; import OldElectronAPPNotificationDescription from './components/OldElectronAPPNotificationDescription'; import { isOldJitsiMeetElectronApp } from './functions'; @@ -35,7 +34,7 @@ function _appWillMount(store: IStore, next: Function, action: AnyAction) { dispatch(showErrorNotification({ titleKey: 'notify.OldElectronAPPTitle', description: - }, NOTIFICATION_TIMEOUT_TYPE.LONG)); + })); } return next(action); diff --git a/react/features/prejoin/actions.web.ts b/react/features/prejoin/actions.web.ts index 067f675812f3..59da0d3ab34c 100644 --- a/react/features/prejoin/actions.web.ts +++ b/react/features/prejoin/actions.web.ts @@ -16,7 +16,6 @@ import { import { openURLInBrowser } from '../base/util/openURLInBrowser'; import { executeDialOutRequest, executeDialOutStatusRequest, getDialInfoPageURL } from '../invite/functions'; import { showErrorNotification } from '../notifications/actions'; -import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants'; import { INotificationProps } from '../notifications/types'; import { @@ -108,7 +107,7 @@ function pollForStatus( case DIAL_OUT_STATUS.DISCONNECTED: { dispatch(showErrorNotification({ titleKey: 'prejoin.errorDialOutDisconnected' - }, NOTIFICATION_TIMEOUT_TYPE.LONG)); + })); return onFail(); } @@ -116,7 +115,7 @@ function pollForStatus( case DIAL_OUT_STATUS.FAILED: { dispatch(showErrorNotification({ titleKey: 'prejoin.errorDialOutFailed' - }, NOTIFICATION_TIMEOUT_TYPE.LONG)); + })); return onFail(); } @@ -124,7 +123,7 @@ function pollForStatus( } catch (err) { dispatch(showErrorNotification({ titleKey: 'prejoin.errorDialOutStatus' - }, NOTIFICATION_TIMEOUT_TYPE.LONG)); + })); logger.error('Error getting dial out status', err); onFail(); } @@ -177,7 +176,7 @@ export function dialOut(onSuccess: Function, onFail: Function) { } } - dispatch(showErrorNotification(notification, NOTIFICATION_TIMEOUT_TYPE.LONG)); + dispatch(showErrorNotification(notification)); logger.error('Error dialing out', err); onFail(); } diff --git a/react/features/recording/actions.any.ts b/react/features/recording/actions.any.ts index cbf7510cffa3..49ad77155bc9 100644 --- a/react/features/recording/actions.any.ts +++ b/react/features/recording/actions.any.ts @@ -189,7 +189,7 @@ export function highlightMeetingMoment() { * @returns {showErrorNotification} */ export function showRecordingError(props: Object) { - return showErrorNotification(props, NOTIFICATION_TIMEOUT_TYPE.LONG); + return showErrorNotification(props); } /** @@ -301,7 +301,7 @@ export function showStartedRecordingNotification( } catch (err) { dispatch(showErrorNotification({ titleKey: 'recording.errorFetchingLink' - }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM)); + })); return logger.error('Could not fetch recording link', err); } diff --git a/react/features/recording/components/Recording/AbstractStartRecordingDialog.ts b/react/features/recording/components/Recording/AbstractStartRecordingDialog.ts index ec96ee4abcb0..7b45099d0356 100644 --- a/react/features/recording/components/Recording/AbstractStartRecordingDialog.ts +++ b/react/features/recording/components/Recording/AbstractStartRecordingDialog.ts @@ -9,7 +9,6 @@ import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet'; import { updateDropboxToken } from '../../../dropbox/actions'; import { getDropboxData, getNewAccessToken, isEnabled as isDropboxEnabled } from '../../../dropbox/functions.any'; import { showErrorNotification } from '../../../notifications/actions'; -import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications/constants'; import { setRequestingSubtitles } from '../../../subtitles/actions.any'; import { setSelectedRecordingService, startLocalVideoRecording } from '../../actions'; import { RECORDING_METADATA_ID, RECORDING_TYPES } from '../../constants'; @@ -381,7 +380,7 @@ class AbstractStartRecordingDialog extends Component { } else { dispatch(showErrorNotification({ titleKey: 'dialog.noDropboxToken' - }, NOTIFICATION_TIMEOUT_TYPE.LONG)); + })); return; } diff --git a/react/features/recording/middleware.ts b/react/features/recording/middleware.ts index bce721481703..61f9dc07209d 100644 --- a/react/features/recording/middleware.ts +++ b/react/features/recording/middleware.ts @@ -162,7 +162,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { false, 'local', err.message, isRecorderTranscriptionsRunning(getState())); } - dispatch(showErrorNotification(props, NOTIFICATION_TIMEOUT_TYPE.MEDIUM)); + dispatch(showErrorNotification(props)); }); break; } diff --git a/react/features/room-lock/middleware.ts b/react/features/room-lock/middleware.ts index 61a24e217746..f73c770403ed 100644 --- a/react/features/room-lock/middleware.ts +++ b/react/features/room-lock/middleware.ts @@ -142,7 +142,7 @@ function _setPasswordFailed(store: IStore, next: Function, action: AnyAction) { APP.store.dispatch(showErrorNotification({ descriptionKey, titleKey - }, NOTIFICATION_TIMEOUT_TYPE.LONG)); + })); } return next(action); diff --git a/react/features/transcribing/middleware.ts b/react/features/transcribing/middleware.ts index e575a0443ec3..a0ffe284b0e0 100644 --- a/react/features/transcribing/middleware.ts +++ b/react/features/transcribing/middleware.ts @@ -1,6 +1,5 @@ import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; import { showErrorNotification } from '../notifications/actions'; -import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants'; import { TRANSCRIBER_LEFT } from './actionTypes'; import './subscriber'; @@ -17,7 +16,7 @@ MiddlewareRegistry.register(({ dispatch }) => next => action => { if (action.abruptly) { dispatch(showErrorNotification({ titleKey: 'transcribing.failed' - }, NOTIFICATION_TIMEOUT_TYPE.LONG)); + })); } break; } diff --git a/react/features/videosipgw/middleware.ts b/react/features/videosipgw/middleware.ts index dfd814ea19f9..e3efda480417 100644 --- a/react/features/videosipgw/middleware.ts +++ b/react/features/videosipgw/middleware.ts @@ -105,7 +105,7 @@ function _inviteRooms(rooms: ISipRoom[], conference: IJitsiConference, dispatch: dispatch(showErrorNotification({ descriptionKey: 'videoSIPGW.errorInvite', titleKey: 'videoSIPGW.errorInviteTitle' - }, NOTIFICATION_TIMEOUT_TYPE.LONG)); + })); return; } @@ -159,14 +159,14 @@ function _sessionStateChanged( displayName: event.displayName }, descriptionKey: 'videoSIPGW.errorInviteFailed' - }, NOTIFICATION_TIMEOUT_TYPE.LONG); + }); } case JitsiSIPVideoGWStatus.STATE_OFF: { if (event.failureReason === JitsiSIPVideoGWStatus.STATUS_BUSY) { return showErrorNotification({ descriptionKey: 'videoSIPGW.busy', titleKey: 'videoSIPGW.busyTitle' - }, NOTIFICATION_TIMEOUT_TYPE.LONG); + }); } else if (event.failureReason) { logger.error(`Unknown sip videogw error ${event.newState} ${ event.failureReason}`);