Skip to content

Commit

Permalink
Merge pull request #504 from integer32llc/ci-badge
Browse files Browse the repository at this point in the history
Add support for showing Travis and Appveyor current build status badges
  • Loading branch information
alexcrichton authored Jan 17, 2017
2 parents 6df510b + 9e297fb commit 0288ef9
Show file tree
Hide file tree
Showing 24 changed files with 646 additions and 20 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ git2 = "0.6"
flate2 = "0.2"
semver = "0.5"
url = "1.2.1"
postgres = { version = "0.13", features = ["with-time", "with-openssl"] }
postgres = { version = "0.13", features = ["with-time", "with-openssl", "with-rustc-serialize"] }
r2d2 = "0.7.0"
r2d2_postgres = "0.11"
openssl = "0.9"
Expand Down
16 changes: 16 additions & 0 deletions app/components/badge-appveyor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Ember from 'ember';

export default Ember.Component.extend({
tagName: 'span',
classNames: ['badge'],
repository: Ember.computed.alias('badge.attributes.repository'),
branch: Ember.computed('badge.attributes.branch', function() {
return this.get('badge.attributes.branch') || 'master';
}),
service: Ember.computed('badge.attributes.service', function() {
return this.get('badge.attributes.service') || 'github';
}),
text: Ember.computed('badge', function() {
return `Appveyor build status for the ${ this.get('branch') } branch`;
})
});
13 changes: 13 additions & 0 deletions app/components/badge-travis-ci.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Ember from 'ember';

