Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt the software UI to the new HTTP/JSON API #1112

Merged
merged 27 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
40cbc06
Move the selected patterns to the proposal
imobachgs Mar 20, 2024
dd0c917
Rename Pattern 'id' to 'name'
imobachgs Mar 20, 2024
64d1625
Serialize SelectedBy as a number
imobachgs Mar 20, 2024
248d040
Adapt the SoftwareClient to the HTTP/JSON API
imobachgs Mar 20, 2024
bcbfb4e
Initial adaptation of the software section to the HTTP/JSON API
imobachgs Mar 20, 2024
e8adad1
Move the PatternsSelector to a pop-up
imobachgs Mar 21, 2024
61b0c0f
Add a onOptionClick option to the Selector
imobachgs Mar 21, 2024
eec9124
Revamp the software page
imobachgs Mar 21, 2024
48e5416
Improve the usability of the software page
imobachgs Mar 21, 2024
089fa8a
Introduce a SelectedBy "enum"
imobachgs Mar 21, 2024
a13e6a8
Fix software clients type checking and a few tests
imobachgs Mar 21, 2024
dee2bd5
Type checking fixes
imobachgs Mar 21, 2024
1eab869
Add description to the list of selected patterns
imobachgs Mar 21, 2024
c37c01a
Fix patterns removal when they are in auto mode
imobachgs Mar 22, 2024
426d78b
Adapt the PatternSelector to selectPatterns changes
imobachgs Mar 22, 2024
fcabdcc
Fix a typo in the documentation
imobachgs Mar 22, 2024
e5bdecc
Fix PatternSelector type checking
imobachgs Mar 22, 2024
801cb6e
Move the software selection to the SoftwarePage
imobachgs Mar 22, 2024
53c59ac
Clean-up the PatternSelector component
imobachgs Mar 22, 2024
d7cf9ed
Adapt the PatternSelector tests
imobachgs Mar 22, 2024
738ed3e
Copyright updates
imobachgs Mar 22, 2024
6667a30
Merge branch 'architecture_2024' into adapt-software-ui
imobachgs Mar 22, 2024
97eabe8
Extend SoftwarePage and PatternSelector tests
imobachgs Mar 22, 2024
c209089
Fix indentation
imobachgs Mar 22, 2024
5d84a2f
Allow visiting Software section even if it is not ready
imobachgs Mar 23, 2024
15b2092
Update documentation style in software.js
imobachgs Mar 25, 2024
5b937df
Documentation fixes
imobachgs Mar 25, 2024
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
858 changes: 409 additions & 449 deletions rust/Cargo.lock

Large diffs are not rendered by default.

26 changes: 18 additions & 8 deletions rust/agama-lib/src/software/client.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use super::proxies::Software1Proxy;
use crate::error::ServiceError;
use serde::Serialize;
use serde_repr::Serialize_repr;
use std::collections::HashMap;
use zbus::Connection;

/// Represents a software product
#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct Pattern {
/// Pattern ID (eg., "aaa_base", "gnome")
pub id: String,
/// Pattern name (eg., "aaa_base", "gnome")
pub name: String,
/// Pattern category (e.g., "Production")
pub category: String,
/// Pattern icon path locally on system
Expand All @@ -22,7 +23,8 @@ pub struct Pattern {
}

