Skip to content

Commit 1937862

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 1937862

File tree

6 files changed

+101
-38
lines changed

6 files changed

+101
-38
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

+1-17
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 {
@@ -536,8 +534,7 @@ impl MetaData {
536534
releases.rustdoc_status,
537535
releases.default_target,
538536
releases.doc_targets,
539-
releases.yanked,
540-
releases.features
537+
releases.yanked
541538
FROM releases
542539
INNER JOIN crates ON crates.id = releases.crate_id
543540
WHERE crates.name = $1 AND releases.version = $2",
@@ -556,7 +553,6 @@ impl MetaData {
556553
default_target: row.get(5),
557554
doc_targets: MetaData::parse_doc_targets(row.get(6)),
558555
yanked: row.get(7),
559-
features: MetaData::parse_features(row.get(8)),
560556
})
561557
}
562558

@@ -571,14 +567,6 @@ impl MetaData {
571567
})
572568
.unwrap_or_else(Vec::new)
573569
}
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-
}
582570
}
583571

584572
#[derive(Debug, Clone, PartialEq, Serialize)]
@@ -857,7 +845,6 @@ mod test {
857845
"arm64-unknown-linux-gnu".to_string(),
858846
],
859847
yanked: false,
860-
features: None,
861848
};
862849

863850
let correct_json = json!({
@@ -872,7 +859,6 @@ mod test {
872859
"arm64-unknown-linux-gnu",
873860
],
874861
"yanked": false,
875-
"features": null
876862
});
877863

878864
assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap());
@@ -890,7 +876,6 @@ mod test {
890876
"arm64-unknown-linux-gnu",
891877
],
892878
"yanked": false,
893-
"features": null,
894879
});
895880

896881
assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap());
@@ -908,7 +893,6 @@ mod test {
908893
"arm64-unknown-linux-gnu",
909894
],
910895
"yanked": false,
911-
"features": null,
912896
});
913897

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

src/web/source.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ impl FileList {
5858
releases.files,
5959
releases.default_target,
6060
releases.doc_targets,
61-
releases.yanked,
62-
releases.features
61+
releases.yanked
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)