Skip to content

Display reported build information in a badge and on crate version pages #764

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

Closed
Closed
Prev Previous commit
Next Next commit
Show badge for latest version build status on crate list pages
If the latest version builds on any stable version, show that it
builds on stable. If not and it builds on any beta version, show
that. If not and it builds on any nightly version, show
that. Otherwise, don't show any badge.
  • Loading branch information
carols10cents committed Feb 6, 2018
commit a5dc65170c7805f734a57b19933f3406afc8c33c
41 changes: 41 additions & 0 deletions app/components/badge-build-info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Ember from 'ember';

import { formatDay } from 'cargo/helpers/format-day';

export default Ember.Component.extend({
tagName: 'span',
classNames: ['build_info'],

build_info: Ember.computed('crate.max_build_info_stable', 'crate.max_build_info_beta', 'crate.max_build_info_nightly', function() {
if (this.get('crate.max_build_info_stable')) {
return 'stable';
} else if (this.get('crate.max_build_info_beta')) {
return 'beta';
} else if (this.get('crate.max_build_info_nightly')) {
return 'nightly';
} else {
return null;
}
}),
color: Ember.computed('build_info', function() {
if (this.get('build_info') === 'stable') {
return 'brightgreen';
} else if (this.get('build_info') === 'beta') {
return 'yellow';
} else {
return 'orange';
}
}),
version_display: Ember.computed('build_info', 'crate.max_build_info_stable', 'crate.max_build_info_beta', 'crate.max_build_info_nightly', function() {
if (this.get('build_info') === 'stable') {
return this.get('crate.max_build_info_stable');
} else if (this.get('build_info') === 'beta') {
return formatDay(this.get('crate.max_build_info_beta'));
} else {
return formatDay(this.get('crate.max_build_info_nightly'));
}
}),
version_for_shields: Ember.computed('version_display', function() {
return this.get('version_display').replace(/-/g, '--');
}),
});
3 changes: 3 additions & 0 deletions app/models/crate.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export default DS.Model.extend({
documentation: DS.attr('string'),
repository: DS.attr('string'),
exact_match: DS.attr('boolean'),
max_build_info_nightly: DS.attr('date'),
max_build_info_beta: DS.attr('date'),
max_build_info_stable: DS.attr('string'),

versions: DS.hasMany('versions', { async: true }),
badges: DS.attr(),
Expand Down
6 changes: 6 additions & 0 deletions app/templates/components/badge-build-info.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{{#if build_info}}
<img
src="https://img.shields.io/badge/builds_on-{{ version_for_shields }}-{{color}}.svg"
alt="Known to build on {{ build_info }} {{ version_display }}"
title="Known to build on {{ build_info }} {{ version_display }}" />
{{/if}}
1 change: 1 addition & 0 deletions app/templates/components/crate-row.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
{{#each crate.annotated_badges as |badge|}}
{{component badge.component_name badge=badge data-test-badge=badge.badge_type}}
{{/each}}
{{badge-build-info crate=crate}}
</div>
<div class='summary' data-test-description>
<span class='small'>
Expand Down
3 changes: 2 additions & 1 deletion src/krate/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub fn summary(req: &mut Request) -> CargoResult<Response> {
.map(|versions| Version::max(versions.into_iter().map(|v| v.num)))
.zip(krates)
.map(|(max_version, krate)| {
Ok(krate.minimal_encodable(&max_version, None, false, None))
Ok(krate.minimal_encodable(&max_version, None, false, None, None))
})
.collect()
};
Expand Down Expand Up @@ -156,6 +156,7 @@ pub fn show(req: &mut Request) -> CargoResult<Response> {
Some(badges),
false,
recent_downloads,
None,
),
versions: versions
.into_iter()
Expand Down
15 changes: 14 additions & 1 deletion src/krate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use url::Url;
use app::App;
use util::{human, CargoResult};

use views::EncodableBadge;
use views::{EncodableBadge, EncodableMaxVersionBuildInfo};
use models::{Badge, Category, CrateOwner, Keyword, NewCrateOwnerInvitation, Owner, OwnerKind,
ReverseDependency, User, Version};

Expand Down Expand Up @@ -113,6 +113,9 @@ pub struct EncodableCrate {
pub repository: Option<String>,
pub links: CrateLinks,
pub exact_match: bool,
pub max_build_info_stable: Option<String>,
pub max_build_info_beta: Option<String>,
pub max_build_info_nightly: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
Expand Down Expand Up @@ -315,6 +318,7 @@ impl Crate {
badges: Option<Vec<Badge>>,
exact_match: bool,
recent_downloads: Option<i64>,
max_build_info: Option<EncodableMaxVersionBuildInfo>,
) -> EncodableCrate {
self.encodable(
max_version,
Expand All @@ -324,6 +328,7 @@ impl Crate {
badges,
exact_match,
recent_downloads,
max_build_info,
)
}

Expand All @@ -337,6 +342,7 @@ impl Crate {
badges: Option<Vec<Badge>>,
exact_match: bool,
recent_downloads: Option<i64>,
max_build_info: Option<EncodableMaxVersionBuildInfo>,
) -> EncodableCrate {
let Crate {
name,
Expand All @@ -357,6 +363,7 @@ impl Crate {
let category_ids = categories.map(|cats| cats.iter().map(|cat| cat.slug.clone()).collect());
let badges = badges.map(|bs| bs.into_iter().map(|b| b.encodable()).collect());
let documentation = Crate::remove_blacklisted_documentation_urls(documentation);
let max_build_info = max_build_info.unwrap_or_else(EncodableMaxVersionBuildInfo::default);

EncodableCrate {
id: name.clone(),
Expand All @@ -370,6 +377,9 @@ impl Crate {
categories: category_ids,
badges: badges,
max_version: max_version.to_string(),
max_build_info_stable: max_build_info.stable,
max_build_info_beta: max_build_info.beta,
max_build_info_nightly: max_build_info.nightly,
documentation: documentation,
homepage: homepage,
exact_match: exact_match,
Expand Down Expand Up @@ -606,6 +616,9 @@ mod tests {
downloads: 0,
recent_downloads: None,
max_version: "".to_string(),
max_build_info_stable: None,
max_build_info_beta: None,
max_build_info_nightly: None,
description: None,
homepage: None,
documentation: None,
Expand Down
2 changes: 1 addition & 1 deletion src/krate/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ pub fn publish(req: &mut Request) -> CargoResult<Response> {
warnings: Warnings<'a>,
}
Ok(req.json(&R {
krate: krate.minimal_encodable(&max_version, None, false, None),
krate: krate.minimal_encodable(&max_version, None, false, None, None),
warnings: warnings,
}))
})
Expand Down
28 changes: 23 additions & 5 deletions src/krate/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use user::RequestUser;
use util::{CargoResult, RequestUtils};

use views::EncodableCrate;
use models::{Badge, Crate, OwnerKind, Version};
use models::{Badge, BuildInfo, Crate, OwnerKind, Version};
use schema::*;

use super::{canon_crate_name, ALL_COLUMNS};
Expand Down Expand Up @@ -183,28 +183,46 @@ pub fn search(req: &mut Request) -> CargoResult<Response> {
.load::<Version>(&*conn)?
.grouped_by(&crates)
.into_iter()
.map(|versions| Version::max(versions.into_iter().map(|v| v.num)));
.map(|versions| {
versions
.into_iter()
.max_by(Version::semantically_newest_first)
.unwrap()
})
.collect::<Vec<_>>();

let build_infos = BuildInfo::belonging_to(&versions)
.filter(build_info::passed.eq(true))
.select(::version::build_info::BUILD_INFO_FIELDS)
.load::<BuildInfo>(&*conn)?
.grouped_by(&versions)
.into_iter()
.map(BuildInfo::max);

let crates = versions
.into_iter()
.zip(crates)
.zip(perfect_matches)
.zip(recent_downloads)
.zip(build_infos)
.map(
|(((max_version, krate), perfect_match), recent_downloads)| {
|((((max_version, krate), perfect_match), recent_downloads), build_info)| {
let build_info = build_info?;
// FIXME: If we add crate_id to the Badge enum we can eliminate
// this N+1
let badges = badges::table
.filter(badges::crate_id.eq(krate.id))
.load::<Badge>(&*conn)?;
Ok(krate.minimal_encodable(
&max_version,
&max_version.num,
Some(badges),
perfect_match,
Some(recent_downloads),
Some(build_info.encode()),
))
},
)
.collect::<Result<_, ::diesel::result::Error>>()?;
.collect::<CargoResult<_>>()?;

#[derive(Serialize)]
struct R {
Expand Down
1 change: 1 addition & 0 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub use owner::{CrateOwner, NewTeam, Owner, OwnerKind, Rights, Team};
pub use user::{Email, NewUser, User};
pub use token::ApiToken;
pub use version::{NewVersion, Version};
pub use version::build_info::BuildInfo;

mod category;
mod keyword;
81 changes: 80 additions & 1 deletion src/version/build_info.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use std::str::FromStr;

use chrono::{DateTime, NaiveDate, Utc};
use conduit::{Request, Response};
use semver;
use serde_json;

use app::RequestApp;
Expand All @@ -8,7 +12,8 @@ use owner::rights;
use user::RequestUser;
use util::{human, CargoResult, RequestUtils};
use version::version_and_crate;
use views::EncodableVersionBuildInfoUpload;
use views::{EncodableMaxVersionBuildInfo, EncodableVersionBuildInfoUpload,
ParsedRustChannelVersion};

use schema::*;

Expand All @@ -24,6 +29,80 @@ pub struct BuildInfo {
pub passed: bool,
}

/// The columns to select from the `build_info` table. The table also stores `created_at` and
/// `updated_at` metadata for each row, but we're not displaying those anywhere so we're not
/// bothering to select them.
pub const BUILD_INFO_FIELDS: (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the created_at and updated_at fields ever used at all? Is it worth even having them in the first place?

build_info::version_id,
build_info::rust_version,
build_info::target,
build_info::passed,
) = (
build_info::version_id,
build_info::rust_version,
build_info::target,
build_info::passed,
);

#[derive(Debug)]
/// The maximum version of Rust from each channel that a crate version successfully builds with.
/// Used for summarizing this information in badge form on crate list pages.
pub struct MaxBuildInfo {
pub stable: Option<semver::Version>,
pub beta: Option<NaiveDate>,
pub nightly: Option<NaiveDate>,
}

impl MaxBuildInfo {
/// Encode stable semver number as a string and beta and nightly as times appropriate for
/// JSON.
pub fn encode(self) -> EncodableMaxVersionBuildInfo {
fn naive_date_to_rfc3339(date: NaiveDate) -> String {
DateTime::<Utc>::from_utc(date.and_hms(0, 0, 0), Utc).to_rfc3339()
}

EncodableMaxVersionBuildInfo {
stable: self.stable.map(|v| v.to_string()),
beta: self.beta.map(naive_date_to_rfc3339),
nightly: self.nightly.map(naive_date_to_rfc3339),
}
}
}

impl BuildInfo {
/// From a set of build information data, Find the largest or latest Rust versions that we know
/// about for each channel. Stable uses the largest semver version number; beta and nightly use
/// the latest date.
pub fn max<I>(build_infos: I) -> CargoResult<MaxBuildInfo>
where
I: IntoIterator<Item = BuildInfo>,
{
let build_infos = build_infos
.into_iter()
.map(|bi| ParsedRustChannelVersion::from_str(&bi.rust_version))
.collect::<Result<Vec<_>, _>>()?;

let stable = build_infos
.iter()
.filter_map(ParsedRustChannelVersion::as_stable)
.max();
let beta = build_infos
.iter()
.filter_map(ParsedRustChannelVersion::as_beta)
.max();
let nightly = build_infos
.iter()
.filter_map(ParsedRustChannelVersion::as_nightly)
.max();

Ok(MaxBuildInfo {
stable: stable.cloned(),
beta: beta.cloned(),
nightly: nightly.cloned(),
})
}
}

/// Handles the `POST /crates/:crate_id/:version/build_info` route for the
/// `cargo publish-build-info` command to report on which versions of Rust
/// a crate builds with.
Expand Down
7 changes: 1 addition & 6 deletions src/version/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,7 @@ pub fn build_info(req: &mut Request) -> CargoResult<Response> {
let conn = req.db_conn()?;

let build_infos = BuildInfo::belonging_to(&version)
.select((
build_info::version_id,
build_info::rust_version,
build_info::target,
build_info::passed,
))
.select(::version::build_info::BUILD_INFO_FIELDS)
.load(&*conn)?;

let mut encodable_build_info = EncodableVersionBuildInfo::default();
Expand Down
5 changes: 5 additions & 0 deletions src/version/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ impl Version {

Ok(())
}

/// Orders SemVer numbers so that "higher" version numbers appear first.
pub fn semantically_newest_first(a: &Self, b: &Self) -> ::std::cmp::Ordering {
b.num.cmp(&a.num)
}
}

impl NewVersion {
Expand Down
Loading