Skip to content

Commit

Permalink
Merge pull request #1054 from code-corps/1046-skill-ui-to-new-task-page
Browse files Browse the repository at this point in the history
Add skill ui to new task page
  • Loading branch information
joshsmith committed Feb 15, 2017
2 parents 0cc50ce + cf7fbed commit 16ba87d
Show file tree
Hide file tree
Showing 15 changed files with 177 additions and 43 deletions.
2 changes: 1 addition & 1 deletion app/components/skills-typeahead-result.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default Component.extend({

hasSkill: computed('skill', function() {
let { skill, skillsList } = getProperties(this, 'skill', 'skillsList');
return skillsList.contains(skill);
return skillsList.includes(skill);
}),
selected: alias('skill.selected'),

Expand Down
84 changes: 61 additions & 23 deletions app/controllers/project/tasks/new.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,64 @@ import { isNonValidationError } from 'code-corps-ember/utils/error-utils';
const {
Controller,
get,
RSVP,
set
} = Ember;

export default Controller.extend({
unsavedTaskSkills: [],
actions: {
/**
* saveTask - action
*
* Triggered when user clicks 'save' on the new task form.
* Adds task to the correct task list, which is the inbox task list.
* Saves task and, on success, transitions to the task route.
* On failure, calls the save error handler.
*
* @param {DS.Model} task The task to be saved.
*/
saveTask - action
Triggered when user clicks 'save' on the new task form.
Adds task to the correct task list, which is the inbox task list.
Saves task and, on success, transitions to the task route.
On failure, calls the save error handler.
@param {DS.Model} task The task to be saved.
*/
async saveTask(task) {
let project = get(task, 'project');
let inboxTaskList = await this._getInboxTaskList(project);

set(task, 'taskList', inboxTaskList);

return task.save()
.then((task) => this._saveSkills(task))
.then((task) => this.transitionToRoute('project.tasks.task', get(task, 'number')))
.catch((payload) => this._handleTaskSaveError(payload));
},

deselectSkill(skill) {
let unsavedTaskSkills = get(this, 'unsavedTaskSkills');
unsavedTaskSkills.removeObject(skill);
},

toggleSkill(skill) {
let unsavedTaskSkills = get(this, 'unsavedTaskSkills');
if (unsavedTaskSkills.includes(skill)) {
unsavedTaskSkills.removeObject(skill);
} else {
unsavedTaskSkills.pushObject(skill);
}
}
},

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

/**
* _getInboxTaskList - Private method
*
* Returns a promise, which, when resolved, holds the task list for the specified
* project, which is marked as an inbox task list.
* @param {DS.Model} project The currently loaded project.
* @return {DS.Model} The inbox task list for the specified project.
*/
_getInboxTaskList - Private function
Returns a promise, which, when resolved, holds the task list for the
specified project, which is marked as an inbox task list.
@param {DS.Model} project The currently loaded project.
@return {DS.Model} The inbox task list for the specified project.
*/
async _getInboxTaskList(project) {
let taskLists = await get(project, 'taskLists');
let inboxes = taskLists.filterBy('inbox', true);
Expand All @@ -47,16 +70,31 @@ export default Controller.extend({
},

/**
* _handleTaskSaveError - Private function
*
* Sets the controller `error` property if the error payloed is anything other
* than a validation error
*
* @param {DS.AdapterError} payload The payload to check.
*/
_handleTaskSaveError - Private function
Sets the controller `error` property if the error payloed is anything
other than a validation error
@param {DS.AdapterError} payload The payload to check.
*/
_handleTaskSaveError(payload) {
if (isNonValidationError(payload)) {
set(this, 'error', payload);
}
},

/**
_saveSkills - Private function
Saves any of the skills added during the creation of the task.
@param {DS.Model} task The task being created.
@return {RSVP.Promise} promise that is fulfilled when all `promises`
have been fulfilled, or rejected if any of them become rejected.
*/
async _saveSkills(task) {
let unsavedTaskSkills = get(this, 'unsavedTaskSkills');
let promises = unsavedTaskSkills.map((skill) => this._createTaskSkill(skill, task));
return RSVP.all(promises).then(() => task);
}
});
1 change: 1 addition & 0 deletions app/routes/project/tasks/new.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default Route.extend(AuthenticatedRouteMixin, {
let task = store.createRecord('task', { project, taskType, user });

set(controller, 'task', task);
set(controller, 'unsavedTaskSkills', []);
},

actions: {
Expand Down
6 changes: 3 additions & 3 deletions app/services/project-skills-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ export default Service.extend({
return store.createRecord('project-skill', { project, skill }).save();
},

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

find(skill) {
Expand All @@ -44,7 +44,7 @@ export default Service.extend({

toggle(skill) {
let projectSkills = get(this, 'projectSkills');
if (recordsList.contains(projectSkills, skill)) {
if (recordsList.includes(projectSkills, skill)) {
return this.remove(skill);
} else {
return this.add(skill);
Expand Down
6 changes: 3 additions & 3 deletions app/services/task-skills-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ export default Service.extend({
return store.createRecord('task-skill', { task, skill }).save();
},

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

find(skill) {
Expand All @@ -44,7 +44,7 @@ export default Service.extend({

toggle(skill) {
let taskSkills = get(this, 'taskSkills');
if (recordsList.contains(taskSkills, skill)) {
if (recordsList.includes(taskSkills, skill)) {
return this.remove(skill);
} else {
return this.add(skill);
Expand Down
6 changes: 3 additions & 3 deletions app/services/user-skills-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ export default Service.extend({
return store.createRecord('user-skill', { user, skill }).save();
},

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

find(skill) {
Expand All @@ -41,7 +41,7 @@ export default Service.extend({

toggle(skill) {
let userSkills = get(this, 'userSkills');
if (recordsList.contains(userSkills, skill)) {
if (recordsList.includes(userSkills, skill)) {
return this.remove(skill);
} else {
return this.add(skill);
Expand Down
26 changes: 26 additions & 0 deletions app/templates/project/tasks/new.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,31 @@
{{/if}}
</div>
<div class="task-new-sidebar">
<div class="task-sidebar-section">
<div class="task-sidebar-section__header">
<h2>Skills</h2>
</div>

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

<div class="skills-typeahead-container">
{{skills-typeahead
centered=true
selectSkill=(action 'toggleSkill')
skillsList=unsavedTaskSkills
}}
</div>
</div>
</div>
</div>
2 changes: 1 addition & 1 deletion app/utils/records-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default {
@return {Boolean} `true` if found, otherwise `false`
@public
*/
contains(records, target) {
includes(records, target) {
if (records) {
return records.any((found) => {
let targetId = get(target, 'id');
Expand Down
56 changes: 56 additions & 0 deletions tests/acceptance/task-creation-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,3 +301,59 @@ test('Navigation is aborted if user answers negatively to prompt', function(asse
stub.restore();
});
});

test('Skills can be assigned to task during creation', function(assert) {
assert.expect(6);

let user = server.schema.users.create({ username: 'test_user' });

let project = createProjectWithSluggedRoute();
let { organization } = project;
project.createTaskList({ inbox: true });

authenticateSession(this.application, { user_id: user.id });

projectTasksIndexPage.visit({ organization: organization.slug, project: project.slug });

andThen(() => {
projectTasksIndexPage.clickNewTask();
});

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

andThen(() => {
assert.equal(currentRouteName(), 'project.tasks.new', 'Button takes us to the proper route');
assert.equal(find('[name=task-type]').val(), 'issue', 'Has the right default task type');

projectTasksNewPage.taskTitle('A task title')
.taskMarkdown('A task body')
.taskType('idea');
});

// NOTE: We need to be doing this async, so the code is ugly
// Possibly switching to await/async might make it nicer
andThen(() => {
// find skill
projectTasksNewPage.skillsTypeahead.fillIn('ru');
});

andThen(() => {
// add skill
projectTasksNewPage.skillsTypeahead.inputItems(0).click();
});

andThen(() => {
projectTasksNewPage.clickSubmit();
});

andThen(() => {
assert.equal(server.schema.tasks.all().models.length, 1, 'A task has been created');
assert.equal(server.schema.taskSkills.all().models.length, 1, 'A single task skill has been created.');

let [task] = server.schema.tasks.all().models;
let [taskSkill] = server.schema.taskSkills.all().models;

assert.equal(taskSkill.taskId, task.id, 'The correct task was assigned');
assert.equal(taskSkill.skillId, skill.id, 'The correct skill was assigned');
});
});
2 changes: 1 addition & 1 deletion tests/integration/components/skill-list-item-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ moduleForComponent('skill-list-item', 'Integration | Component | skill list item
},
beforeEach() {
stubService(this, 'user-skills-list', {
contains() {
includes() {
return true;
},
find(skill) {
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/components/skill-list-items-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ moduleForComponent('skill-list-items', 'Integration | Component | skill list ite
integration: true,
beforeEach() {
stubService(this, 'user-skills-list', {
contains(queriedSkill) {
includes(queriedSkill) {
return queriedSkill === skills[1];
},
find(queriedSkill) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let skill = Object.create({
});

let userSkillsList = {
contains() {
includes() {
return true;
},
find(queriedSkill) {
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/components/skills-typeahead-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ let mockStore = {
};

let mockListService = {
contains(queriedSkill) {
includes(queriedSkill) {
return queriedSkill === skills[1];
},
find(queriedSkill) {
Expand Down
13 changes: 13 additions & 0 deletions tests/pages/project/tasks/new.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import {
collection,
create,
fillable,
text,
visitable
} from 'ember-cli-page-object';

import projectMenu from 'code-corps-ember/tests/pages/components/project-menu';
import skillsTypeahead from 'code-corps-ember/tests/pages/components/skills-typeahead';

export default create({
clickPreviewTask: clickable('.preview'),
Expand All @@ -26,5 +28,16 @@ export default create({
scope: '.body-preview'
},

skillsTypeahead,

taskSkillsList: collection({
scope: '.task-skills-list',
itemScope: 'button',
item: {
text: text(),
click: clickable()
}
}),

visit: visitable(':organization/:project/tasks/new')
});
10 changes: 5 additions & 5 deletions tests/unit/utils/records-list-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { module, test } from 'qunit';

const { isEmpty, Object } = Ember;

module('Unit | Utility | skills-list');
module('Unit | Utility | records-list');

let projectSkill = Object.create({
belongsTo(relationshipName) {
Expand Down Expand Up @@ -53,13 +53,13 @@ test('find returns no match correctly', function(assert) {
assert.ok(isEmpty(result));
});

test('contains returns true when there is a match', function(assert) {
test('includes returns true when there is a match', function(assert) {
let projectSkills = [projectSkill];
let result = recordsList.contains(projectSkills, skill);
let result = recordsList.includes(projectSkills, skill);
assert.ok(result);
});

test('contains returns false when there is no match', function(assert) {
let result = recordsList.contains([], skill);
test('includes returns false when there is no match', function(assert) {
let result = recordsList.includes([], skill);
assert.notOk(result);
});

0 comments on commit 16ba87d

Please sign in to comment.