Files
ThreadNet-Web/src/components/views/dialogs/BugReportDialog.tsx
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

320 lines
11 KiB
TypeScript
Raw Normal View History

/*
2024-09-09 14:57:16 +01:00
Copyright 2024 New Vector Ltd.
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019 The Matrix.org Foundation C.I.C.
2024-09-09 14:57:16 +01:00
Copyright 2018 New Vector Ltd
Copyright 2017 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
2024-09-09 14:57:16 +01:00
Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, type ReactNode } from "react";
import { Link } from "@vector-im/compound-web";
2021-10-22 17:23:32 -05:00
2018-04-13 00:43:44 +01:00
import SdkConfig from "../../../SdkConfig";
import Modal from "../../../Modal";
import { _t } from "../../../languageHandler";
import sendBugReport, { downloadBugReport, RageshakeError } from "../../../rageshake/submit-rageshake";
2020-03-30 16:12:28 +01:00
import AccessibleButton from "../elements/AccessibleButton";
2021-07-03 10:44:03 +02:00
import QuestionDialog from "./QuestionDialog";
import BaseDialog from "./BaseDialog";
import Field from "../elements/Field";
import Spinner from "../elements/Spinner";
import DialogButtons from "../elements/DialogButtons";
2021-08-11 17:19:15 +01:00
import { sendSentryReport } from "../../../sentry";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { getBrowserSupport } from "../../../SupportedBrowser";
export interface BugReportDialogProps {
2021-06-14 21:23:28 +01:00
onFinished: (success: boolean) => void;
initialText?: string;
label?: string;
error?: unknown;
2021-06-14 21:23:28 +01:00
}
interface IState {
sendLogs: boolean;
busy: boolean;
err: ReactNode | null;
2021-06-14 21:23:28 +01:00
issueUrl: string;
text: string;
progress: string | null;
2021-06-14 21:23:28 +01:00
downloadBusy: boolean;
downloadProgress: string | null;
2021-06-14 21:23:28 +01:00
}
export default class BugReportDialog extends React.Component<BugReportDialogProps, IState> {
2021-06-14 21:23:28 +01:00
private unmounted: boolean;
private issueRef: React.RefObject<Field | null>;
2021-06-14 21:23:28 +01:00
public constructor(props: BugReportDialogProps) {
2019-12-17 17:26:12 +00:00
super(props);
this.state = {
sendLogs: true,
busy: false,
err: null,
issueUrl: "",
text: props.initialText || "",
progress: null,
2020-08-13 13:08:07 +01:00
downloadBusy: false,
downloadProgress: null,
};
2021-06-14 21:23:28 +01:00
this.unmounted = false;
this.issueRef = React.createRef();
}
public componentDidMount(): void {
this.unmounted = false;
this.issueRef.current?.focus();
// Get all of the extra info dumped to the console when someone is about
// to send debug logs. Since this is a fire and forget action, we do
// this when the bug report dialog is opened instead of when we submit
// logs because we have no signal to know when all of the various
// components have finished logging. Someone could potentially send logs
// before we fully dump everything but it's probably unlikely.
defaultDispatcher.dispatch({
action: Action.DumpDebugLogs,
});
}
public componentWillUnmount(): void {
2021-06-14 21:23:28 +01:00
this.unmounted = true;
}
2021-06-14 21:23:28 +01:00
private onCancel = (): void => {
this.props.onFinished(false);
2021-06-29 13:11:58 +01:00
};
private getErrorText(error: Error | RageshakeError): ReactNode {
if (error instanceof RageshakeError) {
let errorText;
switch (error.errorcode) {
case "DISALLOWED_APP":
errorText = _t("bug_reporting|failed_send_logs_causes|disallowed_app");
break;
case "REJECTED_BAD_VERSION":
errorText = _t("bug_reporting|failed_send_logs_causes|rejected_version");
break;
case "REJECTED_UNEXPECTED_RECOVERY_KEY":
errorText = _t("bug_reporting|failed_send_logs_causes|rejected_recovery_key");
break;
default:
if (error.errorcode?.startsWith("REJECTED")) {
errorText = _t("bug_reporting|failed_send_logs_causes|rejected_generic");
} else {
errorText = _t("bug_reporting|failed_send_logs_causes|server_unknown_error");
}
break;
}
return (
<>
<p>{errorText}</p>
{error.policyURL && (
<Link size="medium" target="_blank" href={error.policyURL}>
{_t("action|learn_more")}
</Link>
)}
</>
);
} else {
return <p>{_t("bug_reporting|failed_send_logs_causes|unknown_error")}</p>;
}
}
2021-06-14 21:23:28 +01:00
private onSubmit = (): void => {
if ((!this.state.text || !this.state.text.trim()) && (!this.state.issueUrl || !this.state.issueUrl.trim())) {
this.setState({
err: _t("bug_reporting|error_empty"),
});
return;
}
const userText =
(this.state.text.length > 0 ? this.state.text + "\n\n" : "") +
"Issue: " +
(this.state.issueUrl.length > 0 ? this.state.issueUrl : "No issue link given");
this.setState({ busy: true, progress: null, err: null });
this.sendProgressCallback(_t("bug_reporting|preparing_logs"));
sendBugReport(SdkConfig.get().bug_report_endpoint_url, {
userText,
sendLogs: true,
2021-06-14 21:23:28 +01:00
progressCallback: this.sendProgressCallback,
labels: this.props.label ? [this.props.label] : [],
}).then(
() => {
2021-06-14 21:23:28 +01:00
if (!this.unmounted) {
this.props.onFinished(false);
2022-06-14 17:51:51 +01:00
Modal.createDialog(QuestionDialog, {
title: _t("bug_reporting|logs_sent"),
description: _t("bug_reporting|thank_you"),
hasCancelButton: false,
});
}
},
(err) => {
2021-06-14 21:23:28 +01:00
if (!this.unmounted) {
this.setState({
busy: false,
progress: null,
err: this.getErrorText(err),
});
}
},
);
2021-08-11 16:11:10 +01:00
sendSentryReport(this.state.text, this.state.issueUrl, this.props.error);
2021-06-29 13:11:58 +01:00
};
2021-06-14 21:23:28 +01:00
private onDownload = async (): Promise<void> => {
2020-08-13 13:08:07 +01:00
this.setState({ downloadBusy: true });
this.downloadProgressCallback(_t("bug_reporting|preparing_download"));
2020-03-30 16:12:28 +01:00
try {
await downloadBugReport({
sendLogs: true,
2021-06-14 21:23:28 +01:00
progressCallback: this.downloadProgressCallback,
labels: this.props.label ? [this.props.label] : [],
2020-03-30 16:12:28 +01:00
});
this.setState({
2020-08-13 13:08:07 +01:00
downloadBusy: false,
downloadProgress: null,
2020-03-30 16:12:28 +01:00
});
} catch (err) {
2021-06-14 21:23:28 +01:00
if (!this.unmounted) {
2020-03-30 16:12:28 +01:00
this.setState({
2020-08-13 13:08:07 +01:00
downloadBusy: false,
downloadProgress:
_t("bug_reporting|failed_download_logs") + `${err instanceof Error ? err.message : ""}`,
2020-03-30 16:12:28 +01:00
});
}
}
};
2021-06-14 21:23:28 +01:00
private onTextChange = (ev: React.FormEvent<HTMLTextAreaElement>): void => {
this.setState({ text: ev.currentTarget.value });
2021-06-29 13:11:58 +01:00
};
2021-06-14 21:23:28 +01:00
private onIssueUrlChange = (ev: React.FormEvent<HTMLInputElement>): void => {
this.setState({ issueUrl: ev.currentTarget.value });
2021-06-29 13:11:58 +01:00
};
2021-06-14 21:23:28 +01:00
private sendProgressCallback = (progress: string): void => {
if (this.unmounted) {
return;
}
2021-06-14 21:23:28 +01:00
this.setState({ progress });
2021-06-29 13:11:58 +01:00
};
2021-06-14 21:23:28 +01:00
private downloadProgressCallback = (downloadProgress: string): void => {
if (this.unmounted) {
2020-08-13 13:08:07 +01:00
return;
}
this.setState({ downloadProgress });
2021-06-29 13:11:58 +01:00
};
2020-08-13 13:08:07 +01:00
public render(): React.ReactNode {
let error: JSX.Element | undefined;
if (this.state.err) {
error = <div className="error">{this.state.err}</div>;
}
let progress: JSX.Element | undefined;
if (this.state.busy) {
progress = (
<div className="progress">
2021-07-03 10:44:03 +02:00
<Spinner />
{this.state.progress} ...
</div>
);
}
let warning: JSX.Element | undefined;
if (
(window.Modernizr && Object.values(window.Modernizr).some((support) => support === false)) ||
!getBrowserSupport()
) {
warning = (
<p>
<strong>{_t("bug_reporting|unsupported_browser")}</strong>
</p>
);
}
return (
2021-07-23 10:23:45 +01:00
<BaseDialog
className="mx_BugReportDialog"
onFinished={this.onCancel}
title={_t("bug_reporting|submit_debug_logs")}
2018-04-27 15:19:08 +01:00
contentId="mx_Dialog_content"
>
<div className="mx_Dialog_content" id="mx_Dialog_content">
{warning}
<p>{_t("bug_reporting|description")}</p>
2022-12-12 12:24:14 +01:00
<p>
<strong>
2022-12-12 12:24:14 +01:00
{_t(
"bug_reporting|before_submitting",
2022-12-12 12:24:14 +01:00
{},
{
a: (sub) => (
2022-12-12 12:24:14 +01:00
<a
target="_blank"
href={SdkConfig.get().feedback.new_issue_url}
rel="noreferrer noopener"
2022-12-12 12:24:14 +01:00
>
{sub}
</a>
),
},
)}
</strong>
</p>
2020-03-30 16:12:28 +01:00
2020-08-13 13:08:07 +01:00
<div className="mx_BugReportDialog_download">
2021-06-14 21:23:28 +01:00
<AccessibleButton onClick={this.onDownload} kind="link" disabled={this.state.downloadBusy}>
{_t("bug_reporting|download_logs")}
2020-08-13 13:08:07 +01:00
</AccessibleButton>
{this.state.downloadProgress && <span>{this.state.downloadProgress} ...</span>}
2020-08-13 13:08:07 +01:00
</div>
2020-03-30 16:12:28 +01:00
2019-04-01 17:39:12 +01:00
<Field
type="text"
className="mx_BugReportDialog_field_input"
label={_t("bug_reporting|github_issue")}
2021-06-14 21:23:28 +01:00
onChange={this.onIssueUrlChange}
2019-04-01 17:39:12 +01:00
value={this.state.issueUrl}
placeholder="https://github.com/vector-im/element-web/issues/..."
ref={this.issueRef}
2019-04-01 17:39:12 +01:00
/>
<Field
className="mx_BugReportDialog_field_input"
element="textarea"
label={_t("bug_reporting|textarea_label")}
2019-04-01 17:39:12 +01:00
rows={5}
2021-06-14 21:23:28 +01:00
onChange={this.onTextChange}
2019-04-01 17:39:12 +01:00
value={this.state.text}
placeholder={_t("bug_reporting|additional_context")}
2019-04-01 17:39:12 +01:00
/>
{progress}
{error}
</div>
2018-04-27 15:19:08 +01:00
<DialogButtons
primaryButton={_t("bug_reporting|send_logs")}
2021-06-14 21:23:28 +01:00
onPrimaryButtonClick={this.onSubmit}
2018-04-27 15:19:08 +01:00
focus={true}
2021-06-14 21:23:28 +01:00
onCancel={this.onCancel}
2018-04-27 15:19:08 +01:00
disabled={this.state.busy}
/>
</BaseDialog>
);
}
}