Skip to content

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

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
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
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, '--');
}),
});
13 changes: 13 additions & 0 deletions app/helpers/format-build-result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Ember from 'ember';

export function formatBuildResult(result) {
if (result === true) {
return 'Pass';
} else if (result === false) {
return 'Fail';
} else {
return null;
}
}

export default Ember.Helper.helper(params => formatBuildResult(params[0]));
8 changes: 8 additions & 0 deletions app/helpers/format-day.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Ember from 'ember';
import moment from 'moment';

export function formatDay(date) {
return date ? moment(date).utc().format('YYYY-MM-DD') : null;
}

export default Ember.Helper.helper(params => formatDay(params[0]));
7 changes: 7 additions & 0 deletions app/helpers/value-or-default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Ember from 'ember';

export function valueOrDefault(value, default_value) {
return value ? value : default_value;
}

export default Ember.Helper.helper(params => valueOrDefault(params[0], params[1]));
46 changes: 46 additions & 0 deletions app/models/build-info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import DS from 'ember-data';
import Ember from 'ember';

const TIER1 = [
['x86_64-unknown-linux-gnu', 'Linux'],
['x86_64-apple-darwin', 'macOS'],
['x86_64-pc-windows-gnu', 'Windows (GNU)'],
['x86_64-pc-windows-msvc', 'Windows (MSVC)'],
];

const last = name => (
Ember.computed(name, function() {
const items = this.get(name);
return items[items.length - 1];
})
);

export default DS.Model.extend({
version: DS.belongsTo('version', { async: true }),
ordering: DS.attr(),
stable: DS.attr(),
beta: DS.attr(),
nightly: DS.attr(),

has_any_info: Ember.computed('ordering', function() {
const ordering = this.get('ordering');
const num_results = ordering.stable.length + ordering.nightly.length + ordering.beta.length;
return num_results > 0;
}),

latest_stable: last('ordering.stable'),
latest_beta: last('ordering.beta'),
latest_nightly: last('ordering.nightly'),
tier1_results: Ember.computed('nightly', 'latest_nightly', 'beta', 'latest_beta', 'stable', 'latest_stable', function() {
const nightly_results = this.get('nightly')[this.get('latest_nightly')] || {};
const beta_results = this.get('beta')[this.get('latest_beta')] || {};
const stable_results = this.get('stable')[this.get('latest_stable')] || {};

return TIER1.map(([target, display]) => ({
display_target: display,
nightly: nightly_results[target],
beta: beta_results[target],
stable: stable_results[target]
}));
}),
});
3 changes: 3 additions & 0 deletions app/models/crate.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export default DS.Model.extend({
documentation: DS.attr('string'),
repository: DS.attr('string'),
license: DS.attr('string'),
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
1 change: 1 addition & 0 deletions app/models/version.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default DS.Model.extend({
async: false
}),
authors: DS.hasMany('users', { async: true }),
build_info: DS.belongsTo('build-info', { async: true }),
dependencies: DS.hasMany('dependency', { async: true }),
version_downloads: DS.hasMany('version-download', { async: true }),

Expand Down
19 changes: 19 additions & 0 deletions app/styles/crate.scss
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,25 @@
}
}

#crate-build-info {
padding-bottom: 50px;
border-bottom: 5px solid $gray-border;
margin-bottom: 30px;

.description {
margin-bottom: 30px;
}

table {
border: 1px solid $gray-border;
td, th {
border: 1px solid $gray-border;
padding: 5px 10px;
text-align: left;
}
}
}

