Merge pull request #3849 from matrix-org/travis/download-logs

Add a rageshake function to download the logs locally
pull/21833/head
Michael Telatynski 2020-08-18 17:50:01 +01:00 committed by GitHub
commit 1fce3fd2ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 176 additions and 28 deletions

View File

@ -96,6 +96,7 @@
"react-transition-group": "^4.4.1",
"resize-observer-polyfill": "^1.5.1",
"sanitize-html": "^1.27.1",
"tar-js": "^0.3.0",
"text-encoding-utf-8": "^1.0.2",
"url": "^0.11.0",
"velocity-animate": "^1.5.2",
@ -127,6 +128,7 @@
"@types/lodash": "^4.14.158",
"@types/modernizr": "^3.5.3",
"@types/node": "^12.12.51",
"@types/pako": "^1.0.1",
"@types/qrcode": "^1.3.4",
"@types/react": "^16.9",
"@types/react-dom": "^16.9.8",

View File

@ -60,6 +60,7 @@
@import "./views/context_menus/_WidgetContextMenu.scss";
@import "./views/dialogs/_AddressPickerDialog.scss";
@import "./views/dialogs/_Analytics.scss";
@import "./views/dialogs/_BugReportDialog.scss";
@import "./views/dialogs/_ChangelogDialog.scss";
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
@import "./views/dialogs/_ConfirmUserActionDialog.scss";

View File

@ -0,0 +1,23 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
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.
*/
.mx_BugReportDialog {
.mx_BugReportDialog_download {
.mx_AccessibleButton_kind_link {
padding-left: 0;
}
}
}

View File

@ -23,7 +23,8 @@ import * as sdk from '../../../index';
import SdkConfig from '../../../SdkConfig';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
import sendBugReport from '../../../rageshake/submit-rageshake';
import sendBugReport, {downloadBugReport} from '../../../rageshake/submit-rageshake';
import AccessibleButton from "../elements/AccessibleButton";
export default class BugReportDialog extends React.Component {
constructor(props) {
@ -35,6 +36,8 @@ export default class BugReportDialog extends React.Component {
issueUrl: "",
text: "",
progress: null,
downloadBusy: false,
downloadProgress: null,
};
this._unmounted = false;
this._onSubmit = this._onSubmit.bind(this);
@ -43,6 +46,7 @@ export default class BugReportDialog extends React.Component {
this._onIssueUrlChange = this._onIssueUrlChange.bind(this);
this._onSendLogsChange = this._onSendLogsChange.bind(this);
this._sendProgressCallback = this._sendProgressCallback.bind(this);
this._downloadProgressCallback = this._downloadProgressCallback.bind(this);
}
componentWillUnmount() {
@ -95,6 +99,31 @@ export default class BugReportDialog extends React.Component {
});
}
_onDownload = async (ev) => {
this.setState({ downloadBusy: true });
this._downloadProgressCallback(_t("Preparing to download logs"));
try {
await downloadBugReport({
sendLogs: true,
progressCallback: this._downloadProgressCallback,
label: this.props.label,
});
this.setState({
downloadBusy: false,
downloadProgress: null,
});
} catch (err) {
if (!this._unmounted) {
this.setState({
downloadBusy: false,
downloadProgress: _t("Failed to send logs: ") + `${err.message}`,
});
}
}
};
_onTextChange(ev) {
this.setState({ text: ev.target.value });
}
@ -114,6 +143,13 @@ export default class BugReportDialog extends React.Component {
this.setState({progress: progress});
}
_downloadProgressCallback(downloadProgress) {
if (this._unmounted) {
return;
}
this.setState({ downloadProgress });
}
render() {
const Loader = sdk.getComponent("elements.Spinner");
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
@ -173,6 +209,14 @@ export default class BugReportDialog extends React.Component {
},
) }
</b></p>
<div className="mx_BugReportDialog_download">
<AccessibleButton onClick={this._onDownload} kind="link" disabled={this.state.downloadBusy}>
{ _t("Download logs") }
</AccessibleButton>
{this.state.downloadProgress && <span>{this.state.downloadProgress} ...</span>}
</div>
<Field
type="text"
className="mx_BugReportDialog_field_input"

View File

@ -503,7 +503,8 @@
"Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout",
"Collecting app version information": "Collecting app version information",
"Collecting logs": "Collecting logs",
"Uploading report": "Uploading report",
"Uploading logs": "Uploading logs",
"Downloading logs": "Downloading logs",
"Waiting for response from server": "Waiting for response from server",
"Messages containing my display name": "Messages containing my display name",
"Messages containing my username": "Messages containing my username",
@ -1579,9 +1580,11 @@
"Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.",
"Preparing to send logs": "Preparing to send logs",
"Failed to send logs: ": "Failed to send logs: ",
"Preparing to download logs": "Preparing to download logs",
"Reminder: Your browser is unsupported, so your experience may be unpredictable.": "Reminder: Your browser is unsupported, so your experience may be unpredictable.",
"Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.",
"Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.": "Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.",
"Download logs": "Download logs",
"GitHub issue": "GitHub issue",
"Notes": "Notes",
"If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.": "If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.",

