Skip to content

Commit

Permalink
Improve bug report template structure
Browse files Browse the repository at this point in the history
Fixes #2516, which caused newlines in device link
Link to config files
Only link to devices page for ubuntu touch devices
mention if a local config file was used
  • Loading branch information
NeoTheThird committed Mar 23, 2022
1 parent 8107d24 commit c3d3b47
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 99 deletions.
4 changes: 2 additions & 2 deletions __mocks__/open-cuts-reporter.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const ubuntuPastebin = {
const openCutsReporter = {
OpenCutsReporter: jest.fn().mockReturnValue({
smartRun: jest.fn()
})
};

module.exports = ubuntuPastebin;
module.exports = openCutsReporter;
22 changes: 10 additions & 12 deletions __mocks__/systeminformation.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
function osInfo(cb) {
cb({
distro: "distro",
release: "release",
codename: "codename",
platform: "platform",
kernel: "kernel",
arch: "arch",
build: "build",
servicepack: "servicepack"
});
}
const osInfo = jest.fn().mockResolvedValue({
distro: "distro",
release: "release",
codename: "codename",
platform: "platform",
kernel: "kernel",
arch: "arch",
build: "build",
servicepack: "servicepack"
});

module.exports.osInfo = osInfo;
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"semver": "^7.3.5",
"sudo-prompt": "^9.2.1",
"svelte-markdown": "^0.2.2",
"systeminformation": "^5.11.8",
"systeminformation": "^5.11.9",
"tsparticles": "^1.42.2",
"winston": "^3.6.0",
"yaml": "^2.0.0-8"
Expand Down
126 changes: 48 additions & 78 deletions src/lib/reporter.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use strict";

/*
* Copyright (C) 2020-2021 UBports Foundation <info@ubports.com>
* Copyright (C) 2020-2022 UBports Foundation <info@ubports.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand All @@ -24,6 +24,7 @@ const { path: cachePath } = require("./cache.js");
const log = require("./log.js");
const { OpenCutsReporter } = require("open-cuts-reporter");
const settings = require("./settings.js");
const cli = require("./cli.js");
const core = require("../core/core.js");
const { prompt } = require("./prompt.js");
const { paste } = require("./paste.js");
Expand All @@ -45,75 +46,47 @@ const MISSING_LOG =
* report errors or successes
*/
class Reporter {
/**
* Get device string
* @returns {String} codename of the device to install or a string indicating its absence
*/
getDeviceString() {
try {
return core.props && core.props.config && core.props.config.codename
? `${core.props.config.codename}`
: "(device not yet detected)";
} catch (e) {
return "unknown";
}
}

/**
* Get target os string
* @returns {String} codename of the os to install or a string indicating its absence
*/
getTargetOsString() {
try {
return core.props && core.props.os
? core.props.os.name
: "(target os not yet set)";
} catch (e) {
return "unknown";
}
}

/**
* Get settings string
* @returns {String} install settings string or a string indicating its absence
*/
getSettingsString() {
try {
`\`${JSON.stringify(core.props.settings || {})}\``;
} catch (e) {
return "unknown";
}
}

/**
* Get information about the os the installer is running on
* @async
* @returns {String} environment information
*/
async getEnvironment() {
return new Promise(function (resolve, reject) {
try {
osInfo(hostOs =>
resolve(
[
hostOs.distro,
hostOs.release,
hostOs.codename,
hostOs.platform,
hostOs.kernel,
hostOs.arch,
hostOs.build,
hostOs.servicepack,
`NodeJS ${process.version}`
]
.filter(i => i)
.join(" ")
)
);
} catch (error) {
return process.platform;
}
});
return osInfo()
.then(r =>
[
r.distro,
r.release,
r.codename,
r.platform,
r.kernel,
r.arch,
r.build,
r.servicepack,
`NodeJS ${process.version}`
]
.filter(i => i)
.join(" ")
)
.catch(() => process.platform);
}

/**
* get device link markdown from codename
* @param {String} codename device codename, not necessarily canonical
* @returns {String} device link markdown
*/
getDeviceLinkMarkdown(codename) {
return (
((core?.props?.config?.codename &&
`[\`${core?.props?.config?.codename}\`](https://github.com/ubports/installer-configs/blob/master/v2/devices/${core?.props?.config?.codename}.yml)` +
((core?.props?.config?.name &&
core?.props?.os?.name === "Ubuntu Touch" &&
` ([${core?.props?.config?.name}](https://devices.ubuntu-touch.io/device/${core?.props?.config?.codename}/))`) ||
` (${core?.props?.config?.name})`)) ||
(codename && `\`${codename}\``) ||
"(not device dependent)") + (cli.file ? " with local config file" : "")
);
}

/**
Expand All @@ -129,20 +102,15 @@ class Reporter {
[
`**UBports Installer \`${packageInfo.version}\` (${data.package})**`,
`Environment: \`${data.environment}\``,
...(data.device in ["unknown", "(device not yet detected)"]
? [`Device: ${data.device}`]
: [
`Device: [${data.device}]`,
`(https://devices.ubuntu-touch.io/device/${data.device})`
]),
`Target OS: ${this.getTargetOsString()}`,
`Settings: \`${this.getSettingsString()}\``,
`Device: ${this.getDeviceLinkMarkdown(data.device)}`,
`Target OS: ${core?.props?.os?.name}`,
`Settings: \`${JSON.stringify(core?.props?.settings || {})}\``,
`OPEN-CUTS run: ${runUrl || MISSING_LOG}`,
`Pastebin: ${logUrl || MISSING_LOG}`,
"\n",
data.comment,
"\n",
...(data.error ? ["**Error:**", "```", data.error, "```"] : []),
data.comment && `\n${data.comment?.trim()}\n`,
...(data.error && data.error !== "Unknown Error"
? ["**Error:**", "```", data.error, "```"]
: []),
"<!-- thank you for reporting! -->\n"
]
.filter(i => i)
Expand Down Expand Up @@ -230,7 +198,7 @@ class Reporter {
name: "Device",
type: "text",
placeholder: "Device codename",
value: this.getDeviceString(),
value: core?.props?.config?.codename || "",
required: true
},
{
Expand Down Expand Up @@ -308,7 +276,9 @@ class Reporter {
name: "Comment",
type: "text",
placeholder: "You can provide detailed information here...",
value: `Installed ${this.getTargetOsString()} on ${this.getDeviceString()} from a computer running ${await this.getEnvironment()}.`,
value: `Installed ${core?.props?.os?.name} on ${
core?.props?.config?.codename
} from a computer running ${await this.getEnvironment()}.`,
required: true
},
...this.genericFormFields()
Expand Down
101 changes: 96 additions & 5 deletions src/lib/reporter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ const log = require("./log.js");
jest.mock("./log.js");
const { paste } = require("./paste.js");
jest.mock("./paste.js");
const cli = require("./cli.js");
jest.mock("./cli.js");
const settings = require("./settings.js");
const core = require("../core/core.js");
jest.mock("../core/core.js");
const { prompt } = require("./prompt.js");
jest.mock("./prompt.js");
const { OpenCutsReporter } = require("open-cuts-reporter");
Expand All @@ -17,15 +21,102 @@ it("should be a singleton", () => {
expect(require("./reporter.js")).toBe(require("./reporter.js"));
});

beforeEach(() => jest.clearAllMocks());

describe("getEnvironment()", () => {
it("should resolve environment string", () => {
return reporter
.getEnvironment()
.then(r =>
expect(r).toEqual(
`distro release codename platform kernel arch build servicepack NodeJS ${process.version}`
)
);
});
it("should resolve platform on error", () => {
osInfo.mockRejectedValueOnce("oh no");
return reporter
.getEnvironment()
.then(r => expect(r).toEqual(process.platform));
});
});

describe("getDeviceLinkMarkdown()", () => {
it("should return default", () => {
expect(reporter.getDeviceLinkMarkdown()).toEqual("(not device dependent)");
});
it("should return codename", () => {
expect(reporter.getDeviceLinkMarkdown("a")).toEqual("`a`");
});
it("should mention cli", () => {
cli.file = "/tmp";
expect(reporter.getDeviceLinkMarkdown("a")).toEqual(
"`a` with local config file"
);
delete cli.file;
});
it("should assemble markdown", () => {
core.props = {
config: {
codename: "bacon",
name: "Oneplus One"
},
os: {
name: "SomeOS"
}
};
expect(reporter.getDeviceLinkMarkdown("something")).toEqual(
"[`bacon`](https://github.com/ubports/installer-configs/blob/master/v2/devices/bacon.yml) (Oneplus One)"
);
});
it("should assemble markdown with device page link for UT", () => {
core.props = {
config: {
codename: "bacon",
name: "Oneplus One"
},
os: {
name: "Ubuntu Touch"
}
};
expect(reporter.getDeviceLinkMarkdown("something")).toEqual(
"[`bacon`](https://github.com/ubports/installer-configs/blob/master/v2/devices/bacon.yml) ([Oneplus One](https://devices.ubuntu-touch.io/device/bacon/))"
);
});
});

describe("getDebugInfo()", () => {
it("should resolve debug without error", () => {
return reporter
.getDebugInfo({ error: "Everything exploded" })
.then(decodeURIComponent)
.then(r =>
expect(r).toContain("**Error:**\n```\nEverything exploded\n```")
);
});
it("should resolve debug without error on unknown", () => {
return reporter
.getDebugInfo({ error: "Unknown Error" })
.then(decodeURIComponent)
.then(r => expect(r).not.toContain("**Error:**"));
});
it("should resolve debug without error on null", () => {
return reporter
.getDebugInfo({})
.then(decodeURIComponent)
.then(r => expect(r).not.toContain("**Error:**"));
});
});

describe("prepareErrorReport()", () => {
it("should return error report object", () => {
expect(reporter.prepareErrorReport()).resolves.toBeDefined();
return reporter.prepareErrorReport().then(r => expect(r).toBeDefined);
});
});

describe("prepareSuccessReport()", () => {
it("should return success report object", () => {
expect(reporter.prepareSuccessReport()).resolves.toBeDefined();
return reporter.prepareSuccessReport().then(r => expect(r).toBeDefined);
});
});

Expand All @@ -45,11 +136,11 @@ describe("sendBugReport()", () => {
describe("sendOpenCutsRun()", () => {
it("should send open-cuts run", () => {
log.get.mockResolvedValue("log content");
expect(
reporter.sendOpenCutsRun(null, {
return reporter
.sendOpenCutsRun(null, {
result: "PASS"
})
).resolves.toEqual(undefined);
.then(r => expect(r).toEqual(undefined));
});
});

Expand Down

0 comments on commit c3d3b47

Please sign in to comment.