Skip to content

Commit ea3fc0a

Browse files
committed
Support GitLab Issues
Move GitHub Tasks to github namespace `/tasks/github` Add a new gitlab route `/tasks/gitlab` Add a new gitlab serializer Closes #38
1 parent c520198 commit ea3fc0a

File tree

14 files changed

+190
-8
lines changed

14 files changed

+190
-8
lines changed

app/components/settings-modal.js

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export default Component.extend({
88

99
// Properties
1010
token_github_com: oneWay('userSettings.tokens.github_com'),
11+
token_gitlab_com: oneWay('userSettings.tokens.gitlab_com'),
1112

1213
init(...args) {
1314
this._super(...args);
@@ -19,6 +20,7 @@ export default Component.extend({
1920
},
2021
saveSettings() {
2122
this.userSettings.setToken('github_com', this.get('token_github_com'));
23+
this.userSettings.setToken('gitlab_com', this.get('token_gitlab_com'));
2224
this.set('isActive', false);
2325
},
2426
},

app/controllers/tasks/github.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import Controller from '@ember/controller';
2+
3+
export default Controller.extend({
4+
});

app/data/organizations.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default {
1111
},
1212
{
1313
type: 'gitlab',
14-
identifier: 'coala',
14+
identifier: 'coala/mobans',
1515
},
1616
],
1717
},

app/models/gitlab-task.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import TaskModel from './task';
2+
3+
export default TaskModel.extend({});

app/router.js

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const Router = EmberRouter.extend({
99
Router.map(function mainRoute() {
1010
return this.route('tasks', function tasksRoute() {
1111
this.route('github');
12+
this.route('gitlab');
1213
});
1314
});
1415

app/routes/tasks/gitlab.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Route from '@ember/routing/route';
2+
import { inject } from '@ember/service';
3+
4+
export default Route.extend({
5+
gitlab: inject(),
6+
store: inject(),
7+
organizations: inject(),
8+
9+
model(params, transition) {
10+
const { org } = transition.queryParams;
11+
const store = this.get('store');
12+
const projects = this.get('organizations').fetchGitlabProjects(org);
13+
if (projects.length > 0) {
14+
return this.get('gitlab').tasks({ projects }).then((data) => {
15+
data.forEach((task) => {
16+
store.pushPayload('gitlab-task', task);
17+
});
18+
return store.peekAll('gitlab-task');
19+
});
20+
}
21+
return [];
22+
},
23+
});

app/serializers/gitlab-task.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import DS from 'ember-data';
2+
3+
export default DS.JSONSerializer.extend({
4+
pushPayload(store, payload) {
5+
const task = {};
6+
task.id = payload.id;
7+
task.bodyText = payload.description;
8+
task.commentCount = payload.user_notes_count;
9+
// Set the default color to 65C8FF, otherwise, we need to refetch the default
10+
// color from `project/:id/labels` endpoint.
11+
task.labels = payload.labels.map(label => ({ color: '65C8FF', name: label }));
12+
task.updatedAt = payload.updated_at;
13+
task.title = payload.title;
14+
task.url = payload.web_url;
15+
16+
task.author = {};
17+
task.author.url = payload.author.web_url;
18+
task.author.login = payload.author.username;
19+
task.author.avatarUrl = payload.author.avatar_url;
20+
21+
task.repository = {};
22+
task.repository.nameWithOwner = payload.repository.path_with_namespace;
23+
task.repository.url = payload.repository.web_url;
24+
25+
task.isPullRequest = payload._type === 'PullRequest';
26+
27+
store.push(this.normalize(store.modelFor('gitlab-task'), task));
28+
return task;
29+
},
30+
});

app/services/gitlab.js

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { inject } from '@ember/service';
2+
import AjaxService from 'ember-ajax/services/ajax';
3+
import RSVP from 'rsvp';
4+
5+
const ENDPOINT = 'https://gitlab.com/api/v4';
6+
const PROJECT_ENDPOINT = `${ENDPOINT}/projects/{projectId}`;
7+
const ISSUE_ENDPOINT = `${PROJECT_ENDPOINT}/issues?order_by=created_at&state=opened&per_page=100`;
8+
9+
function buildIssuesUrl(projectId) {
10+
return ISSUE_ENDPOINT.replace('{projectId}', encodeURIComponent(projectId));
11+
}
12+
13+
function buildProjectUrl(projectId) {
14+
return PROJECT_ENDPOINT.replace('{projectId}', encodeURIComponent(projectId));
15+
}
16+
17+
export default AjaxService.extend({
18+
userSettings: inject(),
19+
20+
host: 'https://gitlab.com/',
21+
_issueUrl: '',
22+
23+
init(...arg) {
24+
this._super(...arg);
25+
this.set('headers', { 'Private-Token': this.get('userSettings').tokens.get('gitlab_com') });
26+
},
27+
28+
tasks({ projects }) {
29+
const tasks = projects.map((projectId) => {
30+
const embedRepoInfo = taskList => this.fetchRepositoryInfo(projectId)
31+
.then(repoInfo => taskList.map((task) => {
32+
const decoratedTask = Object.assign({}, task);
33+
decoratedTask.repository = repoInfo;
34+
decoratedTask.type = 'gitlab-task';
35+
return decoratedTask;
36+
}));
37+
return this.fetchIssues(projectId).then(embedRepoInfo);
38+
});
39+
40+
return RSVP.all(tasks).then(taskList =>
41+
taskList
42+
.reduce((combinedTasks, initialTasklist) => combinedTasks.concat(initialTasklist), []));
43+
},
44+
45+
fetchRepositoryInfo(projectId) {
46+
return this.request(buildProjectUrl(projectId));
47+
},
48+
49+
fetchIssues(projectId) {
50+
return this.request(buildIssuesUrl(projectId));
51+
},
52+
53+
});

app/services/organizations.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import Object, { computed } from '@ember/object';
1+
import EmberObject, { computed } from '@ember/object';
22
import Service from '@ember/service';
33
import organizations from '../data/organizations';
44

55
export default Service.extend({
66
init(...args) {
77
this._super(...args);
8-
this.organizations = Object.create(organizations);
8+
this.organizations = EmberObject.create(organizations);
99
},
1010

1111
list: computed('organizations', function getOrganizationList() {
@@ -15,4 +15,18 @@ export default Service.extend({
1515
fetch(slug) {
1616
return this.organizations.get(slug);
1717
},
18+
19+
fetchGitlabProjects(slug) {
20+
const { trackers } = this.fetch(slug);
21+
if (!trackers) {
22+
return [];
23+
}
24+
return trackers.reduce((previous, tracker) => {
25+
if (tracker.type === 'gitlab') {
26+
return [...previous, tracker.identifier];
27+
}
28+
return previous;
29+
}, []);
30+
},
31+
1832
});

app/templates/components/settings-modal.hbs

+8
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@
1414
<p class="help is-danger">In order to fetch results from GitHub, you must specify GitHub token. With ANY scope you like. No exact scope is required.</p>
1515
<p class="help">Get GitHub user token from <a href="https://github.com/settings/tokens" title="https://github.com/settings/tokens" target="_blank">https://github.com/settings/tokens</a></p>
1616
</div>
17+
<div class="field">
18+
<label class="label">GitLab Token</label>
19+
<div class="control">
20+
{{input class="input" value=token_gitlab_com}}
21+
</div>
22+
<p class="help is-danger">In order to fetch results from GitLab, you must specify GitLab token. With ANY scope you like. No exact scope is required.</p>
23+
<p class="help">Get GitHub user token from <a href="https://github.com/settings/tokens" title="https://github.com/settings/tokens" target="_blank">https://github.com/settings/tokens</a></p>
24+
</div>
1725
</section>
1826
<footer class="modal-card-foot">
1927
<button class="button is-success" onclick={{action "saveSettings"}}>Save</button>

app/templates/tasks.hbs

-5
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,6 @@
2424
</aside>
2525
</section>
2626
<div class="column is-three-quarters">
27-
<nav class="panel">
28-
<p class="panel-tabs is-size-4">
29-
<a class="is-active {{if isGithubpage "disabled"}}">GitHub</a>
30-
</p>
31-
</nav>
3227
{{outlet}}
3328
</div>
3429
</div>

app/templates/tasks/github.hbs

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
<nav class="panel">
2+
<p class="panel-tabs is-size-4">
3+
<a class="is-active" disabled>GitHub</a>
4+
<a>|</a>
5+
{{#link-to 'tasks.gitlab'}}
6+
GitLab
7+
{{/link-to}}
8+
</p>
9+
</nav>
110
{{#each tasks as |task|}}
211
{{task-item task=task}}
312
<hr>

app/templates/tasks/gitlab.hbs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<nav class="panel">
2+
<p class="panel-tabs is-size-4">
3+
{{#link-to 'tasks.github'}}
4+
GitHub
5+
{{/link-to}}
6+
<a>|</a>
7+
<a class="is-active" disabled>GitLab</a>
8+
</p>
9+
</nav>
10+
{{#each model as |task|}}
11+
{{task-item task=task}}
12+
<hr>
13+
{{/each}}
14+
{{#unless model}}
15+
<p>This Org doesn't have any gitlab repository.</p>
16+
{{/unless}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Service from '@ember/service';
2+
import { visit, find } from '@ember/test-helpers';
3+
import { module, test } from 'qunit';
4+
import { setupApplicationTest } from 'ember-qunit';
5+
6+
// Simulating empty gitlab Projects
7+
const emptyGitlabOrgService = Service.extend({
8+
fetchGitlabProjects: () => [],
9+
fetch: () => ({}),
10+
});
11+
12+
module('Acceptance/tasks/list-gitlab-tasks-test', function listGitlabTaskTests(hooks) {
13+
setupApplicationTest(hooks);
14+
15+
hooks.beforeEach(function beforeEach() {
16+
this.owner.register('service:organizations', emptyGitlabOrgService);
17+
});
18+
19+
test('should show no gitlab repository', async function listGitlabTasks(assert) {
20+
await visit('/tasks/gitlab?org=discourse');
21+
const $noGitlabRepo = find('.is-three-quarters > p');
22+
assert.equal($noGitlabRepo.innerText, "This Org doesn't have any gitlab repository.");
23+
});
24+
});

0 commit comments

Comments
 (0)