export default Ember.Component.extend({
tagName: 'span',
classNames: ['badge'],
repository: Ember.computed.alias('badge.attributes.repository'),
branch: Ember.computed('badge.attributes.branch', function() {
return this.get('badge.attributes.branch') || 'master';
}),
text: Ember.computed('branch', function() {
return `Travis CI build status for the ${ this.get('branch') } branch`;
})
});
1 change: 1 addition & 0 deletions app/controllers/crate/version.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default Ember.Controller.extend({
requestedVersion: null,
keywords: computed.alias('crate.keywords'),
categories: computed.alias('crate.categories'),
badges: computed.alias('crate.badges'),

sortedVersions: computed.readOnly('crate.versions'),

Expand Down
15 changes: 15 additions & 0 deletions app/mirage/fixtures/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ export default {
"name": "rust_mixin",
"repository": "https://github.com/huonw/external_mixin",
"updated_at": "2015-02-27T11:52:13Z",
"badges": [
{
"attributes": {
"repository": "huonw/external_mixin"
},
"badge_type": "appveyor"
},
{
"attributes": {
"branch": "master",
"repository": "huonw/external_mixin"
},
"badge_type": "travis-ci"
}
],
"versions": null
}, {
"created_at": "2015-02-27T11:51:58Z",
Expand Down
11 changes: 11 additions & 0 deletions app/models/crate.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import DS from 'ember-data';
import Ember from 'ember';

export default DS.Model.extend({
name: DS.attr('string'),
Expand All @@ -17,6 +18,16 @@ export default DS.Model.extend({
license: DS.attr('string'),

versions: DS.hasMany('versions', { async: true }),
badges: DS.attr(),
enhanced_badges: Ember.computed.map('badges', badge => ({
// jshint ignore:start
// needed until https://github.com/jshint/jshint/issues/2991 is fixed
...badge,
// jshint ignore:end
component_name: `badge-${badge.badge_type}`
})),
badge_sort: ['badge_type'],
annotated_badges: Ember.computed.sort('enhanced_badges', 'badge_sort'),
owners: DS.hasMany('users', { async: true }),
version_downloads: DS.hasMany('version-download', { async: true }),
keywords: DS.hasMany('keywords', { async: true }),
Expand Down
3 changes: 0 additions & 3 deletions app/styles/crate.scss
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,6 @@
}
.vers {
margin-left: 10px;
img {
margin-bottom: -4px;
}
}

.stats {
Expand Down
6 changes: 6 additions & 0 deletions app/templates/components/badge-appveyor.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<a href="https://ci.appveyor.com/project/{{ repository }}">
<img
src="https://ci.appveyor.com/api/projects/status/{{ service }}/{{ repository }}?svg=true&branch={{ branch }}"
alt="{{ text }}"
title="{{ text }}" />
</a>
6 changes: 6 additions & 0 deletions app/templates/components/badge-travis-ci.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<a href="https://travis-ci.org/{{ repository }}">
<img
src="https://travis-ci.org/{{ repository }}.svg?branch={{ branch }}"
alt="{{ text }}"
title="{{ text }}" />
</a>
3 changes: 3 additions & 0 deletions app/templates/components/crate-row.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
alt="{{ crate.max_version }}"
title="{{ crate.name }}’s current version badge" />
</span>
{{#each crate.annotated_badges as |badge|}}
{{component badge.component_name badge=badge}}
{{/each}}
</div>
<div class='summary'>
<span class='small'>
Expand Down
6 changes: 6 additions & 0 deletions app/templates/crate/version.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@
alt="{{ crate.name }}’s current version badge"
title="{{ crate.name }}’s current version badge" />
</p>

{{#each crate.annotated_badges as |badge|}}
<p>
{{component badge.component_name badge=badge}}
</p>
{{/each}}
</div>

<div class="authors">
Expand Down
194 changes: 194 additions & 0 deletions src/badge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
use util::CargoResult;
use krate::Crate;
use Model;

use std::collections::HashMap;
use pg::GenericConnection;
use pg::rows::Row;
use rustc_serialize::json::Json;

#[derive(Debug, PartialEq, Clone)]
pub enum Badge {
TravisCi {
repository: String, branch: Option<String>,
},
Appveyor {
repository: String, branch: Option<String>, service: Option<String>,
},
}

#[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)]
pub struct EncodableBadge {
pub badge_type: String,
pub attributes: HashMap<String, String>,
}

impl Model for Badge {
fn from_row(row: &Row) -> Badge {
let attributes: Json = row.get("attributes");
if let Json::Object(attributes) = attributes {
let badge_type: String = row.get("badge_type");
match badge_type.as_str() {
"travis-ci" => {
Badge::TravisCi {
branch: attributes.get("branch")
.and_then(Json::as_string)
.map(str::to_string),
repository: attributes.get("repository")
.and_then(Json::as_string)
.map(str::to_string)
.expect("Invalid TravisCi badge \
without repository in the \
database"),
}
},
"appveyor" => {
Badge::Appveyor {
service: attributes.get("service")
.and_then(Json::as_string)
.map(str::to_string),
branch: attributes.get("branch")
.and_then(Json::as_string)
.map(str::to_string),
repository: attributes.get("repository")
.and_then(Json::as_string)
.map(str::to_string)
.expect("Invalid Appveyor badge \
without repository in the \
database"),
}
},
_ => {
panic!("Unknown badge type {} in the database", badge_type);
},
}
} else {
panic!(
"badge attributes {:?} in the database was not a JSON object",
attributes
);
}
}
fn table_name(_: Option<Badge>) -> &'static str { "badges" }
}

impl Badge {
pub fn encodable(self) -> EncodableBadge {
EncodableBadge {
badge_type: self.badge_type().to_string(),
attributes: self.attributes(),
}
}

pub fn badge_type(&self) -> &'static str {
match *self {
Badge::TravisCi {..} => "travis-ci",
Badge::Appveyor {..} => "appveyor",
}
}

pub fn json_attributes(self) -> Json {
Json::Object(self.attributes().into_iter().map(|(k, v)| {
(k, Json::String(v))
}).collect())
}

fn attributes(self) -> HashMap<String, String> {
let mut attributes = HashMap::new();

match self {
Badge::TravisCi { branch, repository } => {
attributes.insert(String::from("repository"), repository);
if let Some(branch) = branch {
attributes.insert(
String::from("branch"),
branch
);
}
},
Badge::Appveyor { service, branch, repository } => {
attributes.insert(String::from("repository"), repository);
if let Some(branch) = branch {
attributes.insert(
String::from("branch"),
branch
);
}
if let Some(service) = service {
attributes.insert(
String::from("service"),
service
);
}
}
}

attributes
}

fn from_attributes(badge_type: &str,
attributes: &HashMap<String, String>)
-> Result<Badge, String> {
match badge_type {
"travis-ci" => {
match attributes.get("repository") {
Some(repository) => {
Ok(Badge::TravisCi {
repository: repository.to_string(),
branch: attributes.get("branch")
.map(String::to_string),
})
},
None => Err(badge_type.to_string()),
}
},
"appveyor" => {
match attributes.get("repository") {
Some(repository) => {
Ok(Badge::Appveyor {
repository: repository.to_string(),
branch: attributes.get("branch")
.map(String::to_string),
service: attributes.get("service")
.map(String::to_string),

})
},
None => Err(badge_type.to_string()),
}
},
_ => Err(badge_type.to_string()),
}
}

pub fn update_crate(conn: &GenericConnection,
krate: &Crate,
badges: HashMap<String, HashMap<String, String>>)
-> CargoResult<Vec<String>> {

let mut invalid_badges = vec![];

let badges: Vec<_> = badges.iter().filter_map(|(k, v)| {
Badge::from_attributes(k, v).map_err(|invalid_badge| {
invalid_badges.push(invalid_badge)
}).ok()
}).collect();

conn.execute("\
DELETE FROM badges \
WHERE crate_id = $1;",
&[&krate.id]
)?;

for badge in badges {
conn.execute("\
INSERT INTO badges (crate_id, badge_type, attributes) \
VALUES ($1, $2, $3) \
ON CONFLICT (crate_id, badge_type) DO UPDATE \
SET attributes = EXCLUDED.attributes;",
&[&krate.id, &badge.badge_type(), &badge.json_attributes()]
)?;
}
Ok(invalid_badges)
}
}
12 changes: 12 additions & 0 deletions src/bin/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,18 @@ fn migrations() -> Vec<Migration> {
ON crates_categories;"));
Ok(())
}),
Migration::add_table(20170102131034, "badges", " \
crate_id INTEGER NOT NULL, \
badge_type VARCHAR NOT NULL, \
attributes JSONB NOT NULL"),
Migration::new(20170102145236, |tx| {
try!(tx.execute("CREATE UNIQUE INDEX badges_crate_type \
ON badges (crate_id, badge_type)", &[]));
Ok(())
}, |tx| {
try!(tx.execute("DROP INDEX badges_crate_type", &[]));
Ok(())
}),
];
// NOTE: Generate a new id via `date +"%Y%m%d%H%M%S"`

Expand Down
Loading

0 comments on commit 0288ef9

Please sign in to comment.