Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a9eea7b
[service] Implement D-Bus method for providing logs
jreidinger Dec 16, 2022
08421a8
[service] Capture log name in stderr not in stdout
jreidinger Dec 20, 2022
32ec86f
[web] Add function for getting path to logs
jreidinger Jan 5, 2023
448000b
[web] Initial UI proposal for "Download logs" link
jreidinger Jan 6, 2023
de7c961
[web] Add the download attribute
jreidinger Jan 6, 2023
cafe310
[web] Add missing type to Blob
jreidinger Jan 6, 2023
c3bb0ac
[web][css] Use fixed block size for header and footer
dgdavid Jan 16, 2023
f0c7baf
[web][css] Make some CSS adjustments
dgdavid Jan 16, 2023
0661e10
[web] Add basic infrastructure for Hamburguer menu
dgdavid Jan 16, 2023
b6f5eeb
[web] Add a noop function for default callbacks
dgdavid Jan 16, 2023
0cbe097
[web] Promote ChangeProductButton to a core component
dgdavid Jan 16, 2023
2a65bcf
[web] Put link for changing product in the sidebar
dgdavid Jan 16, 2023
c813614
[web] Move About link to sidebar
dgdavid Jan 16, 2023
e4d9210
[web] Move host's IPs information to sidebar
dgdavid Jan 16, 2023
affbcca
[web][css] Change speed of sidebar transformation
dgdavid Jan 16, 2023
09339ef
[web] Do not use the portal for footer info by now
dgdavid Jan 16, 2023
2c78f83
[web] Stop using layout inside test-utils
dgdavid Jan 16, 2023
f62f7ed
[web] Add a function for easing layout mocking
dgdavid Jan 16, 2023
b0be28f
[web] Move LogsButton comonent to Sidebar
dgdavid Jan 16, 2023
cd81774
[web] Change LogsButton's position and style
dgdavid Jan 16, 2023
afff710
[web] Use icons for all sidebar actions
dgdavid Jan 16, 2023
7d2835c
[web] use icon as prop in ChangeProductButton
dgdavid Jan 16, 2023
186f595
[web] Fix missing use of mockComponent function
dgdavid Jan 16, 2023
71b7999
[web] Do not trigger navigation when opening the sidebar
dgdavid Jan 17, 2023
98c1bae
[web] Change the Download logs button behavior
dgdavid Jan 17, 2023
eddc1f2
Merge branch 'master' into provide_logs
dgdavid Jan 17, 2023
90a8603
[web] Increase a bit the footer block size
dgdavid Jan 17, 2023
6dd8d06
[service][web] Update changes files
dgdavid Jan 18, 2023
54a3c60
[web] Update Layout documentation
dgdavid Jan 18, 2023
fe5c907
[web] Fixes from code review
dgdavid Jan 18, 2023
b0037c8
[service] Rename some methods and add unit test
dgdavid Jan 18, 2023
f83ebeb
[web] Merge manager methods for collecting logs
dgdavid Jan 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions service/lib/dinstaller/dbus/manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def initialize(backend, logger)
dbus_method(:Probe, "") { config_phase }
dbus_method(:Commit, "") { install_phase }
dbus_method(:CanInstall, "out result:b") { can_install? }
dbus_method(:CollectLogs, "out tarball_filesystem_path:s, in user:s") { |u| collect_logs(u) }
dbus_reader :installation_phases, "aa{sv}"
dbus_reader :current_installation_phase, "u"
dbus_reader :busy_services, "as"
Expand Down Expand Up @@ -88,6 +89,11 @@ def can_install?
backend.valid?
end

# Collects the YaST logs
def collect_logs(user)
backend.collect_logs(user)
end

# Description of all possible installation phase values
#
# @return [Array<Hash>]
Expand Down
12 changes: 12 additions & 0 deletions service/lib/dinstaller/manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@ def valid?
[storage, users, software].all?(&:valid?)
end

# Collects the logs and stores them into an archive
#
# @param user [String] local username who will own archive
# @return [String] path to created archive
def collect_logs(user)
output = Yast::Execute.locally!("save_y2logs", stderr: :capture)
path = output[/^.* (\/tmp\/y2log-\S*)/, 1]
Yast::Execute.locally!("chown", "#{user}:", path)

path
end

private

attr_reader :config
Expand Down
6 changes: 6 additions & 0 deletions service/package/rubygem-d-installer.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Wed Jan 18 08:03:40 UTC 2023 - Josef Reidinger <jreidinger@suse.com>

