Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 472222c

Browse files
author
Kerry
authored
LLS: error handling on stopping beacon (#8406)
* shared stopping error state for timeline, maxi and room warnign Signed-off-by: Kerry Archibald <kerrya@element.io> * check for stopping errors in roomlist share warning Signed-off-by: Kerry Archibald <kerrya@element.io> * lint Signed-off-by: Kerry Archibald <kerrya@element.io> * test stopping errors in OwnBeaconStore Signed-off-by: Kerry Archibald <kerrya@element.io> * update LeftPanelLiveShareWarning tests for stopping errors Signed-off-by: Kerry Archibald <kerrya@element.io> * reinstate try/catch for stopping beacons in create Signed-off-by: Kerry Archibald <kerrya@element.io> * remove unnecessary and buggy beacon stopping on creation Signed-off-by: Kerry Archibald <kerrya@element.io>
1 parent 1bceeb2 commit 472222c

File tree

8 files changed

+213
-49
lines changed

8 files changed

+213
-49
lines changed

src/components/views/beacon/LeftPanelLiveShareWarning.tsx

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ limitations under the License.
1616

1717
import classNames from 'classnames';
1818
import React from 'react';
19+
import { BeaconIdentifier, Room } from 'matrix-js-sdk/src/matrix';
1920

2021
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
2122
import { _t } from '../../../languageHandler';
@@ -34,10 +35,14 @@ interface Props {
3435
* Choose the most relevant beacon
3536
* and get its roomId
3637
*/
37-
const chooseBestBeaconRoomId = (liveBeaconIds, errorBeaconIds): string | undefined => {
38+
const chooseBestBeaconRoomId = (
39+
liveBeaconIds: BeaconIdentifier[],
40+
updateErrorBeaconIds: BeaconIdentifier[],
41+
locationErrorBeaconIds: BeaconIdentifier[],
42+
): Room['roomId'] | undefined => {
3843
// both lists are ordered by creation timestamp in store
3944
// so select latest beacon
40-
const beaconId = errorBeaconIds?.[0] ?? liveBeaconIds?.[0];
45+
const beaconId = updateErrorBeaconIds?.[0] ?? locationErrorBeaconIds?.[0] ?? liveBeaconIds?.[0];
4146
if (!beaconId) {
4247
return undefined;
4348
}
@@ -46,6 +51,16 @@ const chooseBestBeaconRoomId = (liveBeaconIds, errorBeaconIds): string | undefin
4651
return beacon?.roomId;
4752
};
4853

54+
const getLabel = (hasStoppingErrors: boolean, hasLocationErrors: boolean): string => {
55+
if (hasStoppingErrors) {
56+
return _t('An error occurred while stopping your live location');
57+
}
58+
if (hasLocationErrors) {
59+
return _t('An error occured whilst sharing your live location');
60+
}
61+
return _t('You are sharing your live location');
62+
};
63+
4964
const LeftPanelLiveShareWarning: React.FC<Props> = ({ isMinimized }) => {
5065
const isMonitoringLiveLocation = useEventEmitterState(
5166
OwnBeaconStore.instance,
@@ -59,19 +74,30 @@ const LeftPanelLiveShareWarning: React.FC<Props> = ({ isMinimized }) => {
5974
() => OwnBeaconStore.instance.getLiveBeaconIdsWithLocationPublishError(),
6075
);
6176

77+
const beaconIdsWithStoppingError = useEventEmitterState(
78+
OwnBeaconStore.instance,
79+
OwnBeaconStoreEvent.BeaconUpdateError,
80+
() => OwnBeaconStore.instance.getLiveBeaconIds().filter(
81+
beaconId => OwnBeaconStore.instance.beaconUpdateErrors.has(beaconId),
82+
),
83+
);
84+
6285
const liveBeaconIds = useEventEmitterState(
6386
OwnBeaconStore.instance,
6487
OwnBeaconStoreEvent.LivenessChange,
6588
() => OwnBeaconStore.instance.getLiveBeaconIds(),
6689
);
6790

6891
const hasLocationPublishErrors = !!beaconIdsWithLocationPublishError.length;
92+
const hasStoppingErrors = !!beaconIdsWithStoppingError.length;
6993

7094
if (!isMonitoringLiveLocation) {
7195
return null;
7296
}
7397

74-
const relevantBeaconRoomId = chooseBestBeaconRoomId(liveBeaconIds, beaconIdsWithLocationPublishError);
98+
const relevantBeaconRoomId = chooseBestBeaconRoomId(
99+
liveBeaconIds, beaconIdsWithStoppingError, beaconIdsWithLocationPublishError,
100+
);
75101

76102
const onWarningClick = relevantBeaconRoomId ? () => {
77103
dispatcher.dispatch<ViewRoomPayload>({
@@ -81,14 +107,12 @@ const LeftPanelLiveShareWarning: React.FC<Props> = ({ isMinimized }) => {
81107
});
82108
} : undefined;
83109

84-
const label = hasLocationPublishErrors ?
85-
_t('An error occured whilst sharing your live location') :
86-
_t('You are sharing your live location');
110+
const label = getLabel(hasStoppingErrors, hasLocationPublishErrors);
87111

88112
return <AccessibleButton
89113
className={classNames('mx_LeftPanelLiveShareWarning', {
90114
'mx_LeftPanelLiveShareWarning__minimized': isMinimized,
91-
'mx_LeftPanelLiveShareWarning__error': hasLocationPublishErrors,
115+
'mx_LeftPanelLiveShareWarning__error': hasLocationPublishErrors || hasStoppingErrors,
92116
})}
93117
title={isMinimized ? label : undefined}
94118
onClick={onWarningClick}

src/components/views/beacon/RoomLiveShareWarning.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ limitations under the License.
1515
*/
1616

1717
import React from 'react';
18-
import classNames from 'classnames';
1918
import { Room } from 'matrix-js-sdk/src/matrix';
2019

2120
import { _t } from '../../../languageHandler';
@@ -67,7 +66,7 @@ const RoomLiveShareWarningInner: React.FC<RoomLiveShareWarningInnerProps> = ({ l
6766
};
6867

6968
return <div
70-
className={classNames('mx_RoomLiveShareWarning')}
69+
className='mx_RoomLiveShareWarning'
7170
>
7271
<StyledLiveBeaconIcon className="mx_RoomLiveShareWarning_icon" withError={hasError} />
7372

src/components/views/messages/MBeaconBody.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent }, ref) =>
146146
className='mx_MBeaconBody_chin'
147147
beacon={beacon}
148148
displayStatus={displayStatus}
149+
withIcon
149150
/> :
150151
<BeaconStatus
151152
className='mx_MBeaconBody_chin'

src/i18n/strings/en_EN.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2948,6 +2948,7 @@
29482948
"View list": "View list",
29492949
"View List": "View List",
29502950
"Close sidebar": "Close sidebar",
2951+
"An error occurred while stopping your live location": "An error occurred while stopping your live location",
29512952
"An error occured whilst sharing your live location": "An error occured whilst sharing your live location",
29522953
"You are sharing your live location": "You are sharing your live location",
29532954
"%(timeRemaining)s left": "%(timeRemaining)s left",

src/stores/OwnBeaconStore.ts

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export enum OwnBeaconStoreEvent {
5151
LivenessChange = 'OwnBeaconStore.LivenessChange',
5252
MonitoringLivePosition = 'OwnBeaconStore.MonitoringLivePosition',
5353
LocationPublishError = 'LocationPublishError',
54+
BeaconUpdateError = 'BeaconUpdateError',
5455
}
5556

5657
const MOVING_UPDATE_INTERVAL = 2000;
@@ -61,6 +62,7 @@ const BAIL_AFTER_CONSECUTIVE_ERROR_COUNT = 2;
6162
type OwnBeaconStoreState = {
6263
beacons: Map<BeaconIdentifier, Beacon>;
6364
beaconLocationPublishErrorCounts: Map<BeaconIdentifier, number>;
65+
beaconUpdateErrors: Map<BeaconIdentifier, Error>;
6466
beaconsByRoomId: Map<Room['roomId'], Set<BeaconIdentifier>>;
6567
liveBeaconIds: BeaconIdentifier[];
6668
};
@@ -99,6 +101,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
99101
* Reset on successful publish of location
100102
*/
101103
public readonly beaconLocationPublishErrorCounts = new Map<BeaconIdentifier, number>();
104+
public readonly beaconUpdateErrors = new Map<BeaconIdentifier, Error>();
102105
/**
103106
* ids of live beacons
104107
* ordered by creation time descending
@@ -144,6 +147,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
144147
this.beaconsByRoomId.clear();
145148
this.liveBeaconIds = [];
146149
this.beaconLocationPublishErrorCounts.clear();
150+
this.beaconUpdateErrors.clear();
147151
}
148152

149153
protected async onReady(): Promise<void> {
@@ -215,7 +219,6 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
215219
}
216220

217221
await this.updateBeaconEvent(beacon, { live: false });
218-
219222
// prune from local store
220223
removeLocallyCreateBeaconEventId(beacon.beaconInfoId);
221224
};
@@ -300,7 +303,10 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
300303
* Live beacon ids that do not have wire errors
301304
*/
302305
private get healthyLiveBeaconIds() {
303-
return this.liveBeaconIds.filter(beaconId => !this.beaconHasLocationPublishError(beaconId));
306+
return this.liveBeaconIds.filter(beaconId =>
307+
!this.beaconHasLocationPublishError(beaconId) &&
308+
!this.beaconUpdateErrors.has(beaconId),
309+
);
304310
}
305311

306312
private initialiseBeaconState = () => {
@@ -393,19 +399,6 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
393399
);
394400

395401
storeLocallyCreateBeaconEventId(event_id);
396-
397-
// try to stop any other live beacons
398-
// in this room
399-
this.beaconsByRoomId.get(roomId)?.forEach(beaconId => {
400-
if (this.getBeaconById(beaconId)?.isLive) {
401-
try {
402-
// don't await, this is best effort
403-
this.stopBeacon(beaconId);
404-
} catch (error) {
405-
logger.error('Failed to stop live beacons', error);
406-
}
407-
}
408-
});
409402
};
410403

411404
/**
@@ -510,6 +503,11 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
510503
* MatrixClient api
511504
*/
512505

506+
/**
507+
* Updates beacon with provided content update
508+
* Records error in beaconUpdateErrors
509+
* rethrows
510+
*/
513511
private updateBeaconEvent = async (beacon: Beacon, update: Partial<BeaconInfoState>): Promise<void> => {
514512
const { description, timeout, timestamp, live, assetType } = {
515513
...beacon.beaconInfo,
@@ -523,7 +521,21 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
523521
timestamp,
524522
);
525523

526-
await this.matrixClient.unstable_setLiveBeacon(beacon.roomId, updateContent);
524+
try {
525+
await this.matrixClient.unstable_setLiveBeacon(beacon.roomId, updateContent);
526+
// cleanup any errors
527+
const hadError = this.beaconUpdateErrors.has(beacon.identifier);
528+
if (hadError) {
529+
this.beaconUpdateErrors.delete(beacon.identifier);
530+
this.emit(OwnBeaconStoreEvent.BeaconUpdateError, beacon.identifier, false);
531+
}
532+
} catch (error) {
533+
logger.error('Failed to update beacon', error);
534+
this.beaconUpdateErrors.set(beacon.identifier, error);
535+
this.emit(OwnBeaconStoreEvent.BeaconUpdateError, beacon.identifier, true);
536+
537+
throw error;
538+
}
527539
};
528540

529541
/**

src/utils/beacon/useOwnLiveBeacons.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ type LiveBeaconsState = {
3939
*/
4040
export const useOwnLiveBeacons = (liveBeaconIds: BeaconIdentifier[]): LiveBeaconsState => {
4141
const [stoppingInProgress, setStoppingInProgress] = useState(false);
42-
const [error, setError] = useState<Error>();
4342

4443
const hasLocationPublishError = useEventEmitterState(
4544
OwnBeaconStore.instance,
@@ -48,10 +47,22 @@ export const useOwnLiveBeacons = (liveBeaconIds: BeaconIdentifier[]): LiveBeacon
4847
liveBeaconIds.some(OwnBeaconStore.instance.beaconHasLocationPublishError),
4948
);
5049

50+
const hasStopSharingError = useEventEmitterState(
51+
OwnBeaconStore.instance,
52+
OwnBeaconStoreEvent.BeaconUpdateError,
53+
() =>
54+
liveBeaconIds.some(id => OwnBeaconStore.instance.beaconUpdateErrors.has(id)),
55+
);
56+
57+
useEffect(() => {
58+
if (hasStopSharingError) {
59+
setStoppingInProgress(false);
60+
}
61+
}, [hasStopSharingError]);
62+
5163
// reset stopping in progress on change in live ids
5264
useEffect(() => {
5365
setStoppingInProgress(false);
54-
setError(undefined);
5566
}, [liveBeaconIds]);
5667

5768
// select the beacon with latest expiry to display expiry time
@@ -64,10 +75,6 @@ export const useOwnLiveBeacons = (liveBeaconIds: BeaconIdentifier[]): LiveBeacon
6475
try {
6576
await Promise.all(liveBeaconIds.map(beaconId => OwnBeaconStore.instance.stopBeacon(beaconId)));
6677
} catch (error) {
67-
// only clear loading in case of error
68-
// to avoid flash of not-loading state
69-
// after beacons have been stopped but we wait for sync
70-
setError(error);
7178
setStoppingInProgress(false);
7279
}
7380
};
@@ -82,6 +89,6 @@ export const useOwnLiveBeacons = (liveBeaconIds: BeaconIdentifier[]): LiveBeacon
8289
beacon,
8390
stoppingInProgress,
8491
hasLocationPublishError,
85-
hasStopSharingError: !!error,
92+
hasStopSharingError,
8693
};
8794
};

test/components/views/beacon/LeftPanelLiveShareWarning-test.tsx

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import React from 'react';
1818
import { mocked } from 'jest-mock';
1919
import { mount } from 'enzyme';
2020
import { act } from 'react-dom/test-utils';
21-
import { Beacon } from 'matrix-js-sdk/src/matrix';
21+
import { Beacon, BeaconIdentifier } from 'matrix-js-sdk/src/matrix';
2222

2323
import LeftPanelLiveShareWarning from '../../../../src/components/views/beacon/LeftPanelLiveShareWarning';
2424
import { OwnBeaconStore, OwnBeaconStoreEvent } from '../../../../src/stores/OwnBeaconStore';
@@ -33,6 +33,7 @@ jest.mock('../../../../src/stores/OwnBeaconStore', () => {
3333
public getLiveBeaconIdsWithLocationPublishError = jest.fn().mockReturnValue([]);
3434
public getBeaconById = jest.fn();
3535
public getLiveBeaconIds = jest.fn().mockReturnValue([]);
36+
public readonly beaconUpdateErrors = new Map<BeaconIdentifier, Error>();
3637
}
3738
return {
3839
// @ts-ignore
@@ -59,6 +60,8 @@ describe('<LeftPanelLiveShareWarning />', () => {
5960
beforeEach(() => {
6061
jest.spyOn(global.Date, 'now').mockReturnValue(now);
6162
jest.spyOn(dispatcher, 'dispatch').mockClear().mockImplementation(() => { });
63+
64+
OwnBeaconStore.instance.beaconUpdateErrors.clear();
6265
});
6366

6467
afterAll(() => {
@@ -191,5 +194,55 @@ describe('<LeftPanelLiveShareWarning />', () => {
191194

192195
expect(component.html()).toBe(null);
193196
});
197+
198+
describe('stopping errors', () => {
199+
it('renders stopping error', () => {
200+
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error('error'));
201+
const component = getComponent();
202+
expect(component.text()).toEqual('An error occurred while stopping your live location');
203+
});
204+
205+
it('starts rendering stopping error on beaconUpdateError emit', () => {
206+
const component = getComponent();
207+
// no error
208+
expect(component.text()).toEqual('You are sharing your live location');
209+
210+
act(() => {
211+
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error('error'));
212+
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.BeaconUpdateError, beacon2.identifier, true);
213+
});
214+
215+
expect(component.text()).toEqual('An error occurred while stopping your live location');
216+
});
217+
218+
it('renders stopping error when beacons have stopping and location errors', () => {
219+
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue(
220+
[beacon1.identifier],
221+
);
222+
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error('error'));
223+
const component = getComponent();
224+
expect(component.text()).toEqual('An error occurred while stopping your live location');
225+
});
226+
227+
it('goes to room of latest beacon with stopping error when clicked', () => {
228+
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue(
229+
[beacon1.identifier],
230+
);
231+
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error('error'));
232+
const component = getComponent();
233+
const dispatchSpy = jest.spyOn(dispatcher, 'dispatch');
234+
235+
act(() => {
236+
component.simulate('click');
237+
});
238+
239+
expect(dispatchSpy).toHaveBeenCalledWith({
240+
action: Action.ViewRoom,
241+
metricsTrigger: undefined,
242+
// stopping error beacon's room
243+
room_id: beacon2.roomId,
244+
});
245+
});
246+
});
194247
});
195248
});

0 commit comments

Comments
 (0)