Skip to content

Commit af7f0c1

Browse files
Merge pull request #790 from natboehm/team-page
Team page
2 parents 6e19bdb + f8e595a commit af7f0c1

File tree

23 files changed

+649
-50
lines changed

23 files changed

+649
-50
lines changed

app/controllers/team.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Ember from 'ember';
2+
import PaginationMixin from '../mixins/pagination';
3+
4+
const { computed } = Ember;
5+
6+
export default Ember.Controller.extend(PaginationMixin, {
7+
queryParams: ['page', 'per_page', 'sort'],
8+
page: '1',
9+
per_page: 10,
10+
sort: 'alpha',
11+
12+
totalItems: computed.readOnly('model.crates.meta.total'),
13+
14+
currentSortBy: computed('sort', function() {
15+
return (this.get('sort') === 'downloads') ? 'Downloads' : 'Alphabetical';
16+
}),
17+
});

app/models/crate.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export default DS.Model.extend({
3030
badge_sort: ['badge_type'],
3131
annotated_badges: Ember.computed.sort('enhanced_badges', 'badge_sort'),
3232
owners: DS.hasMany('users', { async: true }),
33+
owner_team: DS.hasMany('teams', { async: true }),
34+
owner_user: DS.hasMany('users', { async: true }),
3335
version_downloads: DS.hasMany('version-download', { async: true }),
3436
keywords: DS.hasMany('keywords', { async: true }),
3537
categories: DS.hasMany('categories', { async: true }),

app/models/team.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import DS from 'ember-data';
2+
import Ember from 'ember';
3+
4+
export default DS.Model.extend({
5+
email: DS.attr('string'),
6+
name: DS.attr('string'),
7+
login: DS.attr('string'),
8+
api_token: DS.attr('string'),
9+
avatar: DS.attr('string'),
10+
url: DS.attr('string'),
11+
kind: DS.attr('string'),
12+
org_name: Ember.computed('login', function() {
13+
let login = this.get('login');
14+
let login_split = login.split(':');
15+
return login_split[1];
16+
})
17+
});

app/models/user.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export default DS.Model.extend({
77
api_token: DS.attr('string'),
88
avatar: DS.attr('string'),
99
url: DS.attr('string'),
10+
kind: DS.attr('string'),
1011
});

app/router.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Router.map(function() {
4444
});
4545
this.route('category_slugs');
4646
this.route('catchAll', { path: '*path' });
47+
this.route('team', { path: '/teams/:team_id' });
4748
});
4849

4950
export default Router;

app/routes/team.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Ember from 'ember';
2+
3+
export default Ember.Route.extend({
4+
queryParams: {
5+
page: { refreshedModel: true },
6+
sort: { refreshedModel: true },
7+
},
8+
data: {},
9+
10+
setupController(controller, model) {
11+
this._super(controller, model);
12+
13+
controller.set('fetchingFeed', true);
14+
controller.set('crates', this.get('data.crates'));
15+
},
16+
17+
model(params) {
18+
const { team_id } = params;
19+
return this.store.find('team', team_id).then(
20+
(team) => {
21+
params.team_id = team.get('id');
22+
return Ember.RSVP.hash({
23+
crates: this.store.query('crate', params),
24+
team
25+
});
26+
},
27+
(e) => {
28+
if (e.errors.any(e => e.detail === 'Not Found')) {
29+
this
30+
.controllerFor('application')
31+
.set('nextFlashError', `User '${params.team_id}' does not exist`);
32+
return this.replaceWith('index');
33+
}
34+
}
35+
);
36+
},
37+
});

