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

frontend: Show feature flags in topbar and separate page #1144

Merged
merged 4 commits into from
Nov 13, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
4 changes: 2 additions & 2 deletions src/db/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, FromSql, ToSql)]
#[postgres(name = "feature")]
pub struct Feature {
name: String,
subfeatures: Vec<String>,
pub(crate) name: String,
pub(crate) subfeatures: Vec<String>,
}

impl Feature {
Expand Down
35 changes: 32 additions & 3 deletions src/web/crate_details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ impl CrateDetails {
releases.license,
releases.documentation_url,
releases.default_target,
releases.features,
doc_coverage.total_items,
doc_coverage.documented_items,
doc_coverage.total_items_needing_examples,
Expand Down Expand Up @@ -149,7 +148,6 @@ impl CrateDetails {
default_target: krate.get("default_target"),
doc_targets: MetaData::parse_doc_targets(krate.get("doc_targets")),
yanked: krate.get("yanked"),
features: MetaData::parse_features(krate.get("features")),
};

let documented_items: Option<i32> = krate.get("documented_items");
Expand Down Expand Up @@ -817,14 +815,45 @@ mod tests {
});
}

#[test]
fn feature_flags_with_nested_default() {
wrapper(|env| {
let features = [
("default".into(), vec!["feature1".into()]),
("feature1".into(), vec!["feature2".into()]),
("feature2".into(), Vec::new()),
]
.iter()
.cloned()
.collect::<HashMap<String, Vec<String>>>();
env.fake_release()
.name("library")
.version("0.1.0")
.features(features)
.create()?;

let page = kuchiki::parse_html().one(
env.frontend()
.get("/crate/library/0.1.0/features")
.send()?
.text()?,
);
assert!(page.select_first(r#"p[data-id="empty-features"]"#).is_err());
let def_len = page
.select_first(r#"b[data-id="default-feature-len"]"#)
.unwrap();
assert_eq!(def_len.text_contents(), "3");
Ok(())
});
}

#[test]
fn feature_flags_report_null() {
wrapper(|env| {
let id = env
.fake_release()
.name("library")
.version("0.1.0")
.features(HashMap::new())
.create()?;

env.db()
Expand Down
58 changes: 58 additions & 0 deletions src/web/features.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::db::types::Feature;
use crate::{
db::Pool,
impl_webpage,
Expand All @@ -6,10 +7,13 @@ use crate::{
use iron::{IronResult, Request, Response};
use router::Router;
use serde::Serialize;
use std::collections::{HashMap, VecDeque};

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
struct FeaturesPage {
metadata: MetaData,
features: Option<Vec<Feature>>,
default_len: usize,
}

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

let mut conn = extension!(req, Pool).get()?;
let rows = ctry!(
req,
conn.query(
"SELECT releases.features FROM releases
INNER JOIN crates ON crates.id = releases.crate_id
WHERE crates.name = $1 AND releases.version = $2",
&[&name, &version]
)
);

let row = cexpect!(req, rows.get(0));

let mut default_len = 0;
let features = row
.get::<'_, usize, Option<Vec<Feature>>>(0)
.map(|raw| {
raw.into_iter()
.filter(|feature| !feature.is_private())
.map(|feature| (feature.name.clone(), feature))
.collect::<HashMap<String, Feature>>()
})
.map(|mut feature_map| {
almusil marked this conversation as resolved.
Show resolved Hide resolved
let mut features = get_tree_structure_from_default(&mut feature_map);
let mut remaining = feature_map
.into_iter()
.map(|(_, feature)| feature)
.collect::<Vec<Feature>>();
remaining.sort_by_key(|feature| feature.subfeatures.len());
almusil marked this conversation as resolved.
Show resolved Hide resolved

default_len = features.len();

features.extend(remaining.into_iter().rev());
features
});

FeaturesPage {
metadata: cexpect!(req, MetaData::from_crate(&mut conn, &name, &version)),
features,
default_len,
}
.into_response(req)
}

fn get_tree_structure_from_default(feature_map: &mut HashMap<String, Feature>) -> Vec<Feature> {
let mut features = Vec::new();
let mut queue: VecDeque<String> = VecDeque::new();

queue.push_back("default".into());
while !queue.is_empty() {
let name = queue.pop_front().unwrap();
if let Some(feature) = feature_map.remove(&name) {
feature
.subfeatures
.iter()
.for_each(|sub| queue.push_back(sub.clone()));
features.push(feature);
}
}
features
}
18 changes: 1 addition & 17 deletions src/web/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ mod sitemap;
mod source;
mod statics;

use crate::db::types::Feature;
use crate::{impl_webpage, Context};
use chrono::{DateTime, Utc};
use error::Nope;
Expand Down Expand Up @@ -522,7 +521,6 @@ pub(crate) struct MetaData {
pub(crate) default_target: String,
pub(crate) doc_targets: Vec<String>,
pub(crate) yanked: bool,
pub(crate) features: Option<Vec<Feature>>,
}

impl MetaData {
Expand All @@ -536,8 +534,7 @@ impl MetaData {
releases.rustdoc_status,
releases.default_target,
releases.doc_targets,
releases.yanked,
releases.features
releases.yanked
FROM releases
INNER JOIN crates ON crates.id = releases.crate_id
WHERE crates.name = $1 AND releases.version = $2",
Expand All @@ -556,7 +553,6 @@ impl MetaData {
default_target: row.get(5),
doc_targets: MetaData::parse_doc_targets(row.get(6)),
yanked: row.get(7),
features: MetaData::parse_features(row.get(8)),
})
}

Expand All @@ -571,14 +567,6 @@ impl MetaData {
})
.unwrap_or_else(Vec::new)
}

pub(crate) fn parse_features(features: Option<Vec<Feature>>) -> Option<Vec<Feature>> {
features.map(|vec| {
vec.into_iter()
.filter(|feature| !feature.is_private())
.collect()
})
}
}

#[derive(Debug, Clone, PartialEq, Serialize)]
Expand Down Expand Up @@ -857,7 +845,6 @@ mod test {
"arm64-unknown-linux-gnu".to_string(),
],
yanked: false,
features: None,
};

let correct_json = json!({
Expand All @@ -872,7 +859,6 @@ mod test {
"arm64-unknown-linux-gnu",
],
"yanked": false,
"features": null
});

assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap());
Expand All @@ -890,7 +876,6 @@ mod test {
"arm64-unknown-linux-gnu",
],
"yanked": false,
"features": null,
});

assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap());
Expand All @@ -908,7 +893,6 @@ mod test {
"arm64-unknown-linux-gnu",
],
"yanked": false,
"features": null,
});

assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap());
Expand Down
4 changes: 1 addition & 3 deletions src/web/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ impl FileList {
releases.files,
releases.default_target,
releases.doc_targets,
releases.yanked,
releases.features
releases.yanked
FROM releases
LEFT OUTER JOIN crates ON crates.id = releases.crate_id
WHERE crates.name = $1 AND releases.version = $2",
Expand Down Expand Up @@ -138,7 +137,6 @@ impl FileList {
default_target: rows[0].get(6),
doc_targets: MetaData::parse_doc_targets(rows[0].get(7)),
yanked: rows[0].get(8),
features: MetaData::parse_features(rows[0].get(9)),
},
files: file_list,
})
Expand Down
20 changes: 7 additions & 13 deletions templates/crate/features.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@
<div class="pure-menu package-menu">
<ul class="pure-menu-list">
<li class="pure-menu-heading">Feature flags</li>
{%- if metadata.features -%}
{%- for feature in metadata.features -%}
{%- if features -%}
{%- for feature in features -%}
<li class="pure-menu-item">
<a href="#{{ feature.name }}" class="pure-menu-link" style="text-align:center;">
{{ feature.name }}
</a>
</li>
{%- endfor -%}
{%- elif metadata.features is iterable -%}
{%- elif features is iterable -%}
<li class="pure-menu-item">
<span style="font-size: 13px;">This release does not have any feature flags.</span>
</li>
Expand All @@ -49,15 +49,9 @@

<div class="pure-u-1 pure-u-sm-17-24 pure-u-md-19-24 package-details" id="main">
<h1>{{ metadata.name }}</h1>
{%- if metadata.features -%}
<p>This version has <b>{{ metadata.features | length }}</b> feature flags, <b data-id="default-feature-len">
{%- if metadata.features[0].name == 'default' -%}
{{ metadata.features[0].subfeatures | length }}
{%- else -%}
0
{%- endif -%}
</b> of them enabled by <b>default</b>.</p>
{%- for feature in metadata.features -%}
{%- if features -%}
<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>
{%- for feature in features -%}
<h3 id="{{ feature.name }}">{{ feature.name }}</h3>
<ul class="pure-menu-list">
{%- if feature.subfeatures -%}
Expand All @@ -71,7 +65,7 @@ <h3 id="{{ feature.name }}">{{ feature.name }}</h3>
{%- endif -%}
</ul>
{%- endfor -%}
{%- elif metadata.features is iterable -%}
{%- elif features is iterable -%}
<p data-id="empty-features">This release does not have any feature flags.</p>
{%- else -%}
<p data-id="null-features">Feature flags data are not available for this release.</p>
Expand Down