/// Represents the reason why a pattern is selected.
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
#[derive(Clone, Copy, Debug, PartialEq, Serialize_repr)]
#[repr(u8)]
pub enum SelectedBy {
/// The pattern was selected by the user.
User = 0,
Expand Down Expand Up @@ -69,8 +71,8 @@ impl<'a> SoftwareClient<'a> {
.await?
.into_iter()
.map(
|(id, (category, description, icon, summary, order))| Pattern {
id,
|(name, (category, description, icon, summary, order))| Pattern {
name,
category,
icon,
description,
Expand Down Expand Up @@ -118,11 +120,19 @@ impl<'a> SoftwareClient<'a> {
}

/// Selects patterns by user
pub async fn select_patterns(&self, patterns: &[String]) -> Result<(), ServiceError> {
let patterns: Vec<&str> = patterns.iter().map(AsRef::as_ref).collect();
pub async fn select_patterns(
&self,
patterns: HashMap<String, bool>,
) -> Result<(), ServiceError> {
let (add, remove): (Vec<_>, Vec<_>) =
patterns.into_iter().partition(|(_, install)| *install);

let add: Vec<_> = add.iter().map(|(name, _)| name.as_ref()).collect();
let remove: Vec<_> = remove.iter().map(|(name, _)| name.as_ref()).collect();

let wrong_patterns = self
.software_proxy
.set_user_patterns(patterns.as_slice())
.set_user_patterns(add.as_slice(), remove.as_slice())
.await?;
if !wrong_patterns.is_empty() {
Err(ServiceError::UnknownPatterns(wrong_patterns))
Expand Down
2 changes: 1 addition & 1 deletion rust/agama-lib/src/software/proxies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ trait Software1 {
fn remove_pattern(&self, id: &str) -> zbus::Result<bool>;

/// SetUserPatterns method
fn set_user_patterns(&self, ids: &[&str]) -> zbus::Result<Vec<String>>;
fn set_user_patterns(&self, add: &[&str], remove: &[&str]) -> zbus::Result<Vec<String>>;

/// UsedDiskSpace method
fn used_disk_space(&self) -> zbus::Result<String>;
Expand Down
11 changes: 8 additions & 3 deletions rust/agama-lib/src/software/store.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Implements the store for the storage settings.

use std::collections::HashMap;

use super::{SoftwareClient, SoftwareSettings};
use crate::error::ServiceError;
use zbus::Connection;
Expand All @@ -22,9 +24,12 @@ impl<'a> SoftwareStore<'a> {
}

pub async fn store(&self, settings: &SoftwareSettings) -> Result<(), ServiceError> {
self.software_client
.select_patterns(&settings.patterns)
.await?;
let patterns: HashMap<String, bool> = settings
.patterns
.iter()
.map(|name| (name.to_owned(), true))
.collect();
self.software_client.select_patterns(patterns).await?;

Ok(())
}
Expand Down
56 changes: 21 additions & 35 deletions rust/agama-server/src/software/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@ struct SoftwareState<'a> {
software: SoftwareClient<'a>,
}

/// Software service configuration (product, patterns, etc.).
#[derive(Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct SoftwareConfig {
patterns: Option<Vec<String>>,
/// A map where the keys are the pattern names and the values whether to install them or not.
patterns: Option<HashMap<String, bool>>,
/// Name of the product to install.
product: Option<String>,
}

Expand Down Expand Up @@ -92,7 +95,7 @@ async fn patterns_changed_stream(
}
None
})
.filter_map(|e| e.map(Event::PatternsChanged));
.filter_map(|e| e.map(|patterns| Event::SoftwareProposalChanged { patterns }));
Ok(stream)
}

Expand Down Expand Up @@ -150,43 +153,16 @@ async fn products(State(state): State<SoftwareState<'_>>) -> Result<Json<Vec<Pro
Ok(Json(products))
}

/// Represents a pattern.
///
/// It augments the information coming from the D-Bus client.
#[derive(Serialize, utoipa::ToSchema)]
pub struct PatternEntry {
#[serde(flatten)]
pattern: Pattern,
selected_by: SelectedBy,
}

/// Returns the list of software patterns.
///
/// * `state`: service state.
#[utoipa::path(get, path = "/software/patterns", responses(
(status = 200, description = "List of known software patterns", body = Vec<PatternEntry>),
(status = 200, description = "List of known software patterns", body = Vec<Pattern>),
(status = 400, description = "The D-Bus service could not perform the action")
))]
async fn patterns(
State(state): State<SoftwareState<'_>>,
) -> Result<Json<Vec<PatternEntry>>, Error> {
async fn patterns(State(state): State<SoftwareState<'_>>) -> Result<Json<Vec<Pattern>>, Error> {
let patterns = state.software.patterns(true).await?;
let selected = state.software.selected_patterns().await?;
let items = patterns
.into_iter()
.map(|pattern| {
let selected_by: SelectedBy = selected
.get(&pattern.id)
.copied()
.unwrap_or(SelectedBy::None);
PatternEntry {
pattern,
selected_by,
}
})
.collect();

Ok(Json(items))
Ok(Json(patterns))
}

/// Sets the software configuration.
Expand All @@ -206,7 +182,7 @@ async fn set_config(
}

if let Some(patterns) = config.patterns {
state.software.select_patterns(&patterns).await?;
state.software.select_patterns(patterns).await?;
}

Ok(())
Expand All @@ -226,7 +202,13 @@ async fn get_config(State(state): State<SoftwareState<'_>>) -> Result<Json<Softw
} else {
Some(product)
};
let patterns = state.software.user_selected_patterns().await?;
let patterns = state
.software
.user_selected_patterns()
.await?
.into_iter()
.map(|p| (p, true))
.collect();
let config = SoftwareConfig {
patterns: Some(patterns),
product,
Expand All @@ -240,6 +222,9 @@ pub struct SoftwareProposal {
/// Space required for installation. It is returned as a formatted string which includes
/// a number and a unit (e.g., "GiB").
size: String,
/// Patterns selection. It is respresented as a hash map where the key is the pattern's name
/// and the value why the pattern is selected.
patterns: HashMap<String, SelectedBy>,
}

/// Returns the proposal information.
Expand All @@ -251,7 +236,8 @@ pub struct SoftwareProposal {
))]
async fn proposal(State(state): State<SoftwareState<'_>>) -> Result<Json<SoftwareProposal>, Error> {
let size = state.software.used_disk_space().await?;
let proposal = SoftwareProposal { size };
let patterns = state.software.selected_patterns().await?;
let proposal = SoftwareProposal { size, patterns };
Ok(Json(proposal))
}

Expand Down
1 change: 0 additions & 1 deletion rust/agama-server/src/web/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ use utoipa::OpenApi;
schemas(crate::network::model::Device),
schemas(crate::network::model::Connection),
schemas(agama_lib::network::types::DeviceType),
schemas(crate::software::web::PatternEntry),
schemas(crate::software::web::SoftwareConfig),
schemas(crate::software::web::SoftwareProposal),
schemas(crate::manager::web::InstallerStatus),
Expand Down
6 changes: 5 additions & 1 deletion rust/agama-server/src/web/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ pub enum Event {
ProductChanged {
id: String,
},
PatternsChanged(HashMap<String, SelectedBy>),
// TODO: it should include the full software proposal or, at least,
// all the relevant changes.
SoftwareProposalChanged {
patterns: HashMap<String, SelectedBy>,
},
QuestionsChanged,
InstallationPhaseChanged {
phase: InstallationPhase,
Expand Down
4 changes: 3 additions & 1 deletion service/lib/agama/dbus/software/manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ def issues

dbus_method(:AddPattern, "in id:s, out result:b") { |p| backend.add_pattern(p) }
dbus_method(:RemovePattern, "in id:s, out result:b") { |p| backend.remove_pattern(p) }
dbus_method(:SetUserPatterns, "in ids:as, out wrong:as") { |ids| [backend.assign_patterns(ids)] }
dbus_method(:SetUserPatterns, "in add:as, in remove:as, out wrong:as") do |add, remove|
[backend.assign_patterns(add, remove)]
end

dbus_method :ProvisionsSelected, "in Provisions:as, out Result:ab" do |provisions|
[provisions.map { |p| backend.provision_selected?(p) }]
Expand Down
22 changes: 17 additions & 5 deletions service/lib/agama/software/manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -249,16 +249,28 @@ def remove_pattern(id)
true
end

def assign_patterns(ids)
wrong_patterns = ids.reject { |p| pattern_exist?(p) }
def assign_patterns(add, remove)
wrong_patterns = [add, remove].flatten.reject { |p| pattern_exist?(p) }
return wrong_patterns unless wrong_patterns.empty?

user_patterns = Yast::PackagesProposal.GetResolvables(PROPOSAL_ID, :pattern)
user_patterns.each { |p| Yast::Pkg.ResolvableNeutral(p, :pattern, force = false) }
Yast::PackagesProposal.SetResolvables(PROPOSAL_ID, :pattern, ids)
ids.each { |p| Yast::Pkg.ResolvableInstall(p, :pattern) }
logger.info "Setting patterns to #{ids.inspect}"
logger.info "Adding patterns: #{add.join(", ")}. Removing patterns: #{remove.join(",")}."

Yast::PackagesProposal.SetResolvables(PROPOSAL_ID, :pattern, add)
add.each do |id|
res = Yast::Pkg.ResolvableInstall(id, :pattern)
logger.info "Adding pattern #{id}: #{res.inspect}"
end

remove.each do |id|
res = Yast::Pkg.ResolvableNeutral(id, :pattern, force = false)
logger.info "Removing pattern #{id}: #{res.inspect}"
Yast::PackagesProposal.RemoveResolvables(PROPOSAL_ID, :pattern, [id])
end

proposal.solve_dependencies

selected_patterns_changed

[]
Expand Down
4 changes: 2 additions & 2 deletions web/src/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const createClient = (url) => {
const manager = new ManagerClient(client);
// const monitor = new Monitor(address, MANAGER_SERVICE);
// const network = new NetworkClient(address);
// const software = new SoftwareClient(address);
const software = new SoftwareClient(client);
// const storage = new StorageClient(address);
// const users = new UsersClient(address);
// const questions = new QuestionsClient(address);
Expand Down Expand Up @@ -139,7 +139,7 @@ const createClient = (url) => {
manager,
// monitor,
// network,
// software,
software,
// storage,
// users,
// questions,
Expand Down
4 changes: 2 additions & 2 deletions web/src/client/mixins.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ const WithStatus = (superclass, status_path, service_name) =>
* Register a callback to run when the "CurrentInstallationPhase" changes
*
* @param {function} handler - callback function
* @return {function} function to disable the callback
* @return {import ("./http").RemoveFn} function to disable the callback
*/
onStatusChange(handler) {
return this.client.onEvent("ServiceStatusChanged", ({ status, service }) => {
Expand Down Expand Up @@ -207,7 +207,7 @@ const WithProgress = (superclass, progress_path, service_name) =>
* Register a callback to run when the progress changes
*
* @param {ProgressHandler} handler - callback function
* @return {import ("./dbus").RemoveFn} function to disable the callback
* @return {import ("./http").RemoveFn} function to disable the callback
*/
onProgressChange(handler) {
return this.client.onEvent("Progress", ({ service, ...progress }) => {
Expand Down
Loading
Loading