- Save logs and provide the path to the file
(gh#yast/d-installer#379)

-------------------------------------------------------------------
Tue Jan 17 10:06:23 UT0 2023 - Josef Reidinger <jreidinger@suse.com>

Expand Down
13 changes: 13 additions & 0 deletions service/test/dinstaller/manager_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,17 @@
end
end
end

describe "#collect_logs" do
it "collects the logs and returns the path to the archive" do
expect(Yast::Execute).to receive(:locally!)
.with("save_y2logs", stderr: :capture)
.and_return("Saving YaST logs to /tmp/y2log-hWBn95.tar.xz")
expect(Yast::Execute).to receive(:locally!)
.with("chown", "ytm:", /y2log-hWBn95/)

path = subject.collect_logs("ytm")
expect(path).to eq("/tmp/y2log-hWBn95.tar.xz")
end
end
end
5 changes: 5 additions & 0 deletions web/package/cockpit-d-installer.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
-------------------------------------------------------------------
Wed Jan 18 08:06:05 UTC 2023 - Josef Reidinger <jreidinger@suse.com>

- Allow user downloading logs (gh#yast/d-installer#379)

-------------------------------------------------------------------
Thu Jan 12 16:23:54 UTC 2023 - Josef Reidinger <jreidinger@suse.com>

Expand Down
9 changes: 2 additions & 7 deletions web/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ import { useInstallerClient } from "@context/installer";
import { STARTUP, INSTALL } from "@client/phase";
import { BUSY } from "@client/status";

import { Layout, Title, AdditionalInfo, LoadingEnvironment, DBusError } from "@components/layout";
import { About, InstallationProgress, InstallationFinished } from "@components/core";
import { TargetIpsPopup } from "@components/network";
import { Layout, Title, LoadingEnvironment, DBusError } from "@components/layout";
import { InstallationProgress, InstallationFinished } from "@components/core";

function App() {
const client = useInstallerClient();
Expand Down Expand Up @@ -79,10 +78,6 @@ function App() {
<Layout>
<Title>D-Installer</Title>
<Content />
<AdditionalInfo>
<About />
<TargetIpsPopup />
</AdditionalInfo>
</Layout>
);
}
Expand Down
10 changes: 3 additions & 7 deletions web/src/App.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

import React from "react";
import { act, screen } from "@testing-library/react";
import { installerRender, mockComponent } from "@/test-utils";
import { installerRender, mockComponent, mockLayout } from "@/test-utils";
import App from "./App";
import { createClient } from "@client";
import { STARTUP, CONFIG, INSTALL } from "@client/phase";
Expand All @@ -33,14 +33,15 @@ jest.mock('react-router-dom', () => ({
Outlet: mockComponent("Content"),
}));

jest.mock("@components/layout/Layout", () => mockLayout());

// Mock some components,
// See https://www.chakshunyu.com/blog/how-to-mock-a-react-component-in-jest/#default-export
jest.mock("@components/layout/DBusError", () => mockComponent("D-BusError Mock"));
jest.mock("@components/layout/LoadingEnvironment", () => mockComponent("LoadingEnvironment Mock"));
jest.mock("@components/questions/Questions", () => mockComponent("Questions Mock"));
jest.mock("@components/core/InstallationProgress", () => mockComponent("InstallationProgress Mock"));
jest.mock("@components/core/InstallationFinished", () => mockComponent("InstallationFinished Mock"));
jest.mock("@components/network/TargetIpsPopup", () => mockComponent("Target IPs Mock"));

const callbacks = {};
const getStatusFn = jest.fn();
Expand Down Expand Up @@ -177,10 +178,5 @@ describe("App", () => {
installerRender(<App />);
await screen.findByText("Content");
});

it("renders IP address and hostname", async () => {
installerRender(<App />);
await screen.findByText("Target IPs Mock");
});
});
});
34 changes: 34 additions & 0 deletions web/src/assets/styles/blocks.scss
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,37 @@ section > .content {
.selection-list [data-state="unstyled"] {
border: 0;
}

.sidebar {
--color-background-primary: var(--color-primary);
--wrapper-background: var(--color-gray-light);

position: absolute;
padding: 0;
right: 0;
z-index: 1;
inline-size: 70%;
box-shadow: 0px 0px 20px 10px var(--color-primary-darkest);
}

.sidebar footer {
border-top: 1px solid var(--color-gray);
}

// Remove not wanted PatternFly padding left on a loading link
.sidebar button.pf-m-progress {
--pf-c-button--m-progress--PaddingLeft: var(--pf-global--spacer--md);
}
.sidebar button.pf-m-progress + div {
padding-inline-start: calc(var(--pf-global--spacer--md));
}

.sidebar[data-state="hidden"] {
transition: all 0.04s ease-in-out;
inline-size: 0;
box-shadow: none;
}

.sidebar[data-state="visible"] {
transition: all 0.2s ease-in-out;
}
7 changes: 7 additions & 0 deletions web/src/assets/styles/composition.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
margin-block-start: var(--stack-gutter);
}

.flex-stack {
display: flex;
flex-direction: column;
align-items: start;
@extend .stack;
}

