From dcbb7e9686d41aa6ad3bfdabc632daa691448398 Mon Sep 17 00:00:00 2001
From: wenincode
Date: Sun, 27 Nov 2016 12:14:49 -0700
Subject: [PATCH] Create volunteer-headshot component
---
app/components/thank-you-container.js | 76 ++++++++++++++++++-
app/components/volunteer-headshot.js | 74 ++++++++++++++++++
app/models/organization-membership.js | 19 +++++
app/styles/app.scss | 1 +
app/styles/components/volunteer-headshot.scss | 22 ++++++
app/styles/templates/project/thank-you.scss | 18 ++++-
.../components/thank-you-container.hbs | 5 ++
.../components/volunteer-headshot.hbs | 3 +
.../components/thank-you-container-test.js | 39 +++++++++-
.../components/volunteer-headshot-test.js | 63 +++++++++++++++
tests/pages/components/thank-you-container.js | 7 +-
tests/pages/components/volunteer-headshot.js | 13 ++++
12 files changed, 332 insertions(+), 8 deletions(-)
create mode 100644 app/components/volunteer-headshot.js
create mode 100644 app/models/organization-membership.js
create mode 100644 app/styles/components/volunteer-headshot.scss
create mode 100644 app/templates/components/volunteer-headshot.hbs
create mode 100644 tests/integration/components/volunteer-headshot-test.js
create mode 100644 tests/pages/components/volunteer-headshot.js
diff --git a/app/components/thank-you-container.js b/app/components/thank-you-container.js
index 192171417..ebba69121 100644
--- a/app/components/thank-you-container.js
+++ b/app/components/thank-you-container.js
@@ -2,11 +2,85 @@ import Ember from 'ember';
const {
Component,
+ computed,
+ computed: { filter, mapBy },
+ get,
inject: { service }
} = Ember;
+const MAX_VOLUNTEERS = 12;
+
+/**
+ The `thank-you-container` component presents the main content for the
+ `thank-you` donations page. This includes an icon, thank you message and
+ a list of contributors.
+
+ ## Default Usage
+
+ ```handlebars
+ {{thank-you-container project=project}}
+ ```
+
+ @class thank-you-container
+ @module Component
+ @extends Ember.Component
+ */
export default Component.extend({
classNames: ['thank-you-container'],
- onboarding: service()
+ onboarding: service(),
+
+ /**
+ A filter for the project's approved users
+
+ @property approvedProjectUsers
+ @type Ember.Array
+ */
+ approvedProjectUsers: filter('project.projectUsers', function(projectUser) {
+ return get(projectUser, 'role') !== 'pending';
+ }),
+
+ /**
+ A computed array of approved members
+
+ @property approvedUsers
+ @type Ember.Array
+ */
+ approvedUsers: mapBy('approvedProjectUsers', 'user'),
+
+ /**
+ Retuns a subset of at most `MAX_VOLUNTEERS` members from the `approvedUsers` array.
+
+ @property volunteers
+ @type Ember.Array
+ */
+ volunteers: computed('approvedUsers', function() {
+ let approvedUsers = get(this, 'approvedUsers');
+
+ if (approvedUsers.length > MAX_VOLUNTEERS) {
+ approvedUsers = this.randomSubset(approvedUsers, MAX_VOLUNTEERS);
+ }
+
+ return approvedUsers;
+ }),
+
+ /*
+ * Source: http://stackoverflow.com/a/11935263/1787262
+ * Username: Tim Down
+ * Date: December 3rd, 2016
+ */
+ randomSubset(arr, size) {
+ let shuffled = arr.slice(0);
+ let i = arr.length;
+ let temp, index;
+
+ while (i--) {
+ index = Math.floor((i + 1) * Math.random());
+ temp = shuffled[index];
+ shuffled[index] = shuffled[i];
+ shuffled[i] = temp;
+ }
+
+ return shuffled.slice(0, size);
+ }
});
diff --git a/app/components/volunteer-headshot.js b/app/components/volunteer-headshot.js
new file mode 100644
index 000000000..9a41a0fc9
--- /dev/null
+++ b/app/components/volunteer-headshot.js
@@ -0,0 +1,74 @@
+import Ember from 'ember';
+
+const {
+ Component,
+ computed,
+ get,
+ isPresent
+} = Ember;
+
+/**
+ The `volunteer-headshot` component presents a thumbnail of a volunteer, their
+ name and randomly selects one of their roles.
+
+ ## Default Usage
+
+ ```handlebars
+ {{volunteer-headshot volunteer=user}}
+ ```
+
+ @class volunteer-headshot
+ @module Component
+ @extends Ember.Component
+ */
+export default Component.extend({
+ attributeBindings: ['data-test-selector'],
+ classNames: ['volunteer-headshot'],
+
+ /**
+ A computed alias of the volunteer's user roles.
+
+ @property userRoles
+ @type Ember.Array
+ */
+ userRoles: computed.alias('volunteer.userRoles'),
+
+ /**
+ A randomly selected role from the `userRoles` property.
+
+ @property userRole
+ @type Ember.Model
+ */
+ userRole: computed('userRoles', function() {
+ let userRoles = get(this, 'userRoles');
+
+ if (isPresent(userRoles)) {
+ let randomIndex = Math.floor(Math.random() * get(userRoles, 'length'));
+
+ return userRoles.objectAt(randomIndex);
+ }
+ }),
+
+ /**
+ Returns the volunteer's name. If the `name` property is not defined, it
+ computes a name from the volunteer's `firstName` & `lastName`. If neither
+ are defined it returns the volunteer's username.
+
+ @property volunteerName
+ @type String
+ */
+ volunteerName: computed('volunteer.{name,firstName,lastName}', function() {
+ let name = get(this, 'volunteer.name');
+ let firstName = get(this, 'volunteer.firstName');
+ let lastName = get(this, 'volunteer.lastName');
+ let userName = get(this, 'volunteer.userName');
+
+ if (isPresent(name)) {
+ return name;
+ } else if (isPresent(firstName) && isPresent(lastName)) {
+ return `${firstName} ${lastName}`;
+ } else {
+ return userName;
+ }
+ })
+});
diff --git a/app/models/organization-membership.js b/app/models/organization-membership.js
new file mode 100644
index 000000000..71967c8c4
--- /dev/null
+++ b/app/models/organization-membership.js
@@ -0,0 +1,19 @@
+import Model from 'ember-data/model';
+import attr from 'ember-data/attr';
+import { belongsTo } from 'ember-data/relationships';
+import Ember from 'ember';
+
+const { computed } = Ember;
+
+export default Model.extend({
+ role: attr(),
+
+ member: belongsTo('user', { async: true }),
+ organization: belongsTo('organization', { async: true }),
+
+ isAdmin: computed.equal('role', 'admin'),
+ isContributor: computed.equal('role', 'contributor'),
+ isNotPending: computed.not('isPending'),
+ isOwner: computed.equal('role', 'owner'),
+ isPending: computed.equal('role', 'pending')
+});
diff --git a/app/styles/app.scss b/app/styles/app.scss
index 2f7839700..6e5d4e1b6 100644
--- a/app/styles/app.scss
+++ b/app/styles/app.scss
@@ -176,6 +176,7 @@
@import "components/user-projects-list";
@import "components/user-sidebar";
+@import "components/volunteer-headshot";
// used from task/new.hbs
@import "components/project-skills-list";
diff --git a/app/styles/components/volunteer-headshot.scss b/app/styles/components/volunteer-headshot.scss
new file mode 100644
index 000000000..c0f6c0dad
--- /dev/null
+++ b/app/styles/components/volunteer-headshot.scss
@@ -0,0 +1,22 @@
+.volunteer-headshot {
+ @include omega(6n);
+ @include span-columns(2);
+
+ display: inline-block;
+ margin-bottom: 1em;
+
+ &__name {
+ font-weight: 600;
+ margin: 0;
+ }
+
+ &__role {
+ font-size: $body-font-size-small;
+ color: $text--light;
+ margin-top: 0;
+ }
+
+ img {
+ border-radius: 4px;
+ }
+}
diff --git a/app/styles/templates/project/thank-you.scss b/app/styles/templates/project/thank-you.scss
index 8c1ad068f..b8176881e 100644
--- a/app/styles/templates/project/thank-you.scss
+++ b/app/styles/templates/project/thank-you.scss
@@ -9,17 +9,27 @@
}
}
+ &__contributors {
+ @include span-columns(12);
+
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: center;
+ margin: 1.4em 0;
+ }
+
h3 {
font-size: $header-font-size-large;
font-weight: 600;
margin: 10px 0;
}
- p {
- margin-bottom: 1.4em;
- }
-
&__message {
color: $text--light;
+
+ p {
+ margin-bottom: 1.4em;
+ }
}
}
diff --git a/app/templates/components/thank-you-container.hbs b/app/templates/components/thank-you-container.hbs
index 841d6e9b3..509850fac 100644
--- a/app/templates/components/thank-you-container.hbs
+++ b/app/templates/components/thank-you-container.hbs
@@ -14,3 +14,8 @@
{{/if}}
+
+ {{#each volunteers as |volunteer|}}
+ {{volunteer-headshot volunteer=volunteer data-test-selector="volunteer headshot"}}
+ {{/each}}
+
diff --git a/app/templates/components/volunteer-headshot.hbs b/app/templates/components/volunteer-headshot.hbs
new file mode 100644
index 000000000..5b9b7f60b
--- /dev/null
+++ b/app/templates/components/volunteer-headshot.hbs
@@ -0,0 +1,3 @@
+
+{{volunteerName}}
+{{userRole.role.name}}
diff --git a/tests/integration/components/thank-you-container-test.js b/tests/integration/components/thank-you-container-test.js
index b61214b7d..c91801dcb 100644
--- a/tests/integration/components/thank-you-container-test.js
+++ b/tests/integration/components/thank-you-container-test.js
@@ -1,16 +1,45 @@
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import PageObject from 'ember-cli-page-object';
-import thankYouContainerComponent from '../../pages/components/thank-you-container';
+import thankYouContainerComponent from 'code-corps-ember/tests/pages/components/thank-you-container';
let page = PageObject.create(thankYouContainerComponent);
+const members = [
+ 'Rudolph',
+ 'Charlie Brown',
+ 'Yukon Cornelius',
+ 'Frosty the Snowman'
+];
+
+function generateProjectUsers(size) {
+ let projectUsers = [];
+
+ for (let i = 0; i < size; i++) {
+ projectUsers.push({
+ role: 'contributor',
+ user: {
+ name: members[Math.floor(Math.random() * members.length)],
+ photoThumbUrl: '/assets/images/icons/test.png',
+ userRoles: [
+ {
+ name: 'Contributor'
+ }
+ ]
+ }
+ });
+ }
+
+ return projectUsers;
+}
+
moduleForComponent('thank-you-container', 'Integration | Component | thank-you container', {
integration: true,
beforeEach() {
this.set('project', {
id: 42,
- title: 'A Test Project'
+ title: 'A Test Project',
+ projectUsers: generateProjectUsers(14)
});
page.setContext(this);
@@ -23,3 +52,9 @@ test('it renders the thank you text', function(assert) {
assert.equal(page.thankYouText, `From all the volunteers on the ${this.get('project.title')} team.`);
});
+
+test('it renders a subset of 12 volunteers', function(assert) {
+ assert.expect(1);
+
+ assert.equal(page.volunteers().count, 12);
+});
diff --git a/tests/integration/components/volunteer-headshot-test.js b/tests/integration/components/volunteer-headshot-test.js
new file mode 100644
index 000000000..64dbad580
--- /dev/null
+++ b/tests/integration/components/volunteer-headshot-test.js
@@ -0,0 +1,63 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+import PageObject from 'ember-cli-page-object';
+import volunteerHeadshot from '../../pages/components/volunteer-headshot';
+
+let page = PageObject.create(volunteerHeadshot);
+
+const userRoles = [
+ { role: { name: 'Developer' } },
+ { role: { name: 'Ember Developer' } },
+ { role: { name: 'UX Designer' } },
+ { role: { name: 'Software Engineer' } },
+ { role: { name: 'Project Coordinator' } },
+ { role: { name: 'Designer & Developer' } }
+];
+
+moduleForComponent('volunteer-headshot', 'Integration | Component | volunteer headshot', {
+ integration: true,
+ beforeEach() {
+ this.set('user', {
+ name: 'Test User',
+ photoThumbUrl: '/assets/images/icons/test.png',
+ userRoles
+ });
+ page.setContext(this);
+ page.render(hbs`{{volunteer-headshot volunteer=user}}`);
+ }
+});
+
+test('it renders the volunteer\'s name', function(assert) {
+ assert.expect(1);
+ assert.equal(page.name, this.get('user.name'));
+});
+
+test('it computes the name if it is not present', function(assert) {
+ let firstName = 'Split';
+ let lastName = 'Name';
+
+ this.set('user.name', null);
+ this.set('user.firstName', firstName);
+ this.set('user.lastName', lastName);
+
+ assert.equal(page.name, `${firstName} ${lastName}`);
+});
+
+test('it randomly selects one of the available roles', function(assert) {
+ assert.expect(1);
+
+ let roles = userRoles.map((userRole) => {
+ return userRole.role.name;
+ });
+ assert.ok(roles.includes(page.role));
+});
+
+test('it sets the image alt text', function(assert) {
+ assert.expect(1);
+ assert.equal(page.image.alt, `${this.get('user.name')}`);
+});
+
+test('it sets the image src', function(assert) {
+ assert.expect(1);
+ assert.equal(page.image.src, this.get('user.photoThumbUrl'));
+});
diff --git a/tests/pages/components/thank-you-container.js b/tests/pages/components/thank-you-container.js
index 1ec8b32c6..62001a131 100644
--- a/tests/pages/components/thank-you-container.js
+++ b/tests/pages/components/thank-you-container.js
@@ -1,5 +1,6 @@
import {
clickable,
+ collection,
text
} from 'ember-cli-page-object';
@@ -12,5 +13,9 @@ export default {
clickLink: clickable('a'),
- thankYouText: text('[data-test-selector="thank you message"]')
+ thankYouText: text('[data-test-selector="thank you message"]'),
+
+ volunteers: collection({
+ scope: '[data-test-selector="volunteer headshot"]'
+ })
};
diff --git a/tests/pages/components/volunteer-headshot.js b/tests/pages/components/volunteer-headshot.js
new file mode 100644
index 000000000..d7d99aeaf
--- /dev/null
+++ b/tests/pages/components/volunteer-headshot.js
@@ -0,0 +1,13 @@
+import { attribute, text } from 'ember-cli-page-object';
+
+export default {
+ image: {
+ scope: 'img',
+
+ alt: attribute('alt'),
+ src: attribute('src')
+ },
+
+ name: text('[data-test-selector="volunteer name"]'),
+ role: text('[data-test-selector="volunteer role"]')
+};