View File

@ -21,10 +21,10 @@ import pako from 'pako';
import {MatrixClientPeg} from '../MatrixClientPeg';
import PlatformPeg from '../PlatformPeg';
import { _t } from '../languageHandler';
import Tar from "tar-js";
import * as rageshake from './rageshake';
// polyfill textencoder if necessary
import * as TextEncodingUtf8 from 'text-encoding-utf-8';
import SettingsStore from "../settings/SettingsStore";
@ -40,27 +40,7 @@ interface IOpts {
progressCallback?: (string) => void;
}
/**
* Send a bug report.
*
* @param {string} bugReportEndpoint HTTP url to send the report to
*
* @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 sent.
*/
export default async function sendBugReport(bugReportEndpoint: string, opts: IOpts) {
if (!bugReportEndpoint) {
throw new Error("No bug report endpoint has been set.");
}
opts = opts || {};
async function collectBugReport(opts: IOpts = {}, gzipLogs = true) {
const progressCallback = opts.progressCallback || (() => {});
progressCallback(_t("Collecting app version information"));
@ -187,19 +167,104 @@ export default async function sendBugReport(bugReportEndpoint: string, opts: IOp
const logs = await rageshake.getLogsForReport();
for (const entry of logs) {
// encode as UTF-8
const buf = new TextEncoder().encode(entry.lines);
let buf = new TextEncoder().encode(entry.lines);
// compress
const compressed = pako.gzip(buf);
if (gzipLogs) {
buf = pako.gzip(buf);
}
body.append('compressed-log', new Blob([compressed]), entry.id);
body.append('compressed-log', new Blob([buf]), entry.id);
}
}
progressCallback(_t("Uploading report"));
return body;
}
/**
* Send a bug report.
*
* @param {string} bugReportEndpoint HTTP url to send the report to
*
* @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 sent.
*/
export default async function sendBugReport(bugReportEndpoint: string, opts: IOpts = {}) {
if (!bugReportEndpoint) {
throw new Error("No bug report endpoint has been set.");
}
const progressCallback = opts.progressCallback || (() => {});
const body = await collectBugReport(opts);
progressCallback(_t("Uploading logs"));
await _submitReport(bugReportEndpoint, body, progressCallback);
}
/**
* Downloads the files from a bug report. This is the same as sendBugReport,
* but instead causes the browser to download the files locally.
*
* @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).
*/
export async function downloadBugReport(opts: IOpts = {}) {
const progressCallback = opts.progressCallback || (() => {});
const body = await collectBugReport(opts, false);
progressCallback(_t("Downloading logs"));
let metadata = "";
const tape = new Tar();
let i = 0;
for (const [key, value] of body.entries()) {
if (key === 'compressed-log') {
await new Promise((resolve => {
const reader = new FileReader();
reader.addEventListener('loadend', ev => {
tape.append(`log-${i++}.log`, new TextDecoder().decode(ev.target.result as ArrayBuffer));
resolve();
});
reader.readAsArrayBuffer(value as Blob);
}));
} else {
metadata += `${key} = ${value}\n`;
}
}
tape.append('issue.txt', metadata);
// 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');
dl.href = `data:application/octet-stream;base64,${btoa(uint8ToString(tape.out))}`;
dl.download = 'rageshake.tar';
document.body.appendChild(dl);
dl.click();
document.body.removeChild(dl);
}
// Source: https://github.com/beatgammit/tar-js/blob/master/examples/main.js
function uint8ToString(buf: Buffer) {
let out = '';
for (let i = 0; i < buf.length; i += 1) {
out += String.fromCharCode(buf[i]);
}
return out;
}
function _submitReport(endpoint: string, body: FormData, progressCallback: (string) => void) {
return new Promise((resolve, reject) => {
const req = new XMLHttpRequest();

View File

@ -1633,6 +1633,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.51.tgz#446a67af8c5ff98947d7cef296484c6ad47ddb16"
integrity sha512-6ILqt8iNThALrxDv2Q4LyYFQxULQz96HKNIFd4s9QRQaiHINYeUpLqeU/2IU7YMtvipG1fQVAy//vY8/fX1Y9w==
"@types/pako@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/pako/-/pako-1.0.1.tgz#33b237f3c9aff44d0f82fe63acffa4a365ef4a61"
integrity sha512-GdZbRSJ3Cv5fiwT6I0SQ3ckeN2PWNqxd26W9Z2fCK1tGrrasGy4puvNFtnddqH9UJFMQYXxEuuB7B8UK+LLwSg==
"@types/prop-types@*":
version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
@ -9100,6 +9105,11 @@ tapable@^1.0.0, tapable@^1.1.3:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
tar-js@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/tar-js/-/tar-js-0.3.0.tgz#6949aabfb0ba18bb1562ae51a439fd0f30183a17"
integrity sha1-aUmqv7C6GLsVYq5RpDn9DzAYOhc=
terser-webpack-plugin@^1.4.3:
version "1.4.4"
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz#2c63544347324baafa9a56baaddf1634c8abfc2f"