Skip to content

Commit

Permalink
Added admin UI for project approval
Browse files Browse the repository at this point in the history
Fix ember-modal-dialog deprecation warning
  • Loading branch information
begedin authored and joshsmith committed Dec 8, 2017
1 parent a6cda16 commit c9aadbf
Show file tree
Hide file tree
Showing 17 changed files with 367 additions and 1 deletion.
18 changes: 18 additions & 0 deletions app/controllers/admin/projects/index.js
@@ -0,0 +1,18 @@
import Controller from '@ember/controller';
import { get, set } from '@ember/object';
import { inject as service } from '@ember/service';

export default Controller.extend({
flashMessages: service(),

async approve(project) {
set(project, 'approved', true);
let title = get(project, 'title');
project.save().then(() => {
get(this, 'flashMessages').clearMessages().success(`You approved ${title}.`);
}).catch(() => {
project.rollbackAttributes();
get(this, 'flashMessages').clearMessages().danger(`An error occurred while approving ${title}.`);
});
}
});
1 change: 1 addition & 0 deletions app/router.js
Expand Up @@ -36,6 +36,7 @@ Router.map(function() {
this.route('organization-invites', function() {
this.route('new');
});
this.route('projects', function() {});
});

// GitHub OAuth redirection route
Expand Down
7 changes: 7 additions & 0 deletions app/routes/admin/projects/index.js
@@ -0,0 +1,7 @@
import Route from '@ember/routing/route';

