Better error handling in jump to date (#10405)
- Friendly error messages with details - Add a way to submit debug logs for actual errors (non-networking errors) - Don't jump someone back to a room they already navigated away from. Fixes bug mentioned in https://github.com/vector-im/element-web/issues/21263#issuecomment-1056809714
This commit is contained in:
parent
1af71089dd
commit
e5f06df3f7
11 changed files with 424 additions and 83 deletions
|
@ -18,16 +18,19 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import { Direction } from "matrix-js-sdk/src/models/event-timeline";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { ConnectionError, MatrixError, HTTPError } from "matrix-js-sdk/src/http-api";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { formatFullDateNoTime } from "../../../DateUtils";
|
||||
import { formatFullDateNoDay, formatFullDateNoTime } from "../../../DateUtils";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import dispatcher from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
import Modal from "../../../Modal";
|
||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||
import BugReportDialog from "../dialogs/BugReportDialog";
|
||||
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
|
||||
import { contextMenuBelow } from "../rooms/RoomTile";
|
||||
import { ContextMenuTooltipButton } from "../../structures/ContextMenu";
|
||||
import IconizedContextMenu, {
|
||||
|
@ -36,6 +39,7 @@ import IconizedContextMenu, {
|
|||
} from "../context_menus/IconizedContextMenu";
|
||||
import JumpToDatePicker from "./JumpToDatePicker";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||
|
||||
function getDaysArray(): string[] {
|
||||
return [_t("Sunday"), _t("Monday"), _t("Tuesday"), _t("Wednesday"), _t("Thursday"), _t("Friday"), _t("Saturday")];
|
||||
|
@ -76,7 +80,7 @@ export default class DateSeparator extends React.Component<IProps, IState> {
|
|||
if (this.settingWatcherRef) SettingsStore.unwatchSetting(this.settingWatcherRef);
|
||||
}
|
||||
|
||||
private onContextMenuOpenClick = (e: React.MouseEvent): void => {
|
||||
private onContextMenuOpenClick = (e: ButtonEvent): void => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const target = e.target as HTMLButtonElement;
|
||||
|
@ -118,12 +122,12 @@ export default class DateSeparator extends React.Component<IProps, IState> {
|
|||
|
||||
private pickDate = async (inputTimestamp: number | string | Date): Promise<void> => {
|
||||
const unixTimestamp = new Date(inputTimestamp).getTime();
|
||||
const roomIdForJumpRequest = this.props.roomId;
|
||||
|
||||
const cli = MatrixClientPeg.get();
|
||||
try {
|
||||
const roomId = this.props.roomId;
|
||||
const cli = MatrixClientPeg.get();
|
||||
const { event_id: eventId, origin_server_ts: originServerTs } = await cli.timestampToEvent(
|
||||
roomId,
|
||||
roomIdForJumpRequest,
|
||||
unixTimestamp,
|
||||
Direction.Forward,
|
||||
);
|
||||
|
@ -132,28 +136,113 @@ export default class DateSeparator extends React.Component<IProps, IState> {
|
|||
`found ${eventId} (${originServerTs}) for timestamp=${unixTimestamp} (looking forward)`,
|
||||
);
|
||||
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
event_id: eventId,
|
||||
highlighted: true,
|
||||
room_id: roomId,
|
||||
metricsTrigger: undefined, // room doesn't change
|
||||
});
|
||||
} catch (e) {
|
||||
const code = e.errcode || e.statusCode;
|
||||
// only show the dialog if failing for something other than a network error
|
||||
// (e.g. no errcode or statusCode) as in that case the redactions end up in the
|
||||
// detached queue and we show the room status bar to allow retry
|
||||
if (typeof code !== "undefined") {
|
||||
// display error message stating you couldn't delete this.
|
||||
// Only try to navigate to the room if the user is still viewing the same
|
||||
// room. We don't want to jump someone back to a room after a slow request
|
||||
// if they've already navigated away to another room.
|
||||
const currentRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
|
||||
if (currentRoomId === roomIdForJumpRequest) {
|
||||
dispatcher.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
event_id: eventId,
|
||||
highlighted: true,
|
||||
room_id: roomIdForJumpRequest,
|
||||
metricsTrigger: undefined, // room doesn't change
|
||||
});
|
||||
} else {
|
||||
logger.debug(
|
||||
`No longer navigating to date in room (jump to date) because the user already switched ` +
|
||||
`to another room: currentRoomId=${currentRoomId}, roomIdForJumpRequest=${roomIdForJumpRequest}`,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`Error occured while trying to find event in ${roomIdForJumpRequest} ` +
|
||||
`at timestamp=${unixTimestamp}:`,
|
||||
err,
|
||||
);
|
||||
|
||||
// Only display an error if the user is still viewing the same room. We
|
||||
// don't want to worry someone about an error in a room they no longer care
|
||||
// about after a slow request if they've already navigated away to another
|
||||
// room.
|
||||
const currentRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
|
||||
if (currentRoomId === roomIdForJumpRequest) {
|
||||
let friendlyErrorMessage = "An error occured while trying to find and jump to the given date.";
|
||||
let submitDebugLogsContent: JSX.Element = <></>;
|
||||
if (err instanceof ConnectionError) {
|
||||
friendlyErrorMessage = _t(
|
||||
"A network error occurred while trying to find and jump to the given date. " +
|
||||
"Your homeserver might be down or there was just a temporary problem with " +
|
||||
"your internet connection. Please try again. If this continues, please " +
|
||||
"contact your homeserver administrator.",
|
||||
);
|
||||
} else if (err instanceof MatrixError) {
|
||||
if (err?.errcode === "M_NOT_FOUND") {
|
||||
friendlyErrorMessage = _t(
|
||||
"We were unable to find an event looking forwards from %(dateString)s. " +
|
||||
"Try choosing an earlier date.",
|
||||
{ dateString: formatFullDateNoDay(new Date(unixTimestamp)) },
|
||||
);
|
||||
} else {
|
||||
friendlyErrorMessage = _t("Server returned %(statusCode)s with error code %(errorCode)s", {
|
||||
statusCode: err?.httpStatus || _t("unknown status code"),
|
||||
errorCode: err?.errcode || _t("unavailable"),
|
||||
});
|
||||
}
|
||||
} else if (err instanceof HTTPError) {
|
||||
friendlyErrorMessage = err.message;
|
||||
} else {
|
||||
// We only give the option to submit logs for actual errors, not network problems.
|
||||
submitDebugLogsContent = (
|
||||
<p>
|
||||
{_t(
|
||||
"Please submit <debugLogsLink>debug logs</debugLogsLink> to help us " +
|
||||
"track down the problem.",
|
||||
{},
|
||||
{
|
||||
debugLogsLink: (sub) => (
|
||||
<AccessibleButton
|
||||
// This is by default a `<div>` which we
|
||||
// can't nest within a `<p>` here so update
|
||||
// this to a be a inline anchor element.
|
||||
element="a"
|
||||
kind="link"
|
||||
onClick={() => this.onBugReport(err instanceof Error ? err : undefined)}
|
||||
data-testid="jump-to-date-error-submit-debug-logs-button"
|
||||
>
|
||||
{sub}
|
||||
</AccessibleButton>
|
||||
),
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
description: _t("Unable to find event at that date. (%(code)s)", { code }),
|
||||
title: _t("Unable to find event at that date"),
|
||||
description: (
|
||||
<div data-testid="jump-to-date-error-content">
|
||||
<p>{friendlyErrorMessage}</p>
|
||||
{submitDebugLogsContent}
|
||||
<details>
|
||||
<summary>{_t("Error details")}</summary>
|
||||
<p>{String(err)}</p>
|
||||
</details>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private onBugReport = (err?: Error): void => {
|
||||
Modal.createDialog(BugReportDialog, {
|
||||
error: err,
|
||||
initialText: "Error occured while using jump to date #jump-to-date",
|
||||
});
|
||||
};
|
||||
|
||||
private onLastWeekClicked = (): void => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 7);
|
||||
|
@ -189,11 +278,20 @@ export default class DateSeparator extends React.Component<IProps, IState> {
|
|||
onFinished={this.onContextMenuCloseClick}
|
||||
>
|
||||
<IconizedContextMenuOptionList first>
|
||||
<IconizedContextMenuOption label={_t("Last week")} onClick={this.onLastWeekClicked} />
|
||||
<IconizedContextMenuOption label={_t("Last month")} onClick={this.onLastMonthClicked} />
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Last week")}
|
||||
onClick={this.onLastWeekClicked}
|
||||
data-testid="jump-to-date-last-week"
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Last month")}
|
||||
onClick={this.onLastMonthClicked}
|
||||
data-testid="jump-to-date-last-month"
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("The beginning of the room")}
|
||||
onClick={this.onTheBeginningClicked}
|
||||
data-testid="jump-to-date-beginning"
|
||||
/>
|
||||
</IconizedContextMenuOptionList>
|
||||
|
||||
|
@ -207,6 +305,7 @@ export default class DateSeparator extends React.Component<IProps, IState> {
|
|||
return (
|
||||
<ContextMenuTooltipButton
|
||||
className="mx_DateSeparator_jumpToDateMenu mx_DateSeparator_dateContent"
|
||||
data-testid="jump-to-date-separator-button"
|
||||
onClick={this.onContextMenuOpenClick}
|
||||
isExpanded={!!this.state.contextMenuPosition}
|
||||
title={_t("Jump to date")}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue