Skip to content

Add skills to tasks #1045

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

Merged
merged 2 commits into from
Feb 15, 2017
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,23 @@ const {
} = Ember;

/**
`project-card-skills` composes the given project's list of skills on a
project's card.
`related-skills` renders a list of associated skill records for an entity

- For projects, this is `project.projectSkills`
- For tasks, this is `task.taskSkills`

## default usage

```handlebars
{{project-card-skills skills=project.skills}}
{{related-skills skills=parent.xSkills}}
```

@class project-card-skills
@class card-skills
@module Component
@extends Ember.Component
*/
export default Component.extend({
classNames: ['project-card-skills'],
classNames: ['related-skills'],

/**
Returns whether or not the overflowing skills on the project card should be
Expand Down
4 changes: 3 additions & 1 deletion app/components/skill-list-item-link.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ export default Component.extend({

session: service(),

click() {
click(e) {
e.stopPropagation();

if (get(this, 'session.isAuthenticated')) {
this._toggleClickState();

Expand Down
3 changes: 2 additions & 1 deletion app/components/task-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Ember from 'ember';
const {
Component,
computed,
computed: { alias },
computed: { alias, mapBy },
get,
inject: { service }
} = Ember;
Expand Down Expand Up @@ -35,6 +35,7 @@ export default Component.extend({
taskTypeClass: computed('taskType', function() {
return `task-card--${get(this, 'taskType')}`;
}),
taskSkills: mapBy('task.taskSkills', 'skill'),

click() {
let isLoading = get(this, 'isLoading');
Expand Down
41 changes: 41 additions & 0 deletions app/controllers/project/tasks/task.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ export default Controller.extend({
*/
store: service(),

/**
* The injected taskSkillsList service
* Used to toggle task Skills
*
* @property taskSkillsList
* @type Ember.Service
*/
taskSkillsList: service(),

/**
* The injected services/current-user service
* @property currentUser
Expand All @@ -37,6 +46,14 @@ export default Controller.extend({
*/
comments: filterBy('task.comments', 'isNew', false),

/**
* Shorthad for a list of the current task's assigned skills
*
* @property taskSkills
* @type DS.ManyArray
*/
taskSkills: alias('task.taskSkills'),

/**
* Alias to the user property of the current user
*
Expand Down Expand Up @@ -79,6 +96,30 @@ export default Controller.extend({
*/
onSaveError(payload) {
this._onError(payload);
},

/**
* removeTaskSkill - Triggers when a user clicks an assigned task skill's
* button. Destroys the taskSkill record, removing the skill from the task's
* list of skills.
*
* @param {DS.Model} taskSkill - The taskSkill record to destroy
*/
removeTaskSkill(taskSkill) {
return taskSkill.destroyRecord();
},

/**
* toggleSkill - Triggers when a user clicks or submits a skill in the skill
* dropdown. Uses the `taskSkillList` service to create or destroy the
* `taskSkill` record associated to the currently loaded `task` and the
* provided `skill`
*
* @param {DS.Model} skill - The skill to create or destroy a `taskSkill` for
*/
toggleSkill(skill) {
let list = get(this, 'taskSkillsList');
return list.toggle(skill);
}
},

Expand Down
7 changes: 7 additions & 0 deletions app/models/task-skill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Model from 'ember-data/model';
import { belongsTo } from 'ember-data/relationships';

export default Model.extend({
task: belongsTo('task', { async: true }),
skill: belongsTo('skill', { async: true })
});
2 changes: 2 additions & 0 deletions app/models/task.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ export default Model.extend(ContainsCodeMixin, {
*/
taskList: belongsTo('task-list', { async: true }),

taskSkills: hasMany('task-skill', { async: true }),

/**
The task user mentions that belong to the task.

Expand Down
6 changes: 6 additions & 0 deletions app/routes/project/tasks/task.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const {
export default Route.extend({
currentUser: service(),
store: service(),
taskSkillsList: service(),

model(params) {
let projectId = this.modelFor('project').id;
Expand All @@ -19,6 +20,11 @@ export default Route.extend({
return store.queryRecord('task', { projectId, number });
},

afterModel(task) {
get(this, 'taskSkillsList').setTask(task);
return task;
},

setupController(controller, task) {
let store = get(this, 'store');
let user = get(this, 'currentUser.user');
Expand Down
57 changes: 57 additions & 0 deletions app/services/task-skills-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Ember from 'ember';
import recordsList from 'code-corps-ember/utils/records-list';

const {
computed: {
alias
},
get,
getProperties,
inject: { service },
Service,
set
} = Ember;

export default Service.extend({
store: service(),

taskSkills: alias('task.taskSkills'),

add(skill) {
let { store, task } = getProperties(this, 'store', 'task');
return store.createRecord('task-skill', { task, skill }).save();
},

contains(skill) {
let taskSkills = get(this, 'taskSkills');
return recordsList.contains(taskSkills, skill);
},

find(skill) {
let { taskSkills, task } = getProperties(this, 'taskSkills', 'task');
return recordsList.find(taskSkills, skill, task);
},

remove(skill) {
let taskSkill = this.find(skill);
return taskSkill.destroyRecord();
},

setTask(task) {
set(this, 'task', task);
return this._refresh();
},

toggle(skill) {
let taskSkills = get(this, 'taskSkills');
if (recordsList.contains(taskSkills, skill)) {
return this.remove(skill);
} else {
return this.add(skill);
}
},

_refresh() {
return get(this, 'taskSkills').reload();
}
});
6 changes: 5 additions & 1 deletion app/styles/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@
// COMPONENTS - PROJECTS
//
@import "components/project-card-members";
@import "components/project-card-skills";
@import "components/project-card";
@import "components/project-details";
@import "components/project-header";
Expand All @@ -111,6 +110,11 @@
@import "components/skills-list";
@import "components/skills-typeahead";

//
// COMPONETS - RELATED SKILLS
//
@import "components/related-skills";

//
// COMPONENTS - TASKS
//
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
.project-card-skills {
margin: 20px 0 0 0;
.related-skills {
&.project-card__skills {
margin: 20px 0 0 0;
}

&.task-card__skills {
margin: 0 0 7px 0;
}

.expander {
display: flex;
Expand All @@ -25,6 +31,10 @@
margin-left: 10px;
}

&.hidden {
display: none;
}

a {
color: $text--lighter;
&:hover {
Expand Down
2 changes: 1 addition & 1 deletion app/templates/components/project-card.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
<p class="description">
{{project.description}}
</p>
{{project-card-skills skills=projectSkills}}
{{related-skills class="project-card__skills" skills=projectSkills}}
{{project-card-members members=projectOrganizationMembers}}
</div>
1 change: 1 addition & 0 deletions app/templates/components/task-card.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<span class="task-card__title" data-test-selector="task title">{{task.title}}</span>
<span class="task-card__number" data-test-selector="task number">#{{task.number}}</span>
</p>
{{related-skills class="task-card__skills" skills=taskSkills}}
<p class="task-card__meta">
<span class="task-card__icon"></span>
<span class="task-card__time" data-test-selector="task time">{{moment-from-now task.insertedAt}}</span>
Expand Down
26 changes: 26 additions & 0 deletions app/templates/project/tasks/task.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,32 @@
</div>
</div>
<div class="task-sidebar">
<div class="task-sidebar-section">
<div class="task-sidebar-section__header">
<h2>Skills</h2>
</div>

<div class="task-skills-list centered">
{{#each task.taskSkills as |taskSkill|}}
{{skill-button
alwaysShowX=true
iconBefore=false
isLoading=taskSkill.isLoading
remove=(action 'removeTaskSkill' taskSkill)
size="small"
skill=taskSkill.skill
}}
{{/each}}
</div>

<div class="skills-typeahead-container">
{{skills-typeahead
centered=true
selectSkill=(action 'toggleSkill')
skillsList=taskSkillsList
}}
</div>
</div>
</div>
</div>
</div>
18 changes: 14 additions & 4 deletions mirage/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ function generatePreviewMentions(schema, preview) {

// The set of routes we have defined; needs updated when adding new routes
const routes = [
'categories', 'comment-user-mentions', 'comments', 'donation-goals', 'organizations', 'task-lists',
'task-user-mentions', 'tasks', 'previews', 'projects', 'project-categories', 'slugged-routes',
'stripe-connect-accounts', 'stripe-connect-subscriptions', 'stripe-connect-plans',
'stripe-platform-cards', 'stripe-platform-customers',
'categories', 'comment-user-mentions', 'comments', 'donation-goals',
'organizations', 'task-lists', 'task-skills', 'task-user-mentions', 'tasks',
'previews', 'projects', 'project-categories', 'slugged-routes',
'stripe-connect-accounts', 'stripe-connect-subscriptions',
'stripe-connect-plans', 'stripe-platform-cards', 'stripe-platform-customers',
'user-categories', 'users'
];

Expand Down Expand Up @@ -539,6 +540,15 @@ export default function() {
return task;
});

/**
* Task skills
*/

this.get('/task-skills', { coalesce: true });
this.post('/task-skills');
this.get('/task-skills/:id');
this.delete('/task-skills/:id');

/**
* Token
*/
Expand Down
6 changes: 6 additions & 0 deletions mirage/models/task-skill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Model, belongsTo } from 'ember-cli-mirage';

export default Model.extend({
task: belongsTo(),
skill: belongsTo()
});
46 changes: 46 additions & 0 deletions tests/acceptance/task-editing-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,49 @@ test('A task cannot be opened or closed by someone else', function(assert) {
assert.notOk(taskPage.taskStatusButton.close.isVisible, 'The close button is not rendered');
});
});

test('Skills can be assigned or unassigned to/from task', function(assert) {
assert.expect(5);

let done = assert.async();

server.create('skill', {
title: 'Ruby'
});

let user = server.schema.users.create({ username: 'test_user' });
authenticateSession(this.application, { user_id: user.id });

let project = createProjectWithSluggedRoute();
let { organization } = project;
let task = project.createTask({ title: 'Test title', body: 'Test body', taskType: 'issue', number: 1 });
task.user = user;
task.save();

taskPage.visit({
organization: organization.slug,
project: project.slug,
number: task.number
});

andThen(() => {
taskPage.skillsTypeahead.fillIn('ru');
});

andThen(() => {
assert.equal(taskPage.skillsTypeahead.inputItems().count, 1);
assert.equal(taskPage.skillsTypeahead.inputItems(0).text, 'Ruby');
taskPage.skillsTypeahead.inputItems(0).click();
});

andThen(() => {
assert.equal(taskPage.taskSkillsList().count, 1);
assert.equal(taskPage.taskSkillsList(0).text, 'Ruby');
taskPage.taskSkillsList(0).click();
});

andThen(() => {
assert.equal(taskPage.taskSkillsList().count, 0);
done();
});
});
Loading