#crate-downloads {
@include display-flex;
@include flex-wrap(wrap);
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 @@ -10,6 +10,7 @@
{{#each crate.annotated_badges as |badge|}}
{{component badge.component_name badge=badge}}
{{/each}}
{{badge-build-info crate=crate}}
</div>
<div class='summary'>
<span class='small'>
Expand Down
34 changes: 34 additions & 0 deletions app/templates/crate/version.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,40 @@
</div>
</div>

<div id='crate-build-info'>
<h3>Build info</h3>

{{#if currentVersion.build_info.has_any_info }}
<div class='description'>
Most recent reported build information for
version {{ currentVersion.num }}:
</div>

<table>
<tr>
<th>Tier 1 Target</th>
<th>Stable: {{ value-or-default currentVersion.build_info.latest_stable 'None' }}</th>
<th>Beta: {{ value-or-default (format-day currentVersion.build_info.latest_beta) 'None' }}</th>
<th>Nightly: {{ value-or-default (format-day currentVersion.build_info.latest_nightly) 'None' }}</th>
</tr>

{{#each currentVersion.build_info.tier1_results as |result|}}
<tr>
<td>{{ result.display_target }}</td>
<td>{{ format-build-result result.stable }}</td>
<td>{{ format-build-result result.beta }}</td>
<td>{{ format-build-result result.nightly }}</td>
</tr>
{{/each}}
</table>
{{else}}
<div class='description'>
No build information available for this version. If you are the
crate owner... // TODO LINK TO DOCS
</div>
{{/if}}
</div>

<div id='crate-downloads'>
<div class='stats'>
<h3>Stats Overview</h3>
Expand Down
25 changes: 25 additions & 0 deletions src/bin/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,31 @@ fn migrations() -> Vec<Migration> {
try!(tx.execute("DROP INDEX badges_crate_type", &[]));
Ok(())
}),
Migration::add_table(20170127104519, "build_info", " \
version_id INTEGER NOT NULL, \
rust_version VARCHAR NOT NULL, \
target VARCHAR NOT NULL, \
passed BOOLEAN NOT NULL, \
created_at TIMESTAMP NOT NULL DEFAULT now(), \
updated_at TIMESTAMP NOT NULL DEFAULT now()"),
Migration::new(20170127104925, |tx| {
try!(tx.execute("CREATE UNIQUE INDEX build_info_combo \
ON build_info (version_id, rust_version, target)", &[]));
Ok(())
}, |tx| {
try!(tx.execute("DROP INDEX build_info_combo", &[]));
Ok(())
}),
Migration::run(20170127143020,
"ALTER TABLE crates \
ADD COLUMN max_build_info_stable VARCHAR, \
ADD COLUMN max_build_info_beta TIMESTAMP, \
ADD COLUMN max_build_info_nightly TIMESTAMP",
"ALTER TABLE crates \
DROP COLUMN max_build_info_stable, \
DROP COLUMN max_build_info_beta, \
DROP COLUMN max_build_info_nightly",
),
];
// NOTE: Generate a new id via `date +"%Y%m%d%H%M%S"`

Expand Down
39 changes: 36 additions & 3 deletions src/krate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ pub struct Crate {
pub license: Option<String>,
pub repository: Option<String>,
pub max_upload_size: Option<i32>,
pub max_build_info_stable: Option<semver::Version>,
pub max_build_info_beta: Option<Timespec>,
pub max_build_info_nightly: Option<Timespec>,
}

#[derive(RustcEncodable, RustcDecodable)]
Expand All @@ -66,6 +69,9 @@ pub struct EncodableCrate {
pub created_at: String,
pub downloads: i32,
pub max_version: String,
pub max_build_info_stable: Option<String>,
pub max_build_info_beta: Option<String>,
pub max_build_info_nightly: Option<String>,
pub description: Option<String>,
pub homepage: Option<String>,
pub documentation: Option<String>,
Expand Down Expand Up @@ -246,6 +252,7 @@ impl Crate {
name, created_at, updated_at, downloads, max_version, description,
homepage, documentation, license, repository,
readme: _, id: _, max_upload_size: _,
max_build_info_stable, max_build_info_beta, max_build_info_nightly,
} = self;
let versions_link = match versions {
Some(..) => None,
Expand All @@ -267,6 +274,9 @@ impl Crate {
categories: category_ids,
badges: badges,
max_version: max_version.to_string(),
max_build_info_stable: max_build_info_stable.map(|s| s.to_string()),
max_build_info_beta: max_build_info_beta.map(::encode_time),
max_build_info_nightly: max_build_info_nightly.map(::encode_time),
documentation: documentation,
homepage: homepage,
description: description,
Expand Down Expand Up @@ -386,11 +396,28 @@ impl Crate {
let zero = semver::Version::parse("0.0.0").unwrap();
if *ver > self.max_version || self.max_version == zero {
self.max_version = ver.clone();
self.max_build_info_stable = None;
self.max_build_info_beta = None;
self.max_build_info_nightly = None;
}
let stmt = try!(conn.prepare("UPDATE crates SET max_version = $1
WHERE id = $2 RETURNING updated_at"));
let rows = try!(stmt.query(&[&self.max_version.to_string(), &self.id]));

let stmt = conn.prepare(" \
UPDATE crates \
SET max_version = $1,
max_build_info_stable = $2,
max_build_info_beta = $3,
max_build_info_nightly = $4 \
WHERE id = $5 \
RETURNING updated_at")?;
let rows = try!(stmt.query(&[
&self.max_version.to_string(),
&self.max_build_info_stable.as_ref().map(|vers| vers.to_string()),
&self.max_build_info_beta,
&self.max_build_info_nightly,
&self.id
]));
self.updated_at = rows.get(0).get("updated_at");

Version::insert(conn, self.id, ver, features, authors)
}

Expand Down Expand Up @@ -460,6 +487,7 @@ impl Crate {
impl Model for Crate {
fn from_row(row: &Row) -> Crate {
let max: String = row.get("max_version");
let max_build_info_stable: Option<String> = row.get("max_build_info_stable");
Crate {
id: row.get("id"),
name: row.get("name"),
Expand All @@ -474,6 +502,11 @@ impl Model for Crate {
license: row.get("license"),
repository: row.get("repository"),
max_upload_size: row.get("max_upload_size"),
max_build_info_stable: max_build_info_stable.map(|stable| {
semver::Version::parse(&stable).unwrap()
}),
max_build_info_beta: row.get("max_build_info_beta"),
max_build_info_nightly: row.get("max_build_info_nightly"),
}
}
fn table_name(_: Option<Crate>) -> &'static str { "crates" }
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ pub fn middleware(app: Arc<App>) -> MiddlewareBuilder {
api_router.delete("/crates/:crate_id/owners", C(krate::remove_owners));
api_router.delete("/crates/:crate_id/:version/yank", C(version::yank));
api_router.put("/crates/:crate_id/:version/unyank", C(version::unyank));
api_router.get("/crates/:crate_id/:version/build_info", C(version::build_info));
api_router.put("/crates/:crate_id/:version/build_info", C(version::publish_build_info));
api_router.get("/crates/:crate_id/reverse_dependencies", C(krate::reverse_dependencies));
api_router.get("/versions", C(version::index));
api_router.get("/versions/:version_id", C(version::show));
Expand Down
3 changes: 3 additions & 0 deletions src/tests/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ fn krate(name: &str) -> Crate {
created_at: time::now().to_timespec(),
downloads: 10,
max_version: semver::Version::parse("0.0.0").unwrap(),
max_build_info_stable: None,
max_build_info_beta: None,
max_build_info_nightly: None,
documentation: None,
homepage: None,
description: None,
Expand Down
Loading