exclude opted out bundles from package list (#879)
* exclude opted out bundles from package list * count inferred bundle installs as opted in * let opted-in bundles own shared rows
This commit is contained in:
parent
e74eb60ba2
commit
7402fb6e78
7 changed files with 131 additions and 15 deletions
|
|
@ -126,6 +126,7 @@ fn entry_from_item(
|
|||
hash: None,
|
||||
manifest_default: false,
|
||||
hidden: false,
|
||||
opted_in: true,
|
||||
advanced: false,
|
||||
update_available: false,
|
||||
// Offered, not installed, so there is no recency badge to show
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ pub struct PackageEntry {
|
|||
pub manifest_default: bool,
|
||||
/// Private bundle dependency only set for bundle rows
|
||||
pub hidden: bool,
|
||||
/// False for uninstalled rows of bundles the cluster never took keeps them out of the All tab
|
||||
pub opted_in: bool,
|
||||
pub advanced: bool,
|
||||
/// Only set for browser-installed content bundle packages use the bundle update flow
|
||||
pub update_available: bool,
|
||||
|
|
|
|||
|
|
@ -560,6 +560,7 @@ mod tests {
|
|||
.collect(),
|
||||
archive: archive("performance", true, files),
|
||||
has_updates: false,
|
||||
opted_in: true,
|
||||
}]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -144,10 +144,13 @@ pub fn bundle_packages(
|
|||
}
|
||||
}
|
||||
|
||||
let mut ordered: Vec<&BundleWithUpdateStatus> = bundles.iter().collect();
|
||||
ordered.sort_by_key(|b| !b.opted_in);
|
||||
|
||||
let mut rows = Vec::new();
|
||||
let mut seen: HashSet<String> = HashSet::new();
|
||||
|
||||
for bundle in bundles {
|
||||
for bundle in ordered {
|
||||
let bundle_name = &bundle.archive.manifest.name;
|
||||
let category = bundle.archive.manifest.category.clone();
|
||||
for (file, status) in &bundle.files {
|
||||
|
|
@ -199,6 +202,7 @@ pub fn bundle_packages(
|
|||
vec![category.clone()]
|
||||
};
|
||||
|
||||
let opted_in = installed_info.is_some() || bundle.opted_in;
|
||||
let mut row = make_row(
|
||||
pid,
|
||||
Some(bundle_name.clone()),
|
||||
|
|
@ -214,6 +218,7 @@ pub fn bundle_packages(
|
|||
false,
|
||||
// Flagged rather than dropped `HiddenFilter` filters on the row and the seen id stops the loose-content pass resurrecting it as a local file
|
||||
file.hidden,
|
||||
opted_in,
|
||||
);
|
||||
row.advanced = advanced;
|
||||
rows.push(row);
|
||||
|
|
@ -246,6 +251,7 @@ pub fn bundle_packages(
|
|||
.unwrap_or_else(|| info.file_name.clone()),
|
||||
outdated,
|
||||
false,
|
||||
true,
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -267,6 +273,7 @@ fn make_row(
|
|||
fallback_name: String,
|
||||
update_available: bool,
|
||||
hidden: bool,
|
||||
opted_in: bool,
|
||||
) -> PackageEntry {
|
||||
let name = m
|
||||
.map(|p| p.name.clone())
|
||||
|
|
@ -305,6 +312,7 @@ fn make_row(
|
|||
hash: installed_info.map(|i| i.hash.clone()),
|
||||
update_available,
|
||||
hidden,
|
||||
opted_in,
|
||||
advanced: false,
|
||||
seen_status: installed_info.map(|i| i.seen_status).unwrap_or_default(),
|
||||
}
|
||||
|
|
@ -330,7 +338,7 @@ impl Tab {
|
|||
|
||||
pub(super) fn matches(&self, p: &PackageEntry) -> bool {
|
||||
match self {
|
||||
Tab::All => true,
|
||||
Tab::All => p.opted_in,
|
||||
Tab::Category(c) => p.categories.iter().any(|pc| pc == c),
|
||||
Tab::Browser => (p.is_remote() || p.github_hosted) && !p.in_bundle(),
|
||||
Tab::Local => !p.is_remote() && !p.github_hosted,
|
||||
|
|
@ -687,3 +695,63 @@ impl Component for PackageManager {
|
|||
.maybe_child(delete_dialog)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::view::onboarding::test_support::{archive, file};
|
||||
use oneclient_core::{BundleFile, FileUpdateStatus};
|
||||
|
||||
fn bundle(category: &str, opted_in: bool, files: Vec<BundleFile>) -> BundleWithUpdateStatus {
|
||||
BundleWithUpdateStatus {
|
||||
files: files
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|f| (f, FileUpdateStatus::NotInstalled))
|
||||
.collect(),
|
||||
archive: archive(category, true, files),
|
||||
has_updates: false,
|
||||
opted_in,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_tab_skips_bundles_not_opted_into() {
|
||||
let bundles = [
|
||||
bundle(
|
||||
"Declined",
|
||||
false,
|
||||
vec![
|
||||
file("only-declined", true, false),
|
||||
file("shared", true, false),
|
||||
],
|
||||
),
|
||||
bundle(
|
||||
"Taken",
|
||||
true,
|
||||
vec![file("shared", true, false), file("only-taken", true, false)],
|
||||
),
|
||||
];
|
||||
let rows = bundle_packages(
|
||||
Vec::new(),
|
||||
&bundles,
|
||||
&HashMap::new(),
|
||||
&PackageMetaMap::new(),
|
||||
&HashSet::new(),
|
||||
ContentType::Mod,
|
||||
);
|
||||
let all: Vec<&str> = rows
|
||||
.iter()
|
||||
.filter(|p| Tab::All.matches(p))
|
||||
.map(|p| p.package_id.as_str())
|
||||
.collect();
|
||||
assert_eq!(all, ["shared", "only-taken"]);
|
||||
let shared = rows.iter().find(|p| p.package_id == "shared").unwrap();
|
||||
assert_eq!(shared.categories, ["Taken"]);
|
||||
let declined = Tab::Category("Declined".into());
|
||||
assert!(
|
||||
rows.iter()
|
||||
.any(|p| p.package_id == "only-declined" && declined.matches(p))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -253,4 +253,5 @@ pub struct BundleWithUpdateStatus {
|
|||
pub archive: BundleArchive,
|
||||
pub files: Vec<(BundleFile, FileUpdateStatus)>,
|
||||
pub has_updates: bool,
|
||||
pub opted_in: bool,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -843,6 +843,14 @@ pub async fn get_bundles_with_update_status(
|
|||
})
|
||||
.collect();
|
||||
|
||||
// Same liveness the updater uses so an untracked older install is not hidden from the list while it still takes on new files
|
||||
let live: Vec<_> = all_linked.iter().filter(|item| item.enabled).collect();
|
||||
let (live_managed_keys, _) = installed_bundle_keys(ctx, live).await?;
|
||||
let mut live_bundles = live_bundle_names(&bundle_packages, &overrides);
|
||||
live_bundles.extend(infer_subscribed_from_archives(
|
||||
&archives,
|
||||
&live_managed_keys,
|
||||
));
|
||||
let mut results = Vec::new();
|
||||
|
||||
for archive in archives {
|
||||
|
|
@ -874,6 +882,7 @@ pub async fn get_bundles_with_update_status(
|
|||
}
|
||||
|
||||
results.push(BundleWithUpdateStatus {
|
||||
opted_in: live_bundles.contains(&archive.manifest.name),
|
||||
archive,
|
||||
files,
|
||||
has_updates,
|
||||
|
|
@ -918,27 +927,32 @@ async fn addition_eligible_bundles(
|
|||
let live: Vec<_> = all_linked.iter().filter(|item| item.enabled).collect();
|
||||
let (live_managed_keys, _) = installed_bundle_keys(ctx, live).await?;
|
||||
|
||||
let mut eligible: HashSet<String> = bundle_packages
|
||||
.iter()
|
||||
.filter(|bp| bp.enabled != 0)
|
||||
.filter_map(|bp| bp.bundle_name.clone())
|
||||
.collect();
|
||||
|
||||
let mut eligible = live_bundle_names(bundle_packages, overrides);
|
||||
eligible.extend(infer_bundle_names_from_unique_installed_keys(
|
||||
candidate_keys_by_bundle,
|
||||
&live_managed_keys,
|
||||
));
|
||||
|
||||
eligible.extend(
|
||||
overrides
|
||||
.iter()
|
||||
.filter(|o| OverrideType::parse(&o.override_type) == Some(OverrideType::Enabled))
|
||||
.map(|o| o.bundle_name.clone()),
|
||||
);
|
||||
|
||||
Ok(eligible)
|
||||
}
|
||||
|
||||
fn live_bundle_names(
|
||||
bundle_packages: &[BundleTrackedArtifactRow],
|
||||
overrides: &[ClusterBundleOverrideRow],
|
||||
) -> HashSet<String> {
|
||||
bundle_packages
|
||||
.iter()
|
||||
.filter(|bp| bp.enabled != 0)
|
||||
.filter_map(|bp| bp.bundle_name.clone())
|
||||
.chain(
|
||||
overrides
|
||||
.iter()
|
||||
.filter(|o| OverrideType::parse(&o.override_type) == Some(OverrideType::Enabled))
|
||||
.map(|o| o.bundle_name.clone()),
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn installed_bundle_keys<'a>(
|
||||
ctx: &ContentCtx,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use oneclient_common::domain::{ContentType, GameLoader, ProviderId};
|
||||
use oneclient_content::bundles::{
|
||||
BundleFile, BundleFileKind, BundleFileType, BundleManifest, check_bundle_updates,
|
||||
get_bundles_with_update_status,
|
||||
};
|
||||
use oneclient_core::LauncherState;
|
||||
use oneclient_core::clusters::CreateClusterOptions;
|
||||
|
|
@ -487,3 +488,31 @@ async fn tracked_bundle_that_never_synced_is_not_removed() {
|
|||
check.removals_available
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn untracked_older_install_counts_as_opted_in() {
|
||||
let state = oneclient_core::dev::ephemeral_state().await.unwrap();
|
||||
oneclient_core::dev::seed_bundle_archive(
|
||||
&state,
|
||||
manifest(vec![managed_file(true), newly_shipped_file()]),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let cluster_id = cluster_with_tracked_mod(&state).await;
|
||||
bundle_dao::clear_bundle_tracking(&state.services.db, cluster_id, HASH)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let bundles = get_bundles_with_update_status(
|
||||
cluster_id,
|
||||
state.bundles.as_ref(),
|
||||
&state.services.content(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
bundles.iter().all(|b| b.opted_in),
|
||||
"a bundle the updater infers from its installed mods must not have its files hidden from the All tab"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue