Skip to content

Commit 33bdd95

Browse files
committed
frontend: Order default features in tree structure
Order features that are enabled by default in flat tree structure. At the same time report correct number of features being enabled by default.
1 parent 902515f commit 33bdd95

File tree

6 files changed

+99
-35
lines changed

6 files changed

+99
-35
lines changed

src/db/types.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use serde::Serialize;
44
#[derive(Debug, Clone, PartialEq, Eq, Serialize, FromSql, ToSql)]
55
#[postgres(name = "feature")]
66
pub struct Feature {
7-
name: String,
8-
subfeatures: Vec<String>,
7+
pub(crate) name: String,
8+
pub(crate) subfeatures: Vec<String>,
99
}
1010

1111
impl Feature {

src/web/crate_details.rs

+32-3
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ impl CrateDetails {
103103
releases.license,
104104
releases.documentation_url,
105105
releases.default_target,
106-
releases.features,
107106
doc_coverage.total_items,
108107
doc_coverage.documented_items,
109108
doc_coverage.total_items_needing_examples,
@@ -149,7 +148,6 @@ impl CrateDetails {
149148
default_target: krate.get("default_target"),
150149
doc_targets: MetaData::parse_doc_targets(krate.get("doc_targets")),
151150
yanked: krate.get("yanked"),
152-
features: MetaData::parse_features(krate.get("features")),
153151
};
154152

155153
let documented_items: Option<i32> = krate.get("documented_items");
@@ -817,14 +815,45 @@ mod tests {
817815
});
818816
}
819817

818+
#[test]
819+
fn feature_flags_with_nested_default() {
820+
wrapper(|env| {
821+
let features = [
822+
("default".into(), vec!["feature1".into()]),
823+
("feature1".into(), vec!["feature2".into()]),
824+
("feature2".into(), Vec::new()),
825+
]
826+
.iter()
827+
.cloned()
828+
.collect::<HashMap<String, Vec<String>>>();
829+
env.fake_release()
830+
.name("library")
831+
.version("0.1.0")
832+
.features(features)
833+
.create()?;
834+
835+
let page = kuchiki::parse_html().one(
836+
env.frontend()
837+
.get("/crate/library/0.1.0/features")
838+
.send()?
839+
.text()?,
840+
);
841+
assert!(page.select_first(r#"p[data-id="empty-features"]"#).is_err());
842+
let def_len = page
843+
.select_first(r#"b[data-id="default-feature-len"]"#)
844+
.unwrap();
845+
assert_eq!(def_len.text_contents(), "3");
846+
Ok(())
847+
});
848+
}
849+
820850
#[test]
821851
fn feature_flags_report_null() {
822852
wrapper(|env| {
823853
let id = env
824854
.fake_release()
825855
.name("library")
826856
.version("0.1.0")
827-
.features(HashMap::new())
828857
.create()?;
829858

830859
env.db()

src/web/features.rs

+58
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::db::types::Feature;
12
use crate::{
23
db::Pool,
34
impl_webpage,
@@ -6,10 +7,13 @@ use crate::{
67
use iron::{IronResult, Request, Response};
78
use router::Router;
89
use serde::Serialize;
10+
use std::collections::{HashMap, VecDeque};
911

1012
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1113
struct FeaturesPage {
1214
metadata: MetaData,
15+
features: Option<Vec<Feature>>,
16+
default_len: usize,
1317
}
1418

1519
impl_webpage! {
@@ -22,9 +26,63 @@ pub fn build_features_handler(req: &mut Request) -> IronResult<Response> {
2226
let version = cexpect!(req, router.find("version"));
2327

2428
let mut conn = extension!(req, Pool).get()?;
29+
let rows = ctry!(
30+
req,
31+
conn.query(
32+
"SELECT releases.features FROM releases
33+
INNER JOIN crates ON crates.id = releases.crate_id
34+
WHERE crates.name = $1 AND releases.version = $2",
35+
&[&name, &version]
36+
)
37+
);
38+
39+
let row = cexpect!(req, rows.get(0));
40+
41+
let mut default_len = 0;
42+
let features = row
43+
.get::<'_, usize, Option<Vec<Feature>>>(0)
44+
.map(|raw| {
45+
raw.into_iter()
46+
.filter(|feature| !feature.is_private())
47+
.map(|feature| (feature.name.clone(), feature))
48+
.collect::<HashMap<String, Feature>>()
49+
})
50+
.map(|mut feature_map| {
51+
let mut features = get_tree_structure_from_default(&mut feature_map);
52+
let mut remaining = feature_map
53+
.into_iter()
54+
.map(|(_, feature)| feature)
55+
.collect::<Vec<Feature>>();
56+
remaining.sort_by_key(|feature| feature.subfeatures.len());
57+
58+
default_len = features.len();
59+
60+
features.extend(remaining.into_iter().rev());
61+
features
62+
});
2563

2664
FeaturesPage {
2765
metadata: cexpect!(req, MetaData::from_crate(&mut conn, &name, &version)),
66+
features,
67+
default_len,
2868
}
2969
.into_response(req)
3070
}
71+
72+
fn get_tree_structure_from_default(feature_map: &mut HashMap<String, Feature>) -> Vec<Feature> {
73+
let mut features = Vec::new();
74+
let mut queue: VecDeque<String> = VecDeque::new();
75+
76+
queue.push_back("default".into());
77+
while !queue.is_empty() {
78+
let name = queue.pop_front().unwrap();
79+
if let Some(feature) = feature_map.remove(&name) {
80+
feature
81+
.subfeatures
82+
.iter()
83+
.for_each(|sub| queue.push_back(sub.clone()));
84+
features.push(feature);
85+
}
86+
}
87+
features
88+
}

src/web/mod.rs

-15
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ mod sitemap;
9191
mod source;
9292
mod statics;
9393

94-
use crate::db::types::Feature;
9594
use crate::{impl_webpage, Context};
9695
use chrono::{DateTime, Utc};
9796
use error::Nope;
@@ -522,7 +521,6 @@ pub(crate) struct MetaData {
522521
pub(crate) default_target: String,
523522
pub(crate) doc_targets: Vec<String>,
524523
pub(crate) yanked: bool,
525-
pub(crate) features: Option<Vec<Feature>>,
526524
}
527525

528526
impl MetaData {
@@ -556,7 +554,6 @@ impl MetaData {
556554
default_target: row.get(5),
557555
doc_targets: MetaData::parse_doc_targets(row.get(6)),
558556
yanked: row.get(7),
559-
features: MetaData::parse_features(row.get(8)),
560557
})
561558
}
562559

@@ -571,14 +568,6 @@ impl MetaData {
571568
})
572569
.unwrap_or_else(Vec::new)
573570
}
574-
575-
pub(crate) fn parse_features(features: Option<Vec<Feature>>) -> Option<Vec<Feature>> {
576-
features.map(|vec| {
577-
vec.into_iter()
578-
.filter(|feature| !feature.is_private())
579-
.collect()
580-
})
581-
}
582571
}
583572

584573
#[derive(Debug, Clone, PartialEq, Serialize)]
@@ -857,7 +846,6 @@ mod test {
857846
"arm64-unknown-linux-gnu".to_string(),
858847
],
859848
yanked: false,
860-
features: None,
861849
};
862850

863851
let correct_json = json!({
@@ -872,7 +860,6 @@ mod test {
872860
"arm64-unknown-linux-gnu",
873861
],
874862
"yanked": false,
875-
"features": null
876863
});
877864

878865
assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap());
@@ -890,7 +877,6 @@ mod test {
890877
"arm64-unknown-linux-gnu",
891878
],
892879
"yanked": false,
893-
"features": null,
894880
});
895881

896882
assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap());
@@ -908,7 +894,6 @@ mod test {
908894
"arm64-unknown-linux-gnu",
909895
],
910896
"yanked": false,
911-
"features": null,
912897
});
913898

