Skip to content

Commit

Permalink
NEO-954: "no snapshot available" dialog (#696)
Browse files Browse the repository at this point in the history
* Refactored connectionInfoState to snapshotInfoState

Signed-off-by: Milton Moura <[email protected]>

* Draft implementation

Signed-off-by: Milton Moura <[email protected]>

* Show blocking dialog when unable to load a snapshot from room history

Signed-off-by: Milton Moura <[email protected]>

* Remove logging and add support for embedded mode

Signed-off-by: Milton Moura <[email protected]>

* Changes load failure dispatching and associated tests

Signed-off-by: Milton Moura <[email protected]>

* Update dialog message to something more adequate

Signed-off-by: Milton Moura <[email protected]>

* Add changeset

Signed-off-by: Milton Moura <[email protected]>

* Update .changeset/eleven-pans-pull.md

Co-authored-by: Kim Brose <[email protected]>
Signed-off-by: Milton Moura <[email protected]>

* Applying review feedback

Signed-off-by: Milton Moura <[email protected]>

* Applying further review feedback

Signed-off-by: Milton Moura <[email protected]>

* Preparing for merge

Signed-off-by: Milton Moura <[email protected]>

* Rolling back on the connection state store refactor

Signed-off-by: Milton Moura <[email protected]>

* Move snapshotInfoSlice.ts code into connectionInfoSlice.ts.

Signed-off-by: Mikhail Aheichyk <[email protected]>

* Fix typo in name

Signed-off-by: Mikhail Aheichyk <[email protected]>

* Smaller test text changes

Signed-off-by: Mikhail Aheichyk <[email protected]>

* Add variable

Signed-off-by: Mikhail Aheichyk <[email protected]>

* Rename snapshotFailed to snapshotSaveFailed

Signed-off-by: Mikhail Aheichyk <[email protected]>

* Rename setSnapshotSuccessful to setSnapshotSaveSuccessful

Signed-off-by: Mikhail Aheichyk <[email protected]>

---------

Signed-off-by: Milton Moura <[email protected]>
Signed-off-by: Mikhail Aheichyk <[email protected]>
Co-authored-by: Kim Brose <[email protected]>
Co-authored-by: Mikhail Aheichyk <[email protected]>
  • Loading branch information
3 people committed Jan 21, 2025
1 parent 3c6b897 commit 069bf42
Show file tree
Hide file tree
Showing 13 changed files with 307 additions and 30 deletions.
6 changes: 6 additions & 0 deletions .changeset/eleven-pans-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@nordeck/matrix-neoboard-widget': minor
'@nordeck/matrix-neoboard-react-sdk': minor
---

Show information dialog when snapshot loading fails
2 changes: 2 additions & 0 deletions matrix-neoboard-widget/src/AppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
PageLoader,
Snackbar,
SnackbarProvider,
SnapshotLoadStateDialog,
StoreType,
WhiteboardHotkeysProvider,
WhiteboardManager,
Expand Down Expand Up @@ -72,6 +73,7 @@ export const AppContainer = ({
<Snackbar />
<ConnectionStateProvider>
<ConnectionStateDialog />
<SnapshotLoadStateDialog />
<App />
</ConnectionStateProvider>
</SnackbarProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
WhiteboardManagerProvider,
} from '../../state';
import { SynchronizedDocument } from '../../state/types';
import { createStore, setSnapshotFailed, StoreType } from '../../store';
import { createStore, setSnapshotSaveFailed, StoreType } from '../../store';
import { SnackbarProvider } from '../Snackbar';
import { ConnectionStateProvider } from './ConnectionStateProvider';
import { useConnectionState } from './useConnectionState';
Expand Down Expand Up @@ -101,7 +101,7 @@ describe('<ConnectionStateProvider />', () => {
goOffline();

// Set last snapshot to failed
store.dispatch(setSnapshotFailed());
store.dispatch(setSnapshotSaveFailed());

const { result } = renderHook(useConnectionState, { wrapper: Wrapper });

Expand Down Expand Up @@ -138,7 +138,9 @@ describe('<ConnectionStateProvider />', () => {
expect(synchronizedDocument.persist).toHaveBeenCalledTimes(3);

// Store should say snapshot not failed
expect(store.getState().connectionInfoReducer.snapshotFailed).toBe(false);
expect(store.getState().connectionInfoReducer.snapshotSaveFailed).toBe(
false,
);

// Advance > retry interval
await act(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ import React, {
} from 'react';
import { useTranslation } from 'react-i18next';
import { useNetworkState } from 'react-use';
import { useSnackbar } from '../../components/Snackbar';
import { useActiveWhiteboardInstance } from '../../state';
import { useAppDispatch } from '../../store';
import {
selectConnectionInfo,
setSnapshotSuccessful,
setSnapshotSaveSuccessful,
} from '../../store/connectionInfoSlice';
import { useAppSelector } from '../../store/reduxToolkitHooks';
import { useSnackbar } from '../Snackbar';

const logger = getLogger('ConnectionStateProvider');

Expand All @@ -49,8 +49,13 @@ export type ConnectionStateState = {
handleCloseConnectionStateDialog: () => void;
};

export type SnapshotLoadState = {
snapshotLoadDialogOpen: boolean;
handleCloseSnapshotLoadDialog: () => void;
};

export const ConnectionStateContext = createContext<
ConnectionStateState | undefined
(ConnectionStateState & SnapshotLoadState) | undefined
>(undefined);

export const ConnectionStateProvider: React.FC<PropsWithChildren> = function ({
Expand All @@ -69,6 +74,7 @@ export const ConnectionStateProvider: React.FC<PropsWithChildren> = function ({
const networkState = useNetworkState();
const [connectionStateDialogOpen, setConnectionStateDialogOpen] =
useState(false);
const [snapshotLoadDialogOpen, setSnapshotLoadDialogOpen] = useState(false);
const connectionInfo = useAppSelector(selectConnectionInfo);
const connectionState: ConnectionState = networkState.online
? 'online'
Expand All @@ -78,11 +84,28 @@ export const ConnectionStateProvider: React.FC<PropsWithChildren> = function ({
setConnectionStateDialogOpen(true);
}, [setConnectionStateDialogOpen]);

/**
* Monitor load snapshot state. Display dialog if snapshot load failed.
*/
useEffect(() => {
if (!connectionInfo.snapshotLoadFailed) {
// No load snapshot error - no dialog
setSnapshotLoadDialogOpen(false);
return;
}

setSnapshotLoadDialogOpen(true);
}, [
connectionInfo.snapshotLoadFailed,
snapshotLoadDialogOpen,
setSnapshotLoadDialogOpen,
]);

/**
* Monitor send snapshot state. Display a snackbar on errors.
*/
useEffect(() => {
if (connectionInfo.snapshotFailed === false) {
if (connectionInfo.snapshotSaveFailed === false) {
// No send snapshot error - no connection state snackbar and no dialog
clearSnackbar();
setConnectionStateDialogOpen(false);
Expand Down Expand Up @@ -128,9 +151,9 @@ export const ConnectionStateProvider: React.FC<PropsWithChildren> = function ({
});
}, [
clearSnackbar,
connectionInfo.snapshotFailed,
connectionState,
connectionStateDialogOpen,
connectionInfo.snapshotSaveFailed,
handleLearnMoreClick,
setConnectionStateDialogOpen,
showSnackbar,
Expand All @@ -147,7 +170,7 @@ export const ConnectionStateProvider: React.FC<PropsWithChildren> = function ({
return false;
}

if (connectionInfo.snapshotFailed === false) {
if (connectionInfo.snapshotSaveFailed === false) {
// Do not retry, if there is no error
logger.debug(
'Retry snapshot: No retry, because there is no failed snapshot',
Expand All @@ -165,7 +188,7 @@ export const ConnectionStateProvider: React.FC<PropsWithChildren> = function ({

logger.debug('Retry snapshot: Should retry');
return true;
}, [connectionInfo.snapshotFailed, connectionState, whiteboard]);
}, [connectionState, connectionInfo.snapshotSaveFailed, whiteboard]);

// Actually retry to send the snapshot.
useEffect(() => {
Expand All @@ -189,7 +212,7 @@ export const ConnectionStateProvider: React.FC<PropsWithChildren> = function ({

try {
await whiteboard.persist();
dispatch(setSnapshotSuccessful());
dispatch(setSnapshotSaveSuccessful());
return true;
} catch {
return false;
Expand Down Expand Up @@ -222,12 +245,18 @@ export const ConnectionStateProvider: React.FC<PropsWithChildren> = function ({
setConnectionStateDialogOpen(false);
}, [setConnectionStateDialogOpen]);

const handleCloseSnapshotLoadDialog = useCallback(() => {
setSnapshotLoadDialogOpen(false);
}, [setSnapshotLoadDialogOpen]);

return (
<ConnectionStateContext.Provider
value={{
handleCloseConnectionStateDialog,
connectionState,
connectionStateDialogOpen,
handleCloseSnapshotLoadDialog,
snapshotLoadDialogOpen,
}}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2024 Nordeck IT + Consulting GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {
Dialog,
DialogContent,
DialogContentText,
DialogTitle,
} from '@mui/material';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useSnapshotLoadState } from './useSnapshotLoadState';

export const SnapshotLoadStateDialog: React.FC = () => {
const { t } = useTranslation('neoboard');
const { snapshotLoadDialogOpen } = useSnapshotLoadState();

if (!snapshotLoadDialogOpen) {
return null;
}

// This dialog can also be triggered by a generic networking error
// that we are unable to handle because the Widget API is not providing additional
// information on the readRelationships call.
//
// We decide to keep this dialog focused on the scenario that relations fail to
// load because the user is unable to retrieve the snapshots due to being in an encrypted room
// or a room with limited history visibility

return (
<Dialog open={true} disableEscapeKeyDown={true}>
<DialogTitle>
{t('snapshotLoadState.dialog.title', 'Unable to display the board')}
</DialogTitle>
<DialogContent>
<DialogContentText>
{t(
'snapshotLoadState.dialog.text',
'You will be able to see the contents when another user is working on the board.',
)}
</DialogContentText>
</DialogContent>
</Dialog>
);
};
2 changes: 2 additions & 0 deletions packages/react-sdk/src/components/connection-state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@

export { ConnectionStateDialog } from './ConnectionStateDialog';
export { ConnectionStateProvider } from './ConnectionStateProvider';
export { SnapshotLoadStateDialog } from './SnapshotLoadStateDialog';
export { useConnectionState } from './useConnectionState';
export { useSnapshotLoadState } from './useSnapshotLoadState';
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2024 Nordeck IT + Consulting GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { useContext } from 'react';
import {
ConnectionStateContext,
SnapshotLoadState,
} from './ConnectionStateProvider';

export function useSnapshotLoadState(): SnapshotLoadState {
const value = useContext(ConnectionStateContext);

if (!value) {
throw new Error(
'useConnectionState can only be used inside of <ConnectionStateProvider>',
);
}

return value;
}
6 changes: 6 additions & 0 deletions packages/react-sdk/src/locales/de/neoboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,12 @@
"snackbar": {
"dismissAction": "Ausblenden"
},
"snapshotLoadState": {
"dialog": {
"text": "Sie können die Inhalte sehen, wenn ein anderer Benutzer an der Tafel arbeitet.",
"title": "Das Board kann nicht angezeigt werden"
}
},
"textColorPicker": {
"title": "Wähle eine Textfarbe"
},
Expand Down
6 changes: 6 additions & 0 deletions packages/react-sdk/src/locales/en/neoboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,12 @@
"snackbar": {
"dismissAction": "Dismiss"
},
"snapshotLoadState": {
"dialog": {
"text": "You will be able to see the contents when another user is working on the board.",
"title": "Unable to display the board"
}
},
"textColorPicker": {
"title": "Pick a text color"
},
Expand Down
Loading

0 comments on commit 069bf42

Please sign in to comment.