Allow local log downloads when a rageshake URL is not configured. (#31716)
* Add support for storing debug logs locally and allowing local downloads. * static * Comprehensive testing for bug report flow. * Driveby cleanup of typography * fix i18n * Improvements to UX * More testing * update snaps * linting * lint * Fix feedback * Fix boldnewss * fix bold * fix heading * Increase test coverage * remove focus * Don't show the FAQ depending on whether you can submit feedback. * move reset * fix err * Remove unused * update snap * Remove text * Bumping up that coverage * tidy * lint * update snap * Use a const * fix imports * Remove import in e2e test * whoops
This commit is contained in:
2
src/@types/global.d.ts
vendored
2
src/@types/global.d.ts
vendored
@@ -80,7 +80,7 @@ declare global {
|
||||
function setTimeout(handler: TimerHandler, timeout: number, ...arguments: any[]): number;
|
||||
|
||||
interface Window {
|
||||
mxSendRageshake: (text: string, withLogs?: boolean) => void;
|
||||
mxSendRageshake: (text: string, withLogs?: boolean) => Promise<void>;
|
||||
matrixLogger: typeof logger;
|
||||
matrixChat?: MatrixChat;
|
||||
mxSendSentryReport: (userText: string, issueUrl: string, error: Error) => Promise<void>;
|
||||
|
||||
@@ -17,6 +17,12 @@ import { type ValidatedServerConfig } from "./utils/ValidatedServerConfig";
|
||||
/* eslint-disable camelcase */
|
||||
/* eslint @typescript-eslint/naming-convention: ["error", { "selector": "property", "format": ["snake_case"] } ] */
|
||||
|
||||
/**
|
||||
* Bug reports are enabled but must only be locally
|
||||
* downloadable.
|
||||
*/
|
||||
export const BugReportEndpointURLLocal = "local";
|
||||
|
||||
// see element-web config.md for non-developer docs
|
||||
export interface IConfigOptions {
|
||||
// dev note: while true that this is arbitrary JSON, it's valuable to enforce that all
|
||||
@@ -98,7 +104,10 @@ export interface IConfigOptions {
|
||||
show_labs_settings: boolean;
|
||||
features?: Record<string, boolean>; // <FeatureName, EnabledBool>
|
||||
|
||||
bug_report_endpoint_url?: string; // omission disables bug reporting
|
||||
/**
|
||||
* Bug report endpoint URL. "local" means the logs should not be uploaded.
|
||||
*/
|
||||
bug_report_endpoint_url?: typeof BugReportEndpointURLLocal | string; // omission disables bug reporting
|
||||
uisi_autorageshake_app?: string; // defaults to "element-auto-uisi"
|
||||
sentry?: {
|
||||
dsn: string;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
@@ -10,7 +11,7 @@ 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";
|
||||
import { Link, Text } from "@vector-im/compound-web";
|
||||
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import Modal from "../../../Modal";
|
||||
@@ -26,6 +27,7 @@ import { sendSentryReport } from "../../../sentry";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { getBrowserSupport } from "../../../SupportedBrowser";
|
||||
import { BugReportEndpointURLLocal } from "../../../IConfigOptions";
|
||||
|
||||
export interface BugReportDialogProps {
|
||||
onFinished: (success: boolean) => void;
|
||||
@@ -48,6 +50,7 @@ interface IState {
|
||||
export default class BugReportDialog extends React.Component<BugReportDialogProps, IState> {
|
||||
private unmounted: boolean;
|
||||
private issueRef: React.RefObject<Field | null>;
|
||||
private readonly isLocalOnly: boolean;
|
||||
|
||||
public constructor(props: BugReportDialogProps) {
|
||||
super(props);
|
||||
@@ -65,6 +68,8 @@ export default class BugReportDialog extends React.Component<BugReportDialogProp
|
||||
|
||||
this.unmounted = false;
|
||||
this.issueRef = React.createRef();
|
||||
// This config is static at runtime, but may change during tests.
|
||||
this.isLocalOnly = SdkConfig.get().bug_report_endpoint_url === BugReportEndpointURLLocal;
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
@@ -142,6 +147,14 @@ export default class BugReportDialog extends React.Component<BugReportDialogProp
|
||||
this.setState({ busy: true, progress: null, err: null });
|
||||
this.sendProgressCallback(_t("bug_reporting|preparing_logs"));
|
||||
|
||||
if (this.isLocalOnly) {
|
||||
// Shouldn't reach here, but throw in case we do.
|
||||
this.setState({
|
||||
err: _t("bug_reporting|failed_send_logs_causes|unknown_error"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
sendBugReport(SdkConfig.get().bug_report_endpoint_url, {
|
||||
userText,
|
||||
sendLogs: true,
|
||||
@@ -241,77 +254,77 @@ export default class BugReportDialog extends React.Component<BugReportDialogProp
|
||||
(window.Modernizr && Object.values(window.Modernizr).some((support) => support === false)) ||
|
||||
!getBrowserSupport()
|
||||
) {
|
||||
warning = (
|
||||
<p>
|
||||
<strong>{_t("bug_reporting|unsupported_browser")}</strong>
|
||||
</p>
|
||||
);
|
||||
warning = <Text weight="semibold">{_t("bug_reporting|unsupported_browser")}</Text>;
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
className="mx_BugReportDialog"
|
||||
onFinished={this.onCancel}
|
||||
title={_t("bug_reporting|submit_debug_logs")}
|
||||
title={this.isLocalOnly ? _t("bug_reporting|download_logs") : _t("bug_reporting|submit_debug_logs")}
|
||||
contentId="mx_Dialog_content"
|
||||
>
|
||||
<div className="mx_Dialog_content" id="mx_Dialog_content">
|
||||
{warning}
|
||||
<p>{_t("bug_reporting|description")}</p>
|
||||
<p>
|
||||
<strong>
|
||||
{_t(
|
||||
"bug_reporting|before_submitting",
|
||||
{},
|
||||
{
|
||||
a: (sub) => (
|
||||
<a
|
||||
target="_blank"
|
||||
href={SdkConfig.get().feedback.new_issue_url}
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{sub}
|
||||
</a>
|
||||
),
|
||||
},
|
||||
)}
|
||||
</strong>
|
||||
</p>
|
||||
<Text>{_t("bug_reporting|description")}</Text>
|
||||
{this.isLocalOnly ? (
|
||||
<>{this.state.downloadProgress && <span>{this.state.downloadProgress} ...</span>}</>
|
||||
) : (
|
||||
<>
|
||||
<Text weight="semibold">
|
||||
{_t(
|
||||
"bug_reporting|before_submitting",
|
||||
{},
|
||||
{
|
||||
a: (sub) => (
|
||||
<Link target="_blank" href={SdkConfig.get().feedback.new_issue_url}>
|
||||
{sub}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<div className="mx_BugReportDialog_download">
|
||||
<AccessibleButton onClick={this.onDownload} kind="link" disabled={this.state.downloadBusy}>
|
||||
{_t("bug_reporting|download_logs")}
|
||||
</AccessibleButton>
|
||||
{this.state.downloadProgress && <span>{this.state.downloadProgress} ...</span>}
|
||||
</div>
|
||||
<div className="mx_BugReportDialog_download">
|
||||
<AccessibleButton
|
||||
onClick={this.onDownload}
|
||||
kind="link"
|
||||
disabled={this.state.downloadBusy}
|
||||
>
|
||||
{_t("bug_reporting|download_logs")}
|
||||
</AccessibleButton>
|
||||
{this.state.downloadProgress && <span>{this.state.downloadProgress} ...</span>}
|
||||
</div>
|
||||
|
||||
<Field
|
||||
type="text"
|
||||
className="mx_BugReportDialog_field_input"
|
||||
label={_t("bug_reporting|github_issue")}
|
||||
onChange={this.onIssueUrlChange}
|
||||
value={this.state.issueUrl}
|
||||
placeholder="https://github.com/vector-im/element-web/issues/..."
|
||||
ref={this.issueRef}
|
||||
/>
|
||||
<Field
|
||||
className="mx_BugReportDialog_field_input"
|
||||
element="textarea"
|
||||
label={_t("bug_reporting|textarea_label")}
|
||||
rows={5}
|
||||
onChange={this.onTextChange}
|
||||
value={this.state.text}
|
||||
placeholder={_t("bug_reporting|additional_context")}
|
||||
/>
|
||||
{progress}
|
||||
{error}
|
||||
<Field
|
||||
type="text"
|
||||
className="mx_BugReportDialog_field_input"
|
||||
label={_t("bug_reporting|github_issue")}
|
||||
onChange={this.onIssueUrlChange}
|
||||
value={this.state.issueUrl}
|
||||
placeholder="https://github.com/vector-im/element-web/issues/..."
|
||||
ref={this.issueRef}
|
||||
/>
|
||||
<Field
|
||||
className="mx_BugReportDialog_field_input"
|
||||
element="textarea"
|
||||
label={_t("bug_reporting|textarea_label")}
|
||||
rows={5}
|
||||
onChange={this.onTextChange}
|
||||
value={this.state.text}
|
||||
placeholder={_t("bug_reporting|additional_context")}
|
||||
/>
|
||||
{progress}
|
||||
{error}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<DialogButtons
|
||||
primaryButton={_t("bug_reporting|send_logs")}
|
||||
onPrimaryButtonClick={this.onSubmit}
|
||||
primaryButton={this.isLocalOnly ? _t("bug_reporting|download_logs") : _t("bug_reporting|send_logs")}
|
||||
onPrimaryButtonClick={this.isLocalOnly ? this.onDownload : this.onSubmit}
|
||||
focus={true}
|
||||
onCancel={this.onCancel}
|
||||
disabled={this.state.busy}
|
||||
disabled={this.isLocalOnly ? this.state.downloadBusy : this.state.busy}
|
||||
/>
|
||||
</BaseDialog>
|
||||
);
|
||||
|
||||
@@ -45,6 +45,7 @@ const FeedbackDialog: React.FC<IProps> = (props: IProps) => {
|
||||
const onFinished = (sendFeedback: boolean): void => {
|
||||
if (hasFeedback && sendFeedback) {
|
||||
const label = props.feature ? `${props.feature}-feedback` : "feedback";
|
||||
// TODO: Handle rejection.
|
||||
submitFeedback(label, comment, canContact);
|
||||
|
||||
Modal.createDialog(InfoDialog, {
|
||||
|
||||
@@ -38,7 +38,7 @@ const GenericFeatureFeedbackDialog: React.FC<IProps> = ({
|
||||
|
||||
const sendFeedback = async (ok: boolean): Promise<void> => {
|
||||
if (!ok) return onFinished(false);
|
||||
|
||||
// TODO: Handle rejection.
|
||||
submitFeedback(rageshakeLabel, comment, canContact, rageshakeData);
|
||||
onFinished(true);
|
||||
|
||||
|
||||
43
src/components/views/elements/BugReportDialogButton.tsx
Normal file
43
src/components/views/elements/BugReportDialogButton.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from "react";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import Modal from "../../../Modal";
|
||||
import BugReportDialog, { type BugReportDialogProps } from "../dialogs/BugReportDialog";
|
||||
import { BugReportEndpointURLLocal } from "../../../IConfigOptions";
|
||||
|
||||
/**
|
||||
* Renders a button to open the BugReportDialog *if* the configuration
|
||||
* supports it.
|
||||
*/
|
||||
export function BugReportDialogButton({
|
||||
label,
|
||||
error,
|
||||
}: Pick<BugReportDialogProps, "label" | "error">): React.ReactElement | null {
|
||||
const bugReportUrl = SdkConfig.get().bug_report_endpoint_url;
|
||||
const onClick = useCallback(() => {
|
||||
Modal.createDialog(BugReportDialog, {
|
||||
label,
|
||||
error,
|
||||
});
|
||||
}, [label, error]);
|
||||
|
||||
if (!bugReportUrl) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Button kind="secondary" size="sm" onClick={onClick}>
|
||||
{bugReportUrl === BugReportEndpointURLLocal
|
||||
? _t("bug_reporting|download_logs")
|
||||
: _t("bug_reporting|submit_debug_logs")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -12,10 +12,9 @@ import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import PlatformPeg from "../../../PlatformPeg";
|
||||
import Modal from "../../../Modal";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import BugReportDialog from "../dialogs/BugReportDialog";
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
import { BugReportDialogButton } from "./BugReportDialogButton";
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
@@ -60,13 +59,6 @@ export default class ErrorBoundary extends React.PureComponent<Props, IState> {
|
||||
});
|
||||
};
|
||||
|
||||
private onBugReport = (): void => {
|
||||
Modal.createDialog(BugReportDialog, {
|
||||
label: "react-soft-crash",
|
||||
error: this.state.error,
|
||||
});
|
||||
};
|
||||
|
||||
public render(): ReactNode {
|
||||
if (this.state.error) {
|
||||
const newIssueUrl = SdkConfig.get().feedback.new_issue_url;
|
||||
@@ -95,9 +87,7 @@ export default class ErrorBoundary extends React.PureComponent<Props, IState> {
|
||||
|
||||
{_t("bug_reporting|description")}
|
||||
</p>
|
||||
<AccessibleButton onClick={this.onBugReport} kind="primary">
|
||||
{_t("bug_reporting|submit_debug_logs")}
|
||||
</AccessibleButton>
|
||||
<BugReportDialogButton error={this.state.error} label="react-soft-crash" />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,12 +12,11 @@ import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import Modal from "../../../Modal";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import BugReportDialog from "../dialogs/BugReportDialog";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import ViewSource from "../../structures/ViewSource";
|
||||
import { type Layout } from "../../../settings/enums/Layout";
|
||||
import { BugReportDialogButton } from "../elements/BugReportDialogButton";
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
@@ -42,13 +41,6 @@ export default class TileErrorBoundary extends React.Component<IProps, IState> {
|
||||
return { error };
|
||||
}
|
||||
|
||||
private onBugReport = (): void => {
|
||||
Modal.createDialog(BugReportDialog, {
|
||||
label: "react-soft-crash-tile",
|
||||
error: this.state.error,
|
||||
});
|
||||
};
|
||||
|
||||
private onViewSource = (): void => {
|
||||
Modal.createDialog(
|
||||
ViewSource,
|
||||
@@ -69,18 +61,6 @@ export default class TileErrorBoundary extends React.Component<IProps, IState> {
|
||||
mx_EventTile_tileError: true,
|
||||
};
|
||||
|
||||
let submitLogsButton;
|
||||
if (SdkConfig.get().bug_report_endpoint_url) {
|
||||
submitLogsButton = (
|
||||
<>
|
||||
|
||||
<AccessibleButton kind="link" onClick={this.onBugReport}>
|
||||
{_t("bug_reporting|submit_debug_logs")}
|
||||
</AccessibleButton>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
let viewSourceButton;
|
||||
if (mxEvent && SettingsStore.getValue("developerMode")) {
|
||||
viewSourceButton = (
|
||||
@@ -99,7 +79,7 @@ export default class TileErrorBoundary extends React.Component<IProps, IState> {
|
||||
<span>
|
||||
{_t("timeline|error_rendering_message")}
|
||||
{mxEvent && ` (${mxEvent.getType()})`}
|
||||
{submitLogsButton}
|
||||
<BugReportDialogButton error={this.state.error} label="react-tile-soft-crash" />
|
||||
{viewSourceButton}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -13,16 +13,15 @@ import { type EmptyObject } from "matrix-js-sdk/src/matrix";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import SdkConfig from "../../../../../SdkConfig";
|
||||
import Modal from "../../../../../Modal";
|
||||
import PlatformPeg from "../../../../../PlatformPeg";
|
||||
import UpdateCheckButton from "../../UpdateCheckButton";
|
||||
import BugReportDialog from "../../../dialogs/BugReportDialog";
|
||||
import CopyableText from "../../../elements/CopyableText";
|
||||
import SettingsTab from "../SettingsTab";
|
||||
import { SettingsSection } from "../../shared/SettingsSection";
|
||||
import { SettingsSubsection, SettingsSubsectionText } from "../../shared/SettingsSubsection";
|
||||
import ExternalLink from "../../../elements/ExternalLink";
|
||||
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
|
||||
import { BugReportDialogButton } from "../../../elements/BugReportDialogButton";
|
||||
|
||||
interface IState {
|
||||
appVersion: string | null;
|
||||
@@ -80,10 +79,6 @@ export default class HelpUserSettingsTab extends React.Component<EmptyObject, IS
|
||||
});
|
||||
};
|
||||
|
||||
private onBugReport = (): void => {
|
||||
Modal.createDialog(BugReportDialog, {});
|
||||
};
|
||||
|
||||
private renderLegal(): ReactNode {
|
||||
const tocLinks = SdkConfig.get().terms_and_conditions_links;
|
||||
if (!tocLinks) return null;
|
||||
@@ -231,9 +226,7 @@ export default class HelpUserSettingsTab extends React.Component<EmptyObject, IS
|
||||
</>
|
||||
}
|
||||
>
|
||||
<AccessibleButton onClick={this.onBugReport} kind="primary_outline">
|
||||
{_t("bug_reporting|submit_debug_logs")}
|
||||
</AccessibleButton>
|
||||
<BugReportDialogButton />
|
||||
<SettingsSubsectionText>
|
||||
{_t(
|
||||
"bug_reporting|matrix_security_issue",
|
||||
|
||||
@@ -44,6 +44,7 @@ import { type JitsiCallMemberContent, JitsiCallMemberEventType } from "../call-t
|
||||
import SdkConfig from "../SdkConfig.ts";
|
||||
import DMRoomMap from "../utils/DMRoomMap.ts";
|
||||
import { type WidgetMessaging, WidgetMessagingEvent } from "../stores/widgets/WidgetMessaging.ts";
|
||||
import { BugReportEndpointURLLocal } from "../IConfigOptions.ts";
|
||||
|
||||
const TIMEOUT_MS = 16000;
|
||||
const logger = rootLogger.getChild("models/Call");
|
||||
@@ -769,7 +770,7 @@ export class ElementCall extends Call {
|
||||
}
|
||||
|
||||
const rageshakeSubmitUrl = SdkConfig.get("bug_report_endpoint_url");
|
||||
if (rageshakeSubmitUrl) {
|
||||
if (rageshakeSubmitUrl && rageshakeSubmitUrl !== BugReportEndpointURLLocal) {
|
||||
params.append("rageshakeSubmitUrl", rageshakeSubmitUrl);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import * as rageshake from "./rageshake";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import SdkConfig from "../SdkConfig";
|
||||
import { getServerVersionFromFederationApi } from "../components/views/dialogs/devtools/ServerInfo";
|
||||
import type * as Tar from "tar-js";
|
||||
import { BugReportEndpointURLLocal } from "../IConfigOptions";
|
||||
|
||||
interface IOpts {
|
||||
labels?: string[];
|
||||
@@ -342,7 +344,7 @@ async function collectLogs(
|
||||
* the server does not respond with an expected body format.
|
||||
*/
|
||||
export default async function sendBugReport(bugReportEndpoint?: string, opts: IOpts = {}): Promise<string> {
|
||||
if (!bugReportEndpoint) {
|
||||
if (!bugReportEndpoint || bugReportEndpoint === BugReportEndpointURLLocal) {
|
||||
throw new Error("No bug report endpoint has been set.");
|
||||
}
|
||||
|
||||
@@ -354,20 +356,12 @@ export default async function sendBugReport(bugReportEndpoint?: string, opts: IO
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the files from a bug report. This is the same as sendBugReport,
|
||||
* but instead causes the browser to download the files locally.
|
||||
* Loads a bug report into a tarball.
|
||||
*
|
||||
* @param {object} opts optional dictionary of options
|
||||
*
|
||||
* @param {string} opts.userText Any additional user input.
|
||||
*
|
||||
* @param {boolean} opts.sendLogs True to send logs
|
||||
*
|
||||
* @param {function(string)} opts.progressCallback Callback to call with progress updates
|
||||
*
|
||||
* @return {Promise} Resolved when the bug report is downloaded (or started).
|
||||
* @param opts optional dictionary of options
|
||||
* @return Resolves with a Tarball object.
|
||||
*/
|
||||
export async function downloadBugReport(opts: IOpts = {}): Promise<void> {
|
||||
export async function loadBugReport(opts: IOpts = {}): Promise<Tar> {
|
||||
const Tar = (await import("tar-js")).default;
|
||||
const progressCallback = opts.progressCallback || ((): void => {});
|
||||
const body = await collectBugReport(opts, false);
|
||||
@@ -391,7 +385,18 @@ export async function downloadBugReport(opts: IOpts = {}): Promise<void> {
|
||||
}
|
||||
}
|
||||
tape.append("issue.txt", metadata);
|
||||
return tape;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the files from a bug report. This is the same as sendBugReport,
|
||||
* but instead causes the browser to download the files locally.
|
||||
*
|
||||
* @param opts optional dictionary of options
|
||||
* @return Resolved when the bug report is downloaded (or started).
|
||||
*/
|
||||
export async function downloadBugReport(opts: IOpts = {}): Promise<void> {
|
||||
const tape = await loadBugReport(opts);
|
||||
// We have to create a new anchor to download if we want a filename. Otherwise we could
|
||||
// just use window.open.
|
||||
const dl = document.createElement("a");
|
||||
@@ -417,6 +422,10 @@ export async function submitFeedback(
|
||||
canContact = false,
|
||||
extraData: Record<string, any> = {},
|
||||
): Promise<void> {
|
||||
const bugReportEndpointUrl = SdkConfig.get().bug_report_endpoint_url;
|
||||
if (!bugReportEndpointUrl || bugReportEndpointUrl === BugReportEndpointURLLocal) {
|
||||
throw new Error("Bug report URL is not set or local");
|
||||
}
|
||||
let version: string | undefined;
|
||||
try {
|
||||
version = await PlatformPeg.get()?.getAppVersion();
|
||||
@@ -436,11 +445,7 @@ export async function submitFeedback(
|
||||
body.append(k, JSON.stringify(extraData[k]));
|
||||
}
|
||||
|
||||
const bugReportEndpointUrl = SdkConfig.get().bug_report_endpoint_url;
|
||||
|
||||
if (bugReportEndpointUrl) {
|
||||
await submitReport(bugReportEndpointUrl, body, () => {});
|
||||
}
|
||||
await submitReport(bugReportEndpointUrl, body, () => {});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -405,15 +405,14 @@ export const SETTINGS: Settings = {
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
faq: () =>
|
||||
SdkConfig.get().bug_report_endpoint_url && (
|
||||
<>
|
||||
<h4>{_t("labs|video_rooms_faq1_question")}</h4>
|
||||
<p>{_t("labs|video_rooms_faq1_answer")}</p>
|
||||
<h4>{_t("labs|video_rooms_faq2_question")}</h4>
|
||||
<p>{_t("labs|video_rooms_faq2_answer")}</p>
|
||||
</>
|
||||
),
|
||||
faq: () => (
|
||||
<>
|
||||
<h4>{_t("labs|video_rooms_faq1_question")}</h4>
|
||||
<p>{_t("labs|video_rooms_faq1_answer")}</p>
|
||||
<h4>{_t("labs|video_rooms_faq2_question")}</h4>
|
||||
<p>{_t("labs|video_rooms_faq2_answer")}</p>
|
||||
</>
|
||||
),
|
||||
feedbackLabel: "video-room-feedback",
|
||||
feedbackSubheading: _td("labs|video_rooms_feedbackSubheading"),
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
|
||||
@@ -6,10 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { BugReportEndpointURLLocal } from "../IConfigOptions";
|
||||
import SdkConfig from "../SdkConfig";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import { UIFeature } from "../settings/UIFeature";
|
||||
|
||||
export function shouldShowFeedback(): boolean {
|
||||
return !!SdkConfig.get().bug_report_endpoint_url && SettingsStore.getValue(UIFeature.Feedback);
|
||||
const url = SdkConfig.get().bug_report_endpoint_url;
|
||||
return !!url && url !== BugReportEndpointURLLocal && SettingsStore.getValue(UIFeature.Feedback);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import * as rageshake from "../rageshake/rageshake";
|
||||
import SdkConfig from "../SdkConfig";
|
||||
import sendBugReport from "../rageshake/submit-rageshake";
|
||||
import sendBugReport, { loadBugReport } from "../rageshake/submit-rageshake";
|
||||
import { BugReportEndpointURLLocal } from "../IConfigOptions";
|
||||
|
||||
export function initRageshake(): Promise<void> {
|
||||
// we manually check persistence for rageshakes ourselves
|
||||
@@ -54,28 +55,40 @@ export function initRageshakeStore(): Promise<void> {
|
||||
return rageshake.tryInitStorage();
|
||||
}
|
||||
|
||||
window.mxSendRageshake = function (text: string, withLogs?: boolean): void {
|
||||
window.mxSendRageshake = async function (text: string, withLogs = true): Promise<void> {
|
||||
const url = SdkConfig.get().bug_report_endpoint_url;
|
||||
if (!url) {
|
||||
logger.error("Cannot send a rageshake - no bug_report_endpoint_url configured");
|
||||
return;
|
||||
}
|
||||
|
||||
if (withLogs === undefined) withLogs = true;
|
||||
if (!text || !text.trim()) {
|
||||
logger.error("Cannot send a rageshake without a message - please tell us what went wrong");
|
||||
return;
|
||||
}
|
||||
sendBugReport(url, {
|
||||
userText: text,
|
||||
sendLogs: withLogs,
|
||||
progressCallback: logger.log.bind(console),
|
||||
}).then(
|
||||
() => {
|
||||
if (url === BugReportEndpointURLLocal) {
|
||||
try {
|
||||
const tape = await loadBugReport({
|
||||
userText: text,
|
||||
sendLogs: withLogs,
|
||||
progressCallback: logger.log.bind(console),
|
||||
});
|
||||
const blob = new Blob([new Uint8Array(tape.out)], { type: "application/gzip" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
logger.log(`Your logs are available at ${url}`);
|
||||
} catch (err) {
|
||||
logger.error("Failed to load bug report", err);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await sendBugReport(url, {
|
||||
userText: text,
|
||||
sendLogs: withLogs,
|
||||
progressCallback: logger.log.bind(console),
|
||||
});
|
||||
logger.log("Bug report sent!");
|
||||
},
|
||||
(err) => {
|
||||
logger.error(err);
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error("Failed to send bug report", err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user