app/styles/crate.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
img {
2323
@include align-self(center);
2424
}
25+
.team-info {
26+
@include flex-direction(column);
27+
}
2528
}
2629
h1 {
2730
padding-left: 10px;

app/templates/crate/version.hbs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,19 @@
148148
<div>
149149
<h3>Owners</h3>
150150
<ul class='owners'>
151-
{{#each crate.owners as |owner|}}
151+
{{#each crate.owner_team as |team|}}
152152
<li>
153-
{{#link-to 'user' owner.login}}
154-
{{user-avatar user=owner size='medium-small'}}
155-
{{/link-to}}
153+
{{#link-to team.kind team.login}}
154+
{{user-avatar user=team size='medium-small'}}
155+
{{/link-to}}
156+
</li>
157+
{{/each}}
158+
159+
{{#each crate.owner_user as |user|}}
160+
<li>
161+
{{#link-to user.kind user.login}}
162+
{{user-avatar user=user size='medium-small'}}
163+
{{/link-to}}
156164
</li>
157165
{{/each}}
158166
</ul>

app/templates/team.hbs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<div id='crates-heading'>
2+
<div class="wide">
3+
<div class='info'>
4+
{{user-avatar user=model.team size='medium'}}
5+
<div class='team-info'>
6+
<h1>
7+
{{ model.team.org_name }}
8+
</h1>
9+
<h2>
10+
{{ model.team.name }}
11+
</h2>
12+
</div>
13+
{{#user-link user=model.team}}
14+
<img alt="GitHub profile" title="GitHub profile" src="/assets/GitHub-Mark-32px.png"/>
15+
{{/user-link}}
16+
</div>
17+
</div>
18+
</div>
19+
20+
<div id='user-profile'>
21+
<div class='info'>
22+
{{! TODO: reduce duplication with templates/crates.hbs }}
23+
24+
<div id='results'>
25+
<div class='nav'>
26+
<span class='amt small'>
27+
Displaying
28+
<span class='cur'>{{ currentPageStart }}-{{ currentPageEnd }}</span>
29+
of <span class='total'>{{ totalItems }}</span> total results
30+
</span>
31+
</div>
32+
33+
<div class='sort'>
34+
<span class='small'>Sort by</span>
35+
{{#rl-dropdown-container class="dropdown-container"}}
36+
{{#rl-dropdown-toggle tagName="a" class="dropdown"}}
37+
<img class="sort" src="/assets/sort.png"/>
38+
{{ currentSortBy }}
39+
<span class='arrow'></span>
40+
{{/rl-dropdown-toggle}}
41+
42+
{{#rl-dropdown tagName="ul" class="dropdown" closeOnChildClick="a:link"}}
43+
<li>
44+
{{#link-to (query-params sort="alpha")}}
45+
Alphabetical
46+
{{/link-to}}
47+
</li>
48+
<li>
49+
{{#link-to (query-params sort="downloads")}}
50+
Downloads
51+
{{/link-to}}
52+
</li>
53+
{{/rl-dropdown}}
54+
{{/rl-dropdown-container}}
55+
</div>
56+
</div>
57+
58+
<div id='crates' class='white-rows'>
59+
{{#each model.crates as |crate|}}
60+
{{crate-row crate=crate}}
61+
{{/each}}
62+
</div>
63+
64+
<div class='pagination'>
65+
{{#link-to (query-params page=prevPage) class="prev" rel="prev" title="previous page"}}
66+
<img class="left-pag" src="/assets/left-pag.png"/>
67+
{{/link-to}}
68+
{{#each pages as |page|}}
69+
{{#link-to (query-params page=page)}}{{ page }}{{/link-to}}
70+
{{/each}}
71+
{{#link-to (query-params page=nextPage) class="next" rel="next" title="next page"}}
72+
<img class="right-pag" src="/assets/right-pag.png"/>
73+
{{/link-to}}
74+
</div>
75+
</div>
76+
</div>

mirage/config.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ import crateFixture from '../mirage/fixtures/crate';
55
import crateVersionsFixture from '../mirage/fixtures/crate_versions';
66
import crateAuthorsFixture from '../mirage/fixtures/crate_authors';
77
import crateOwnersFixture from '../mirage/fixtures/crate_owners';
8+
import crateTeamsFixture from '../mirage/fixtures/crate_teams';
89
import crateReverseDependenciesFixture from '../mirage/fixtures/crate_reverse_dependencies';
910
import crateDependenciesFixture from '../mirage/fixtures/crate_dependencies';
1011
import crateDownloadsFixture from '../mirage/fixtures/crate_downloads';
1112
import keywordFixture from '../mirage/fixtures/keyword';
13+
import teamFixture from '../mirage/fixtures/team';
14+
import userFixture from '../mirage/fixtures/user';
1215

1316
export default function() {
1417
this.get('/summary', () => summaryFixture);
@@ -20,6 +23,20 @@ export default function() {
2023
crates: searchFixture.crates.slice(start, end),
2124
meta: searchFixture.meta,
2225
};
26+
} else if (request.queryParams.team_id) {
27+
const { start, end } = pageParams(request);
28+
return {
29+
team: teamFixture.team,
30+
crates: searchFixture.crates.slice(start, end),
31+
meta: searchFixture.meta,
32+
};
33+
} else if (request.queryParams.user_id) {
34+
const { start, end } = pageParams(request);
35+
return {
36+
user: userFixture.user,
37+
crates: searchFixture.crates.slice(start, end),
38+
meta: searchFixture.meta,
39+
};
2340
}
2441
});
2542

@@ -28,11 +45,14 @@ export default function() {
2845
this.get('/api/v1/crates/nanomsg', () => crateFixture);
2946
this.get('/api/v1/crates/nanomsg/versions', () => crateVersionsFixture);
3047
this.get('/api/v1/crates/nanomsg/:version_num/authors', () => crateAuthorsFixture);
31-
this.get('/api/v1/crates/nanomsg/owners', () => crateOwnersFixture);
48+
this.get('/api/v1/crates/nanomsg/owner_user', () => crateOwnersFixture);
49+
this.get('/api/v1/crates/nanomsg/owner_team', () => crateTeamsFixture);
3250
this.get('/api/v1/crates/nanomsg/reverse_dependencies', () => crateReverseDependenciesFixture);
3351
this.get('/api/v1/crates/nanomsg/:version_num/dependencies', () => crateDependenciesFixture);
3452
this.get('/api/v1/crates/nanomsg/downloads', () => crateDownloadsFixture);
3553
this.get('/api/v1/keywords/network', () => keywordFixture);
54+
this.get('/api/v1/teams/:team_id', () => teamFixture);
55+
this.get('/api/v1/users/:user_id', () => userFixture);
3656
}
3757

3858
function pageParams(request) {

mirage/fixtures/crate.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ export default {
1515
],
1616
"license": "MIT",
1717
"links": {
18-
"owners": "/api/v1/crates/nanomsg/owners",
18+
"owner_user": "/api/v1/crates/nanomsg/owner_user",
19+
"owner_team": "/api/v1/crates/nanomsg/owner_team",
1920
"reverse_dependencies": "/api/v1/crates/nanomsg/reverse_dependencies",
2021
"version_downloads": "/api/v1/crates/nanomsg/downloads",
2122
"versions": null

mirage/fixtures/crate_teams.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// jscs:disable validateQuoteMarks
2+
export default {
3+
"teams": [
4+
{
5+
"avatar": "https://avatars.githubusercontent.com/u/565790?v=3",
6+
"email": "[email protected]",
7+
"id": 2,
8+
"kind": "team",
9+
"login": "github:org:thehydroimpulse",
10+
"name": "Team Daniel Fagnan",
11+
"url": "https://github.com/thehydroimpulse"
12+
},
13+
{
14+
"avatar": "https://avatars.githubusercontent.com/u/9447137?v=3",
15+
"email": null,
16+
"id": 303,
17+
"kind": "team",
18+
"login": "github:org:blabaere",
19+
"name": "Team Benoît Labaere",
20+
"url": "https://github.com/blabaere"
21+
}
22+
]
23+
};

mirage/fixtures/team.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// jscs:disable validateQuoteMarks
2+
export default {
3+
"team": {
4+
"avatar": "https://avatars.githubusercontent.com/u/565790?v=3",
5+
"id": 1,
6+
"login": "github:org_test:thehydroimpulseteam",
7+
"name": "thehydroimpulseteam",
8+
"url": "https://github.com/thehydroimpulse",
9+
}
10+
};

mirage/fixtures/user.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// jscs:disable validateQuoteMarks
2+
export default {
3+
"user": {
4+
"avatar": "https://avatars.githubusercontent.com/u/565790?v=3",
5+
"email": "[email protected]",
6+
"id": 1,
7+
"login": "thehydroimpulse",
8+
"name": "Daniel Fagnan",
9+
"url": "https://github.com/thehydroimpulse"
10+
}
11+
};

src/krate.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ pub struct CrateLinks {
114114
pub version_downloads: String,
115115
pub versions: Option<String>,
116116
pub owners: Option<String>,
117+
pub owner_team: Option<String>,
118+
pub owner_user: Option<String>,
117119
pub reverse_dependencies: String,
118120
}
119121

@@ -535,6 +537,8 @@ impl Crate {
535537
version_downloads: format!("/api/v1/crates/{}/downloads", name),
536538
versions: versions_link,
537539
owners: Some(format!("/api/v1/crates/{}/owners", name)),
540+
owner_team: Some(format!("/api/v1/crates/{}/owner_team", name)),
541+
owner_user: Some(format!("/api/v1/crates/{}/owner_user", name)),
538542
reverse_dependencies: format!("/api/v1/crates/{}/reverse_dependencies", name),
539543
},
540544
}
@@ -864,6 +868,15 @@ pub fn index(req: &mut Request) -> CargoResult<Response> {
864868
.filter(crate_owners::owner_kind.eq(OwnerKind::User as i32)),
865869
),
866870
);
871+
} else if let Some(team_id) = params.get("team_id").and_then(|s| s.parse::<i32>().ok()) {
872+
query = query.filter(
873+
crates::id.eq_any(
874+
crate_owners::table
875+
.select(crate_owners::crate_id)
876+
.filter(crate_owners::owner_id.eq(team_id))
877+
.filter(crate_owners::owner_kind.eq(OwnerKind::Team as i32)),
878+
),
879+
);
867880
} else if params.get("following").is_some() {
868881
query = query.filter(crates::id.eq_any(
869882
follows::table.select(follows::crate_id).filter(
@@ -1423,6 +1436,40 @@ pub fn owners(req: &mut Request) -> CargoResult<Response> {
14231436
Ok(req.json(&R { users: owners }))
14241437
}
14251438

1439+
/// Handles the `GET /crates/:crate_id/owner_team` route.
1440+
pub fn owner_team(req: &mut Request) -> CargoResult<Response> {
1441+
let crate_name = &req.params()["crate_id"];
1442+
let conn = req.db_conn()?;
1443+
let krate = Crate::by_name(crate_name).first::<Crate>(&*conn)?;
1444+
let owners = Team::owning(&krate, &conn)?
1445+
.into_iter()
1446+
.map(Owner::encodable)
1447+
.collect();
1448+
1449+
#[derive(RustcEncodable)]
1450+
struct R {
1451+
teams: Vec<EncodableOwner>,
1452+
}
1453+
Ok(req.json(&R { teams: owners }))
1454+
}
1455+
1456+
/// Handles the `GET /crates/:crate_id/owner_user` route.
1457+
pub fn owner_user(req: &mut Request) -> CargoResult<Response> {
1458+
let crate_name = &req.params()["crate_id"];
1459+
let conn = req.db_conn()?;
1460+
let krate = Crate::by_name(crate_name).first::<Crate>(&*conn)?;
1461+
let owners = User::owning(&krate, &conn)?
1462+
.into_iter()
1463+
.map(Owner::encodable)
1464+
.collect();
1465+
1466+
#[derive(RustcEncodable)]
1467+
struct R {
1468+
users: Vec<EncodableOwner>,
1469+
}
1470+
Ok(req.json(&R { users: owners }))
1471+
}
1472+
14261473
/// Handles the `PUT /crates/:crate_id/owners` route.
14271474
pub fn add_owners(req: &mut Request) -> CargoResult<Response> {
14281475
modify_owners(req, true)

0 commit comments

Comments
 (0)