export default Route.extend({
model() {
return this.store.findAll('project');
}
});
5 changes: 5 additions & 0 deletions app/templates/admin.hbs
Expand Up @@ -16,6 +16,11 @@
{{fa-icon "envelope-open-o"}} Organization Invites
{{/link-to}}
</li>
<li>
{{#link-to "admin.projects"}}
{{fa-icon "clipboard"}} Projects
{{/link-to}}
</li>
</ul>
</div>
<div class="admin-main">
Expand Down
11 changes: 11 additions & 0 deletions app/templates/admin/projects.hbs
@@ -0,0 +1,11 @@
<div data-test-page-menu class="page-menu page-menu--horizontal">
<div class="container">
<ul>
<li>
{{link-to "Projects" "admin.projects.index" data-test-index-link}}
</li>
</ul>
</div>
</div>

{{outlet}}
51 changes: 51 additions & 0 deletions app/templates/admin/projects/index.hbs
@@ -0,0 +1,51 @@
<div class="log-rows">
<div data-test-log-row-header class="log-row log-row--header">
<span class="log-cell log-cell--shrink"></span>
<span class="log-cell log-cell--shrink">Project</span>
<span class="log-cell log-cell--shrink">Organization</span>
<span class="log-cell">Status</span>
<span class="log-cell log-cell--shrink">Approval</span>
</div>

{{#each model as |project|}}
<div data-test-log-row class="log-row">
<span data-test-icon class="log-cell log-cell--shrink">
{{#if project.iconThumbUrl}}
{{#link-to 'project' project.organization.slug project.slug}}
<img class="icon icon--small" width="35" height="35" src="{{project.iconThumbUrl}}" />
{{/link-to}}
{{else}}
<div class="icon icon--small"></div>
{{/if}}
</span>
<span data-test-title class="log-cell log-cell--shrink">
{{#link-to 'project' project.organization.slug project.slug}}
{{project.title}}
{{/link-to}}
</span>
<span data-test-organization class="log-cell log-cell--shrink">
{{#link-to 'slugged-route' project.organization.slug}}
{{project.organization.name}}
{{/link-to}}
</span>
<span data-test-approval-status class="log-cell">
{{#if project.isSaving}}
{{fa-icon "ellipsis-h"}} Saving...
{{else if project.approved}}
<span class="log-data--success">
{{fa-icon "check-circle"}} Approved
</span>
{{else if project.approvalRequested}}
{{fa-icon "question-circle"}} Pending approval
{{else}}
{{fa-icon "asterisk"}} Created
{{/if}}
</span>
<span data-test-actions class="log-cell log-cell--shrink">
{{#if (and project.approvalRequested (not project.approved))}}
<button {{action approve project}} class="default small">Approve</button>
{{/if}}
</span>
</div>
{{/each}}
</div>
2 changes: 1 addition & 1 deletion app/templates/components/modal-confirm.hbs
@@ -1,6 +1,6 @@
{{#if showDialog}}
<div class="modal-confirm">
{{#modal-dialog clickOutsideToClose=true close="closeDialog" translucentOverlay=true}}
{{#modal-dialog clickOutsideToClose=true onClose="closeDialog" translucentOverlay=true}}
<div class="dialog-text">
<p>{{dialogText}}</p>
</div>
Expand Down
26 changes: 26 additions & 0 deletions tests/acceptance/admin-github-event-test.js
Expand Up @@ -6,6 +6,32 @@ import moment from 'moment';

moduleForAcceptance('Acceptance | Admin | GitHub Event | Show');

test('The page requires logging in', function(assert) {
assert.expect(1);

let event = server.create('github-event');
page.visit({ id: event.id });

andThen(() => {
assert.equal(currentRouteName(), 'login', 'Got redirected to login');
});
});

test('The page requires user to be admin', function(assert) {
assert.expect(2);

let user = server.create('user', { admin: false, id: 1 });
authenticateSession(this.application, { user_id: user.id });

let event = server.create('github-event');
page.visit({ id: event.id });

andThen(() => {
assert.equal(page.flashErrors().count, 1, 'Flash error was rendered');
assert.equal(currentRouteName(), 'projects-list', 'Got redirected');
});
});

test('Displays all the logged events', function(assert) {
assert.expect(12);

Expand Down
24 changes: 24 additions & 0 deletions tests/acceptance/admin-github-events-test.js
Expand Up @@ -5,6 +5,30 @@ import page from '../pages/admin/github-events/index';

moduleForAcceptance('Acceptance | Admin | GitHub Events | Index');

test('The page requires logging in', function(assert) {
assert.expect(1);

page.visit();

andThen(() => {
assert.equal(currentRouteName(), 'login', 'Got redirected to login');
});
});

test('The page requires user to be admin', function(assert) {
assert.expect(2);

let user = server.create('user', { admin: false, id: 1 });
authenticateSession(this.application, { user_id: user.id });

page.visit();

andThen(() => {
assert.equal(page.flashErrors().count, 1, 'Flash error was rendered');
assert.equal(currentRouteName(), 'projects-list', 'Got redirected');
});
});

test('Displays all the logged events', function(assert) {
assert.expect(16);

Expand Down
24 changes: 24 additions & 0 deletions tests/acceptance/admin-organization-invite-new-test.js
Expand Up @@ -7,6 +7,30 @@ import page from 'code-corps-ember/tests/pages/admin/organization-invites/new';

moduleForAcceptance('Acceptance | Admin | Organization Invites | New');

test('The page requires logging in', function(assert) {
assert.expect(1);

page.visit();

andThen(() => {
assert.equal(currentRouteName(), 'login', 'Got redirected to login');
});
});

test('The page requires user to be admin', function(assert) {
assert.expect(2);

let user = server.create('user', { admin: false, id: 1 });
authenticateSession(this.application, { user_id: user.id });

page.visit();

andThen(() => {
assert.equal(page.flashErrors().count, 1, 'Flash error was rendered');
assert.equal(currentRouteName(), 'projects-list', 'Got redirected');
});
});

test('An admin can create and send an organization invite', function(assert) {
assert.expect(3);

Expand Down
24 changes: 24 additions & 0 deletions tests/acceptance/admin-organization-invites-index-test.js
Expand Up @@ -5,6 +5,30 @@ import page from 'code-corps-ember/tests/pages/admin/organization-invites/index'

moduleForAcceptance('Acceptance | Admin | Organization Invites | Index');

test('The page requires logging in', function(assert) {
assert.expect(1);

page.visit();

andThen(() => {
assert.equal(currentRouteName(), 'login', 'Got redirected to login');
});
});

test('The page requires user to be admin', function(assert) {
assert.expect(2);

let user = server.create('user', { admin: false, id: 1 });
authenticateSession(this.application, { user_id: user.id });

page.visit();

andThen(() => {
assert.equal(page.flashErrors().count, 1, 'Flash error was rendered');
assert.equal(currentRouteName(), 'projects-list', 'Got redirected');
});
});

test('Displays all the invites', function(assert) {
assert.expect(21);

Expand Down
124 changes: 124 additions & 0 deletions tests/acceptance/admin-projects-test.js
@@ -0,0 +1,124 @@
import { test } from 'qunit';
import moduleForAcceptance from 'code-corps-ember/tests/helpers/module-for-acceptance';
import { authenticateSession } from 'code-corps-ember/tests/helpers/ember-simple-auth';
import page from 'code-corps-ember/tests/pages/admin/projects/index';
import Mirage from 'ember-cli-mirage';

moduleForAcceptance('Acceptance | Admin | Projects');

test('The page requires logging in', function(assert) {
assert.expect(1);

page.visit();

andThen(() => {
assert.equal(currentRouteName(), 'login', 'Got redirected to login');
});
});

test('The page requires user to be admin', function(assert) {
assert.expect(2);

let user = server.create('user', { admin: false, id: 1 });
authenticateSession(this.application, { user_id: user.id });

page.visit();

andThen(() => {
assert.equal(page.flashErrors().count, 1, 'Flash error was rendered');
assert.equal(currentRouteName(), 'projects-list', 'Got redirected');
});
});

test('An admin can view a list of projects', function(assert) {
assert.expect(15);

let user = server.create('user', { admin: true, id: 1 });

let unapprovedProject = server.create('project', { approvalRequested: false, approved: false });
let projectPendingApproval = server.create('project', { approvalRequested: true, approved: false });
let approvedProject = server.create('project', { approvalRequested: true, approved: true });

[unapprovedProject, projectPendingApproval, approvedProject]
.map(({ organization }) => organization)
.forEach((organization) => server.create('slugged-route', { organization }));

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

page.visit();

andThen(() => {
assert.equal(currentURL(), '/admin/projects');
assert.equal(currentRouteName(), 'admin.projects.index');

assert.equal(page.items().count, 3, 'There are 3 rows.');

[unapprovedProject, projectPendingApproval, approvedProject].forEach((project, index) => {
assert.equal(page.items(index).title.text, project.title, 'Project title is rendered.');
assert.equal(page.items(index).icon.src, project.iconThumbUrl, 'Project icon is rendered.');
});

assert.equal(page.items(0).approvalStatus.text, 'Created', 'Correct status is rendered for newly created project.');
assert.notOk(page.items(0).actions.approve.isVisible, 'Approve button is not rendered for newly created project.');

assert.equal(page.items(1).approvalStatus.text, 'Pending approval', 'Correct status is rendered for project pending approval.');
assert.ok(page.items(1).actions.approve.isVisible, 'Approve button is rendered for project pending approval.');

assert.equal(page.items(2).approvalStatus.text, 'Approved', 'Correct status is rendered for approved project.');
assert.notOk(page.items(2).actions.approve.isVisible, 'Approve button is not rendered for approved project');
});
});

test('An admin can approve a project', function(assert) {
assert.expect(2);

let user = server.create('user', { admin: true, id: 1 });
let unapprovedProject = server.create('project', { approved: false, approvalRequested: true });

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

page.visit();

andThen(() => {
page.items(0).actions.approve.click();
});

andThen(() => {
assert.ok(unapprovedProject.approved, 'Project is approved.');
assert.equal(page.items(0).approvalStatus.text, 'Approved', 'Project status is rendered correctly.');
});
});

test('A flash error renders when project approval fails', function(assert) {
assert.expect(1);

let user = server.create('user', { admin: true, id: 1 });
let project = server.create('project', { approved: false, approvalRequested: true });

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

page.visit();

let done = assert.async();

server.patch(`/projects/${project.id}`, function() {
done();
return new Mirage.Response(500, {}, {
errors: [
{
title: '500 Internal Server Error',
detail: '500 Internal Server Error',
status: 500
}
]
});
});

andThen(() => {
page.items(0).actions.approve.click();
});

andThen(() => {
assert.equal(page.flashErrors().count, 1, 'FLash error is rendered.');
});
});
4 changes: 4 additions & 0 deletions tests/pages/admin/github-events/index.js
Expand Up @@ -3,6 +3,10 @@ import { collection, create, hasClass, visitable } from 'ember-cli-page-object';
export default create({
visit: visitable('/admin/github/events'),

flashErrors: collection({
itemScope: '.flash > div.alert-danger'
}),

logItems: collection({
itemScope: '[data-test-log-row]',
item: {
Expand Down
4 changes: 4 additions & 0 deletions tests/pages/admin/github-events/show.js
Expand Up @@ -3,6 +3,10 @@ import { collection, create, visitable } from 'ember-cli-page-object';
export default create({
visit: visitable('/admin/github/events/:id'),

flashErrors: collection({
itemScope: '.flash > div.alert-danger'
}),

error: {
scope: '[data-test-error]'
},
Expand Down

0 comments on commit c9aadbf

Please sign in to comment.