.split {
display: flex;
align-items: center;
Expand Down
12 changes: 8 additions & 4 deletions web/src/assets/styles/layout.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.wrapper {
display: grid;
grid-template-rows: auto 1fr auto;
grid-template-rows: var(--header-block-size) 1fr var(--footer-block-size);
grid-template-areas:
'header'
'content'
Expand All @@ -11,7 +11,7 @@
block-size: 100dvb;
max-inline-size: 1024px;
margin-inline: auto;
background: white;
background: var(--wrapper-background);

svg {
fill: currentColor;
Expand All @@ -20,15 +20,15 @@
}

.wrapper > * {
padding: var(--spacer-normal);
padding: var(--wrapper-padding);
}

.wrapper > header {
--color-button-plain-link: white;
--color-button-plain-link-hover: #fcfcfc;

grid-area: header;
background: var(--color-primary-darkest);
background: var(--color-background-primary);
color: var(--color-text-secondary);
}

Expand All @@ -46,3 +46,7 @@
.wrapper > footer > img {
max-inline-size: 150px;
}

[data-variant="flip-X"] {
transform: scaleX(-1);
}
7 changes: 7 additions & 0 deletions web/src/assets/styles/variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@
--stack-gutter: var(--spacer-normal);
--split-gutter: var(--spacer-small);

--wrapper-padding: var(--spacer-normal);
--wrapper-background: white;

--color-primary: #30BA78;
--color-primary-darkest: #0C322C;
--color-gray-light: #FCFCFC;
--color-gray: #F2F2F2;
--color-gray-dark: #EFEFEF; // Fog
--color-gray-darker: #999999;
Expand Down Expand Up @@ -55,4 +59,7 @@
--gradient-border-end-color: transparent;

--icon-size-m: 32px;

--header-block-size: 6vh;
--footer-block-size: 8vh;
}
12 changes: 12 additions & 0 deletions web/src/client/manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ class ManagerBaseClient {
return proxy.CanInstall();
}

/**
* Returns the binary content of the YaST logs file
*
* @return {Promise<Uint8Array>}
*/
async fetchLogs() {
const proxy = await this.client.proxy(MANAGER_IFACE);
const path = proxy.CollectLogs("root");
const file = cockpit.file(path, { binary: true });
return file.read();
}

/**
* Return the installer status
*
Expand Down
15 changes: 15 additions & 0 deletions web/src/client/manager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const managerProxy = {
Commit: jest.fn(),
Probe: jest.fn(),
CanInstall: jest.fn(),
CollectLogs: jest.fn(),
CurrentInstallationPhase: 0
};

Expand Down Expand Up @@ -148,3 +149,17 @@ describe("#canInstall", () => {
});
});
});

describe("#fetchLogs", () => {
beforeEach(() => {
managerProxy.CollectLogs = jest.fn(() => "/tmp/y2log-hWBn95.tar.xz");
cockpit.file = jest.fn(() => ({ read: () => "fake-binary-data" }));
});

it("returns the logs file binary content", async () => {
const client = new ManagerClient();
const logsContent = await client.fetchLogs();
expect(logsContent).toEqual("fake-binary-data");
expect(cockpit.file).toHaveBeenCalledWith("/tmp/y2log-hWBn95.tar.xz", { binary: true });
});
});
16 changes: 13 additions & 3 deletions web/src/components/core/About.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,28 @@
*/

import React, { useState } from "react";
import { noop } from "@/utils";
import { Button, Text } from "@patternfly/react-core";
import { Icon } from "@components/layout";
import { Popup } from "@components/core";

export default function About() {
export default function About({ onClickCallback = noop }) {
const [isOpen, setIsOpen] = useState(false);

const open = () => setIsOpen(true);
const open = () => {
setIsOpen(true);
onClickCallback();
};

const close = () => setIsOpen(false);

return (
<>
<Button variant="link" onClick={open}>
<Button
variant="link"
icon={<Icon name="help" size="24" />}
onClick={open}
>
About
</Button>

Expand Down
14 changes: 12 additions & 2 deletions web/src/components/core/About.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
import React from "react";

import { screen, waitFor, within } from "@testing-library/react";
import { installerRender } from "@/test-utils";
import { plainRender } from "@/test-utils";

import About from "./About";

describe("About", () => {
it("allows user to read 'About D-Installer'", async () => {
const { user } = installerRender(<About />);
const { user } = plainRender(<About />);

const button = screen.getByRole("button", { name: /About/i });
await user.click(button);
Expand All @@ -44,4 +44,14 @@ describe("About", () => {
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});
});

it("triggers given onClickCallback function when opening the dialog", async () => {
const onClickCallback = jest.fn();

const { user } = plainRender(<About onClickCallback={onClickCallback} />);
const button = screen.getByRole("button", { name: /About/i });

await user.click(button);
expect(onClickCallback).toHaveBeenCalled();
});
});
Loading