Skip to content

Commit

Permalink
webui: add option to report an issue to global menu
Browse files Browse the repository at this point in the history
  • Loading branch information
rvykydal committed Aug 21, 2023
1 parent 39667b3 commit 4c54b49
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 72 deletions.
4 changes: 2 additions & 2 deletions ui/webui/src/components/AnacondaHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { HeaderKebab } from "./HeaderKebab.jsx";

const _ = cockpit.gettext;

export const AnacondaHeader = ({ beta, title }) => {
export const AnacondaHeader = ({ beta, title, reportLinkURL }) => {
const prerelease = _("Pre-release");
const betanag = beta
? (
Expand Down Expand Up @@ -68,7 +68,7 @@ export const AnacondaHeader = ({ beta, title }) => {
<Text component="h1">{title}</Text>
</TextContent>
{betanag}
<HeaderKebab />
<HeaderKebab reportLinkURL={reportLinkURL} />
</Flex>
</PageSection>
);
Expand Down
153 changes: 111 additions & 42 deletions ui/webui/src/components/Error.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,17 @@ export const bugzillaPrefiledReportURL = (productQueryData) => {
return reportURL.href;
};

const addExceptionDataToReportURL = (url, exception) => {
const newUrl = new URL(url);

const context = exception.contextData?.context ? exception.contextData?.context + " " : "";

newUrl.searchParams.append(
"short_desc",
"WebUI: " + context + exception.name + ": " + exception.message
);
newUrl.searchParams.append(
"comment",
"Installer WebUI Critical Error:\n" + context + exception.name + ": " + exception.message + "\n\n" + _("Please attach the file /tmp/webui.log to the issue.")
);
return newUrl.href;
};

export const CriticalError = ({ exception, isBootIso, reportLinkURL }) => {
const reportURL = addExceptionDataToReportURL(reportLinkURL, exception);
export const BZReportModal = ({
description,
reportLinkURL,
idPrefix,
title,
titleIconVariant,
logFile,
detailsLabel,
detailsContent,
buttons
}) => {
const [logContent, setLogContent] = useState();
const [preparingReport, setPreparingReport] = useState(false);

Expand All @@ -82,63 +75,53 @@ export const CriticalError = ({ exception, isBootIso, reportLinkURL }) => {
const openBZIssue = (reportURL) => {
setPreparingReport(true);
cockpit
.file("/tmp/webui.log")
.file(logFile)
.replace(logContent)
.always(() => setPreparingReport(false))
.then(() => window.open(reportURL, "_blank", "noopener,noreferer"));
};

const context = exception.contextData?.context;

return (
<Modal
description={context
? cockpit.format(_("The installer cannot continue due to a critical error: $0"), _(context))
: _("The installer cannot continue due to a critical error.")}
id="critical-error-modal"
description={description}
id={idPrefix + "-bz-report-modal"}
isOpen
position="top"
showClose={false}
title={_("Critical error")}
titleIconVariant="danger"
title={title}
titleIconVariant={titleIconVariant}
variant={ModalVariant.large}
footer={
<>
{reportLinkURL &&
<Button
variant="primary"
isLoading={preparingReport}
isDisabled={logContent === undefined || preparingReport}
icon={<ExternalLinkAltIcon />}
onClick={() => openBZIssue(reportURL)}
onClick={() => openBZIssue(reportLinkURL)}
component="a">
{preparingReport ? _("Preparing report") : _("Report issue")}
</Button>}
<Button variant="secondary" onClick={exitGui}>
{isBootIso ? _("Reboot") : _("Quit")}
</Button>
{buttons}
</>
}>
<Form>
{detailsLabel &&
<FormGroup
fieldId="critical-error-review-details"
label={_("Error details")}
fieldId={idPrefix + "-bz-report-modal-details"}
label={detailsLabel}
>
<TextContent id="critical-error-review-details">
<Text component={TextVariants.p}>
{exception.name + ": " + exception.message}
</Text>
</TextContent>
</FormGroup>
{detailsContent}
</FormGroup>}
<FormGroup
fieldId="critical-error-review-attached-log"
fieldId={idPrefix + "-bz-report-modal-review-log"}
label={_("Log")}
>
<TextArea
value={logContent}
onChange={setLogContent}
resizeOrientation="vertical"
id="critical-error-review-attached-log"
id={idPrefix + "-bz-report-modal-review-log"}
isDisabled={logContent === undefined || preparingReport}
rows={7}
/>
Expand All @@ -153,6 +136,92 @@ export const CriticalError = ({ exception, isBootIso, reportLinkURL }) => {
);
};

const addExceptionDataToReportURL = (url, exception) => {
const newUrl = new URL(url);
const context = exception.contextData?.context ? exception.contextData?.context + " " : "";
newUrl.searchParams.append(
"short_desc",
"WebUI: " + context + exception.name + ": " + exception.message
);
newUrl.searchParams.append(
"comment",
"Installer WebUI Critical Error:\n" + context + exception.name + ": " + exception.message + "\n\n" + _("Please attach the file /tmp/webui.log to the issue.")
);
return newUrl.href;
};

const exceptionInfo = (exception, idPrefix) => {
return (
<TextContent id={idPrefix + "-bz-report-modal-details"}>
<Text component={TextVariants.p}>
{exception.name + ": " + exception.message}
</Text>
</TextContent>
);
};

const quitButton = (isBootIso) => {
return (
<Button variant="secondary" onClick={exitGui} key="reboot">
{isBootIso ? _("Reboot") : _("Quit")}
</Button>
);
};

export const CriticalError = ({ exception, isBootIso, reportLinkURL }) => {
const context = exception.contextData?.context;
const description = context
? cockpit.format(_("The installer cannot continue due to a critical error: $0"), _(context))
: _("The installer cannot continue due to a critical error.");
const idPrefix = "critical-error";

return (
<BZReportModal
description={description}
reportLinkURL={addExceptionDataToReportURL(reportLinkURL, exception)}
idPrefix={idPrefix}
title={_("Criticall error")}
titleIconVariant="danger"
logFile="/tmp/webui.log"
detailsLabel={_("Error details")}
detailsContent={exceptionInfo(exception, idPrefix)}
buttons={[quitButton(isBootIso)]}
/>

);
};

const addUserIssueDataToReportURL = (url) => {
const newUrl = new URL(url);
newUrl.searchParams.append(
"comment",
_("Please attach the log file /tmp/webui.log to the issue.")
);
return newUrl.href;
};

const cancelButton = (onClose) => {
return (
<Button variant="link" onClick={() => onClose()} id="user-issue-dialog-cancel-btn" key="cancel">
{_("Cancel")}
</Button>
);
};

export const UserIssue = ({ reportLinkURL, setIsReportIssueOpen }) => {
return (
<BZReportModal
description={_("The following log will be sent to the issue tracking system where you may provide additional details.")}
reportLinkURL={addUserIssueDataToReportURL(reportLinkURL)}
idPrefix="user-issue"
title={_("Report issue")}
titleIconVariant={null}
logFile="/tmp/webui.log"
buttons={[cancelButton(() => setIsReportIssueOpen(false))]}
/>
);
};

export const errorHandlerWithContext = (contextData, handler) => {
return (exception) => {
exception.contextData = contextData;
Expand Down
5 changes: 3 additions & 2 deletions ui/webui/src/components/Error.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#critical-error-review-attached-log {
#critical-error-bz-report-modal-review-log,
#user-issue-bz-report-modal-review-log {
font-family: var(--pf-global--FontFamily--monospace);
font-size: var(--pf-global--FontSize--sm);
}

#critical-error-review-details {
#critical-error-bz-report-modal-details {
font-family: var(--pf-global--FontFamily--monospace);
}
18 changes: 16 additions & 2 deletions ui/webui/src/components/HeaderKebab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { ExternalLinkAltIcon } from "@patternfly/react-icons";

import { read_os_release as readOsRelease } from "os-release.js";
import { getAnacondaVersion } from "../helpers/product.js";
import { UserIssue } from "./Error.jsx";

import "./HeaderKebab.scss";

Expand Down Expand Up @@ -107,9 +108,10 @@ const AnacondaAboutModal = ({ isModalOpen, setIsAboutModalOpen }) => {
);
};

export const HeaderKebab = () => {
export const HeaderKebab = ({ reportLinkURL }) => {
const [isOpen, setIsOpen] = useState(false);
const [isAboutModalOpen, setIsAboutModalOpen] = useState(false);
const [isReportIssueOpen, setIsReportIssueOpen] = useState(false);

const onToggle = isOpen => {
setIsOpen(isOpen);
Expand All @@ -127,10 +129,17 @@ export const HeaderKebab = () => {
setIsAboutModalOpen(true);
};

const handleReportIssue = () => {
setIsReportIssueOpen(true);
};

const dropdownItems = [
<DropdownItem id="about-modal-dropdown-item" key="separated link" onClick={handleAboutModal}>
<DropdownItem id="about-modal-dropdown-item-about" key="about" onClick={handleAboutModal}>
{_("About")}
</DropdownItem>,
<DropdownItem id="about-modal-dropdown-item-report" key="report issue" onClick={handleReportIssue}>
{_("Report Issue")}
</DropdownItem>,
];
return (
<>
Expand All @@ -152,6 +161,11 @@ export const HeaderKebab = () => {
isModalOpen={isAboutModalOpen}
setIsAboutModalOpen={setIsAboutModalOpen}
/>}
{isReportIssueOpen &&
<UserIssue
reportLinkURL={reportLinkURL}
setIsReportIssueOpen={setIsReportIssueOpen}
/>}
</>
);
};
2 changes: 1 addition & 1 deletion ui/webui/src/components/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export const Application = () => {
<Page
data-debug={conf.Anaconda.debug}
additionalGroupedContent={
<AnacondaHeader beta={beta} title={title} />
<AnacondaHeader beta={beta} title={title} reportLinkURL={bzReportURL} />
}
groupProps={{
sticky: "top"
Expand Down
69 changes: 46 additions & 23 deletions ui/webui/test/check-basic
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class TestBasic(anacondalib.VirtInstallMachineCase):
b.click("#toggle-kebab")

# Click on the "About" item from the dropdown
b.click("#about-modal-dropdown-item")
b.click("#about-modal-dropdown-item-about")

# Expect PRETTY_NAME to be shown in modal title
b.wait_in_text("#about-modal-title", pretty_name)
Expand All @@ -109,6 +109,48 @@ class TestBasic(anacondalib.VirtInstallMachineCase):
b.click(".pf-c-button[aria-label='Close Dialog']")
b.wait_not_present("#about-modal")

def _testLogReview(self, b, m, idPrefix):
# Test review of logs
self.addCleanup(m.execute, "rm -f /tmp/webui.log")
logfile = "/tmp/webui.log"

self.assertFalse(self.file_exists(logfile))

# Click report issue: save reviewed log and open BZ URL
b.click(f"#{idPrefix}-bz-report-modal .pf-c-button.pf-m-primary")

with b.wait_timeout(20):
b.wait(lambda: self.file_exists(logfile))

# Reviewed log contains more than 100 lines
wait(lambda: int(m.execute(f"wc -l {logfile} | cut -d' ' -f1").strip()) > 100)

# Test editing of the log
user_text = "STRING_ADDED_BY_USER_TO_LOG"
b.set_input_text(f"#{idPrefix}-bz-report-modal-review-log", user_text)
b.click(f"#{idPrefix}-bz-report-modal .pf-c-button.pf-m-primary")
wait(lambda: (m.execute(f"cat {logfile}")) == user_text)

def testReportIssue(self):
b = self.browser
m = self.machine
i = Installer(b, m)

i.open()

b.wait_not_present("#user-issue-bz-report-modal")

b.click("#toggle-kebab")

b.click("#about-modal-dropdown-item-report")

b.wait_visible("#user-issue-bz-report-modal")

self._testLogReview(b, m, "user-issue")

b.click("#user-issue-dialog-cancel-btn")
b.wait_not_present("#user-issue-bz-report-modal")

def testErrorHandling(self):
b = self.browser
m = self.machine
Expand All @@ -131,32 +173,13 @@ class TestBasic(anacondalib.VirtInstallMachineCase):
i.open()
i.next()

b.wait_not_present("#critical-error-modal")
b.wait_not_present("#critical-error-bz-report-modal")

s.rescan_disks()

b.wait_visible("#critical-error-modal")

# Test review of logs
self.addCleanup(m.execute, "rm -f /tmp/webui.log")
logfile = "/tmp/webui.log"

self.assertFalse(self.file_exists(logfile))

# Click report issue: save reviewed log and open BZ URL
b.click("#critical-error-modal .pf-c-button.pf-m-primary")

with b.wait_timeout(20):
b.wait(lambda: self.file_exists(logfile))
b.wait_visible("#critical-error-bz-report-modal")

# Reviewed log contains more than 100 lines
wait(lambda: int(m.execute(f"wc -l {logfile} | cut -d' ' -f1").strip()) > 100)

# Test editing of the log
user_text = "STRING_ADDED_BY_USER_TO_LOG"
b.set_input_text("#critical-error-review-attached-log", user_text)
b.click("#critical-error-modal .pf-c-button.pf-m-primary")
wait(lambda: (m.execute(f"cat {logfile}")) == user_text)
self._testLogReview(b, m, "critical-error")


if __name__ == '__main__':
Expand Down

0 comments on commit 4c54b49

Please sign in to comment.