914899
assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap());

src/web/source.rs

-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ impl FileList {
5959
releases.default_target,
6060
releases.doc_targets,
6161
releases.yanked,
62-
releases.features
6362
FROM releases
6463
LEFT OUTER JOIN crates ON crates.id = releases.crate_id
6564
WHERE crates.name = $1 AND releases.version = $2",
@@ -138,7 +137,6 @@ impl FileList {
138137
default_target: rows[0].get(6),
139138
doc_targets: MetaData::parse_doc_targets(rows[0].get(7)),
140139
yanked: rows[0].get(8),
141-
features: MetaData::parse_features(rows[0].get(9)),
142140
},
143141
files: file_list,
144142
})

templates/crate/features.html

+7-13
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@
2626
<div class="pure-menu package-menu">
2727
<ul class="pure-menu-list">
2828
<li class="pure-menu-heading">Feature flags</li>
29-
{%- if metadata.features -%}
30-
{%- for feature in metadata.features -%}
29+
{%- if features -%}
30+
{%- for feature in features -%}
3131
<li class="pure-menu-item">
3232
<a href="#{{ feature.name }}" class="pure-menu-link" style="text-align:center;">
3333
{{ feature.name }}
3434
</a>
3535
</li>
3636
{%- endfor -%}
37-
{%- elif metadata.features is iterable -%}
37+
{%- elif features is iterable -%}
3838
<li class="pure-menu-item">
3939
<span style="font-size: 13px;">This release does not have any feature flags.</span>
4040
</li>
@@ -49,15 +49,9 @@
4949

5050
<div class="pure-u-1 pure-u-sm-17-24 pure-u-md-19-24 package-details" id="main">
5151
<h1>{{ metadata.name }}</h1>
52-
{%- if metadata.features -%}
53-
<p>This version has <b>{{ metadata.features | length }}</b> feature flags, <b data-id="default-feature-len">
54-
{%- if metadata.features[0].name == 'default' -%}
55-
{{ metadata.features[0].subfeatures | length }}
56-
{%- else -%}
57-
0
58-
{%- endif -%}
59-
</b> of them enabled by <b>default</b>.</p>
60-
{%- for feature in metadata.features -%}
52+
{%- if features -%}
53+
<p>This version has <b>{{ features | length }}</b> feature flags, <b data-id="default-feature-len">{{ default_len }}</b> of them enabled by <b>default</b>.</p>
54+
{%- for feature in features -%}
6155
<h3 id="{{ feature.name }}">{{ feature.name }}</h3>
6256
<ul class="pure-menu-list">
6357
{%- if feature.subfeatures -%}
@@ -71,7 +65,7 @@ <h3 id="{{ feature.name }}">{{ feature.name }}</h3>
7165
{%- endif -%}
7266
</ul>
7367
{%- endfor -%}
74-
{%- elif metadata.features is iterable -%}
68+
{%- elif features is iterable -%}
7569
<p data-id="empty-features">This release does not have any feature flags.</p>
7670
{%- else -%}
7771
<p data-id="null-features">Feature flags data are not available for this release.</p>

0 commit comments

Comments
 (0)