Skip to content
This repository was archived by the owner on Dec 14, 2023. It is now read-only.

Commit ec65be3

Browse files
authored
Add messaging for Dojo anniversary (#257)
* Add messaging for Dojo anniversary * Remove .only, add party popper, yay new year ! * Fix test Add url path to be set, instead of being empty add a bit of something to make it look less worst * Change translation string * Add latest translations Revert change on using API_SERVER env var
1 parent de66500 commit ec65be3

File tree

10 files changed

+272
-8
lines changed

10 files changed

+272
-8
lines changed

config/dev.env.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ var prodEnv = require('./prod.env')
33

44
module.exports = merge(prodEnv, {
55
NODE_ENV: '"development"',
6-
API_SERVER: '""',
6+
API_SERVER: '""', // False good idea, remove me
77
RECAPTCHA_SITE_KEY: '"6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"',
88
GOOGLE_ANALYTICS_PROPERTY_ID: '"UA-25136319-8"',
99
GOOGLE_MAPS_API_KEY: '"AIzaSyD6WWeWYAVLLO1rmVSLiK-gSQUK8-U6w7w"',

config/prod.env.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module.exports = {
22
NODE_ENV: '"production"',
3-
API_SERVER: '""',
3+
API_SERVER: '""', // False good idea, remove me
44
RECAPTCHA_SITE_KEY: '"6LfVKQgTAAAAAF3wUs0q-vfrtsKdHO1HCAkp6pnY"',
55
GOOGLE_ANALYTICS_PROPERTY_ID: process.env.GIT_BRANCH === 'master' ? '"UA-25136319-2"' : '"UA-25136319-8"',
66
GOOGLE_MAPS_API_KEY: '"AIzaSyCLtwLgQX5wXFJ9bK3hYid5YaW6Qo4bGpc"',

config/test.env.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ var devEnv = require('./dev.env')
33

44
module.exports = merge(devEnv, {
55
NODE_ENV: '"testing"',
6-
API_SERVER: '""',
6+
API_SERVER: '""', // False good idea, remove me
77
GOOGLE_ANALYTICS_PROPERTY_ID: '"UA-25136319-8"',
88
GOOGLE_MAPS_API_KEY: '"AIzaSyD6WWeWYAVLLO1rmVSLiK-gSQUK8-U6w7w"',
99
FORUMS_URL_BASE: '"http://localhost:4567"',
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import moment from 'moment';
2+
import eventPage from '../../pages/events';
3+
import homePage from '../../pages/home';
4+
5+
describe('Homepage comms', () => {
6+
beforeEach(() => {
7+
cy.server();
8+
});
9+
describe('Dojo anniversary', () => {
10+
const showsOn = moment().subtract(11, 'months');
11+
// one day too late
12+
const hidesAfter = moment(showsOn).subtract(1, 'months').subtract(1, 'days');
13+
// 10 months too soon
14+
const hidesBefore = moment().subtract(2, 'months');
15+
it('should show a panel for Dojo anniversary to the champion if its creation date is within the good range', () => {
16+
cy.route('/api/2.0/users/instance', 'fx:parentLoggedIn').as('loggedIn');
17+
cy.route('POST', '/api/2.0/dojos/users', [{ dojoId: 'd1', userPermissions: [{ name: 'ticketing-admin' }], userTypes: ['mentor'] }, { dojoId: 'd2', userPermissions: [{ name: 'dojo-admin' }], userTypes: ['champion'] }]).as('userDojos');
18+
cy.route(/\/api\/3\.0\/dojos\/d1\/events\?query\[status\]=published&query\[afterDate\]=\d+&query\[utcOffset\]=\d+&related=sessions\.tickets$/, { results: [] }).as('dojoEvent1');
19+
cy.route(/\/api\/3\.0\/dojos\/d2\/events\?query\[status\]=published&query\[afterDate\]=\d+&query\[utcOffset\]=\d+&related=sessions\.tickets$/, { results: [] }).as('dojoEvent2');
20+
cy.route('/api/2.0/dojos/d1', { id: 'd1', created: hidesAfter, name: 'dojo1' }).as('dojo1');
21+
cy.route('/api/2.0/dojos/d2', { id: 'd2', created: showsOn, name: 'dojo2' }).as('dojo2');
22+
cy.visit('/home');
23+
cy.wait('@userDojos');
24+
cy.wait('@dojo1');
25+
cy.wait('@dojo2');
26+
cy.get(homePage.comms.anniversaryLink).should('be.visible');
27+
cy.get(homePage.comms.anniversaryLink).should('have.length', 1);
28+
cy.get(homePage.comms.anniversaryLink).invoke('text').should('be.eq', '🎉 dojo2, your Dojo anniversary is approaching! Apply now for your FREE birthday pack to celebrate');
29+
});
30+
it('should show two panels for Dojo anniversary to the champion if both creation dates are within the good range', () => {
31+
cy.route('/api/2.0/users/instance', 'fx:parentLoggedIn').as('loggedIn');
32+
cy.route('POST', '/api/2.0/dojos/users', [{ dojoId: 'd1', userPermissions: [{ name: 'dojo-admin' }], userTypes: ['champion'] }, { dojoId: 'd2', userPermissions: [{ name: 'dojo-admin' }], userTypes: ['champion'] }]).as('userDojos');
33+
cy.route(/\/api\/3\.0\/dojos\/d1\/events\?query\[status\]=published&query\[afterDate\]=\d+&query\[utcOffset\]=\d+&related=sessions\.tickets$/, { results: [] }).as('dojoEvent1');
34+
cy.route(/\/api\/3\.0\/dojos\/d2\/events\?query\[status\]=published&query\[afterDate\]=\d+&query\[utcOffset\]=\d+&related=sessions\.tickets$/, { results: [] }).as('dojoEvent2');
35+
cy.route('/api/2.0/dojos/d1', { id: 'd1', created: showsOn, name: 'dojo1' }).as('dojo1');
36+
cy.route('/api/2.0/dojos/d2', { id: 'd2', created: showsOn, name: 'dojo2' }).as('dojo2');
37+
cy.visit('/home');
38+
cy.wait('@userDojos');
39+
cy.wait('@dojo1');
40+
cy.wait('@dojo2');
41+
cy.get(homePage.comms.anniversaryLink).should('be.visible');
42+
cy.get(homePage.comms.anniversaryLink).should('have.length', 2);
43+
cy.get(homePage.comms.anniversaryLink).first().invoke('text').should('be.eq', '🎉 dojo1, your Dojo anniversary is approaching! Apply now for your FREE birthday pack to celebrate');
44+
cy.get(homePage.comms.anniversaryLink).last().invoke('text').should('be.eq', '🎉 dojo2, your Dojo anniversary is approaching! Apply now for your FREE birthday pack to celebrate');
45+
});
46+
47+
it('should not show a panel for Dojo anniversary if the user is not a dojo-admin', () => {
48+
cy.route('/api/2.0/users/instance', 'fx:parentLoggedIn').as('loggedIn');
49+
cy.route('POST', '/api/2.0/dojos/users', [{ dojoId: 'd1', userPermissions: [], userTypes: ['parent-guardian'] }, { dojoId: 'd2', userPermissions: [{ name: 'ticketing-admin' }], userTypes: ['mentor'] }]).as('userDojos');
50+
cy.route(/\/api\/3\.0\/dojos\/d1\/events\?query\[status\]=published&query\[afterDate\]=\d+&query\[utcOffset\]=\d+&related=sessions\.tickets$/, { results: [] }).as('dojoEvent1');
51+
cy.route(/\/api\/3\.0\/dojos\/d2\/events\?query\[status\]=published&query\[afterDate\]=\d+&query\[utcOffset\]=\d+&related=sessions\.tickets$/, { results: [] }).as('dojoEvent2');
52+
cy.route('/api/2.0/dojos/d1', { id: 'd1', created: showsOn, name: 'dojo1' }).as('dojo1');
53+
cy.route('/api/2.0/dojos/d2', { id: 'd2', created: showsOn, name: 'dojo2' }).as('dojo2');
54+
cy.visit('/home');
55+
cy.wait('@userDojos');
56+
cy.wait('@dojo1');
57+
cy.wait('@dojo2');
58+
cy.get(homePage.comms.anniversaryLink).should('not.be.visible');
59+
});
60+
61+
it('should not show a panel for Dojo anniversary if date is not in the good range', () => {
62+
cy.route('/api/2.0/users/instance', 'fx:parentLoggedIn').as('loggedIn');
63+
cy.route('POST', '/api/2.0/dojos/users', [{ dojoId: 'd1', userPermissions: [{ name: 'dojo-admin' }], userTypes: ['champion'] }, { dojoId: 'd2', userPermissions: [{ name: 'dojo-admin' }], userTypes: ['champion'] }]).as('userDojos');
64+
cy.route(/\/api\/3\.0\/dojos\/d1\/events\?query\[status\]=published&query\[afterDate\]=\d+&query\[utcOffset\]=\d+&related=sessions\.tickets$/, { results: [] }).as('dojoEvent1');
65+
cy.route(/\/api\/3\.0\/dojos\/d2\/events\?query\[status\]=published&query\[afterDate\]=\d+&query\[utcOffset\]=\d+&related=sessions\.tickets$/, { results: [] }).as('dojoEvent2');
66+
cy.route('/api/2.0/dojos/d1', { id: 'd1', created: hidesAfter, name: 'dojo1' }).as('dojo1');
67+
cy.route('/api/2.0/dojos/d2', { id: 'd2', created: hidesBefore, name: 'dojo2' }).as('dojo2');
68+
cy.visit('/home');
69+
cy.wait('@userDojos');
70+
cy.wait('@dojo1');
71+
cy.wait('@dojo2');
72+
cy.get(homePage.comms.anniversaryLink).should('not.be.visible');
73+
});
74+
});
75+
});

cypress/pages/home.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,7 @@ export default {
2121
statsYouthGenderChart: '.cd-dashboard-stats__circle',
2222
statsUseZenMessage: '.cd-dashboard-stats p',
2323
statsYouthGirlsHint: '.cd-dashboard-stats__chart-pie-hint',
24+
comms: {
25+
anniversaryLink: '.cd-dashboard__anniversary',
26+
},
2427
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"dependencies": {
2323
"@coderdojo/cd-common": "1.1.11",
2424
"bootstrap": "^3.4.1",
25-
"cp-translations": "1.0.130",
25+
"cp-translations": "1.0.133",
2626
"font-awesome": "^4.7.0",
2727
"handlebars": "^4.1.0",
2828
"js-cookie": "^2.1.4",
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<template>
2+
<div class="cd-dashboard__anniversaries">
3+
<div v-for="dojo in filteredDojos" class="cd-dashboard__anniversary">
4+
<span class="cd-dashboard__anniversary-popper">🎉</span>
5+
<a :href="formUrl(dojo)">{{ $t('{dojoName}, your Dojo anniversary is approaching! Apply now for your FREE birthday pack to celebrate', { dojoName: dojo.name }) }}</a>
6+
</div>
7+
</div>
8+
</template>
9+
10+
<script>
11+
import moment from 'moment';
12+
13+
export default {
14+
name: 'cd-dashboard-dojo-anniversary',
15+
props: ['dojos', 'dojoAdmins'],
16+
data() {
17+
return {
18+
filteredDojos: [],
19+
};
20+
},
21+
computed: {
22+
baseUrl() {
23+
return `${window.location.protocol}//${window.location.hostname}`;
24+
},
25+
},
26+
methods: {
27+
formUrl(dojo) {
28+
const url = `${this.baseUrl}/dojos/${dojo.urlSlug}`;
29+
return `https://docs.google.com/forms/d/e/1FAIpQLScNDxfs7MP4aOA9f8iZPTuNl6NVO2RHpIch5VGwUDiupaGOxA/viewform?entry.803640143=${dojo.name}&entry.2104926148=${url}`;
30+
},
31+
hasAnniversary(dojo) {
32+
const now = moment();
33+
const referenceDate = moment(dojo.created);
34+
referenceDate.year(now.year());
35+
const timeToAnniversary = now.diff(referenceDate, 'months');
36+
// Note: this won't be exact due to the user's tz and the creation date being in IST
37+
return timeToAnniversary >= -2 && timeToAnniversary <= 0 && now <= referenceDate;
38+
},
39+
championOfDojos(dojos, dojoAdmins) {
40+
return dojoAdmins.reduce((acc, membership) => {
41+
if (dojos[membership.dojoId]) acc.push(dojos[membership.dojoId]);
42+
return acc;
43+
}, []);
44+
},
45+
isOld(dojo) {
46+
const difference = moment(dojo.created).diff(moment(), 'month');
47+
return difference <= -10;
48+
},
49+
dojosWithUpcomingAnniversary(dojos) {
50+
// Note: that could also be filtered with a modulo of 12 months
51+
return dojos.filter(dojo => this.hasAnniversary(dojo) && this.isOld(dojo));
52+
},
53+
},
54+
created() {
55+
const isChampionOfDojos = this.championOfDojos(this.dojos, this.dojoAdmins);
56+
this.filteredDojos = this.dojosWithUpcomingAnniversary(isChampionOfDojos);
57+
},
58+
};
59+
</script>
60+
61+
<style scoped lang="less">
62+
@import "~@coderdojo/cd-common/common/_colors";
63+
@import "../common/variables";
64+
65+
.cd-dashboard{
66+
&__anniversary {
67+
text-align: center;
68+
margin-bottom: @margin;
69+
a {
70+
color: @cd-white;
71+
text-decoration: underline;
72+
font-size: @font-size-medium;
73+
i {
74+
margin-right: 8px;
75+
}
76+
}
77+
&-popper {
78+
color: @cd-orange;
79+
font-size: 1.5em;
80+
}
81+
}
82+
}
83+
</style>

src/dashboard/cd-dashboard-events.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
<div v-if="usersDojos && dojoAdmins.length === 1 && hasDojos && dojoAge(firstDojo, 'weeks') < 2">
1313
<dashboard-admin-survey :dojo-name="firstDojo.name"></dashboard-admin-survey>
1414
</div>
15+
<div v-if="usersDojos && dojoAdmins.length > 0 && hasDojos">
16+
<dashboard-dojo-anniversary :dojos="dojos" :dojo-admins="dojoAdmins"></dashboard-dojo-anniversary>
17+
</div>
1518
<div v-if="hasRequests">
1619
<dashboard-pending-volunteering :user-is-new="userIsNew" :requests-to-join="loggedInUser.joinRequests"></dashboard-pending-volunteering>
1720
</div>
@@ -30,6 +33,7 @@
3033
import DashboardHeader from '@/dashboard/cd-dashboard-header';
3134
import DashboardCreateEvent from '@/dashboard/events/cd-dashboard-create-event';
3235
import DashboardAdminSurvey from '@/dashboard/cd-dashboard-admin-survey';
36+
import DashboardDojoAnniversary from '@/dashboard/cd-dashboard-dojo-anniversary';
3337
import DashboardCta from '@/dashboard/cd-dashboard-cta';
3438
import DashboardPendingVolunteering from '@/dashboard/cd-dashboard-pending-volunteering';
3539
import UpcomingEvent from './events/cd-dashboard-upcoming-event';
@@ -41,6 +45,7 @@
4145
DashboardHeader,
4246
DashboardCreateEvent,
4347
DashboardAdminSurvey,
48+
DashboardDojoAnniversary,
4449
DashboardCta,
4550
DashboardPendingVolunteering,
4651
},
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import vueUnitHelper from 'vue-unit-helper';
2+
import moment from 'moment';
3+
import DashboardDojoAnniversary from '!!vue-loader?inject!@/dashboard/cd-dashboard-dojo-anniversary';
4+
5+
describe('Dashboard dojo anniversary component', () => {
6+
let sandbox;
7+
let vm;
8+
let clock;
9+
10+
beforeEach(() => {
11+
sandbox = sinon.sandbox.create();
12+
vm = vueUnitHelper(DashboardDojoAnniversary({
13+
}));
14+
});
15+
16+
afterEach(() => {
17+
sandbox.restore();
18+
clock.restore();
19+
});
20+
21+
22+
describe('methods', () => {
23+
describe('hasAnniversary', () => {
24+
it('should return true if the creation date is less than 2 months from now', () => {
25+
clock = sinon.useFakeTimers(new Date(2018, 3, 24, 0, 0, 0, 0));
26+
const created = moment();
27+
created.add(1, 'months');
28+
expect(vm.hasAnniversary({ created })).to.be.true;
29+
});
30+
it('should return false if the creation date is more than 2 months from now', () => {
31+
clock = sinon.useFakeTimers(new Date(2018, 3, 24, 0, 0, 0, 0));
32+
const created = moment();
33+
created.add(3, 'months');
34+
expect(vm.hasAnniversary({ created })).to.be.false;
35+
});
36+
it('should return false if the creation date is past', () => {
37+
clock = sinon.useFakeTimers(new Date(2018, 3, 24, 0, 0, 0, 0));
38+
const created = moment();
39+
created.subtract(1, 'days');
40+
expect(vm.hasAnniversary({ created })).to.be.false;
41+
});
42+
});
43+
describe('isOld', () => {
44+
it('should return true if the dojo is at least 10 month old', () => {
45+
clock = sinon.useFakeTimers(new Date(2018, 3, 24, 0, 0, 0, 0));
46+
const created = moment();
47+
created.subtract(11, 'month');
48+
expect(vm.isOld({ created })).to.be.true;
49+
});
50+
it('should return false if the dojo is less than 10 month old', () => {
51+
clock = sinon.useFakeTimers(new Date(2018, 3, 24, 0, 0, 0, 0));
52+
const created = moment();
53+
created.subtract(9, 'month');
54+
expect(vm.isOld({ created })).to.be.false;
55+
});
56+
});
57+
describe('championOfDojos', () => {
58+
it('should filter all dojos where the user is not an admin', () => {
59+
const dojos = { d1: { name: 'd1' }, d2: { name: 'd2' } };
60+
const dojoAdmins = [{ dojoId: 'd2' }, { dojoId: 'd3' }];
61+
const res = vm.championOfDojos(dojos, dojoAdmins);
62+
expect(res).to.eql([{ name: 'd2' }]);
63+
});
64+
});
65+
describe('dojosWithUpcomingAnniversary', () => {
66+
it('should return dojos which are not old enough and not near their anniversary', () => {
67+
vm.hasAnniversary = sandbox.stub().onCall(0).returns(true);
68+
vm.isOld = sandbox.stub().onCall(0).returns(true);
69+
// Dojo 2
70+
vm.hasAnniversary.onCall(1).returns(false);
71+
// Dojo 3
72+
vm.hasAnniversary.onCall(2).returns(true);
73+
vm.isOld.onCall(1).returns(false);
74+
const dojos = [{ id: 'd1' }, { id: 'd2' }, { id: 'd3' }];
75+
expect(vm.dojosWithUpcomingAnniversary(dojos)).to.eql([{ id: 'd1' }]);
76+
});
77+
});
78+
describe('formUrl', () => {
79+
it('should return the url of the form with its variables pre-filled', () => {
80+
vm.baseUrl = 'https://zen.coderdojo.com';
81+
const url = vm.formUrl({ name: 'd1', urlSlug: 'ie/dublin/dublin/dublin-docklands-chq' });
82+
expect(url).to.equal('https://docs.google.com/forms/d/e/1FAIpQLScNDxfs7MP4aOA9f8iZPTuNl6NVO2RHpIch5VGwUDiupaGOxA/viewform?entry.803640143=d1&entry.2104926148=https://zen.coderdojo.com/dojos/ie/dublin/dublin/dublin-docklands-chq');
83+
});
84+
});
85+
});
86+
describe('lifecycle', () => {
87+
describe('created', () => {
88+
it('should prepare the dojos', () => {
89+
vm.championOfDojos = sandbox.stub().returns([{ id: 'd1' }]);
90+
vm.dojosWithUpcomingAnniversary = sandbox.stub().returns([{ id: 'd1' }]);
91+
vm.$lifecycleMethods.created();
92+
expect(vm.championOfDojos).to.have.been.calledOnce;
93+
expect(vm.dojosWithUpcomingAnniversary).to.have.been.calledOnce;
94+
expect(vm.filteredDojos).to.eql([{ id: 'd1' }]);
95+
});
96+
});
97+
});
98+
});

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1988,10 +1988,10 @@ cosmiconfig@^2.1.0, cosmiconfig@^2.1.1:
19881988
parse-json "^2.2.0"
19891989
require-from-string "^1.1.0"
19901990

1991-
1992-
version "1.0.130"
1993-
resolved "https://registry.yarnpkg.com/cp-translations/-/cp-translations-1.0.130.tgz#0b0063ba53e398d56005de30b58bc216f8c03f18"
1994-
integrity sha512-nRdYKAjo5BZXs0iJtyubgdlZEDaD8rt1XPfzFjv6Z2lllszhcoOiW0LyF+VPAJBvbh8rlQceUQXdMlhgAXwf6w==
1991+
1992+
version "1.0.133"
1993+
resolved "https://registry.yarnpkg.com/cp-translations/-/cp-translations-1.0.133.tgz#d89e4d24d9ee4c45eaded9d75614a34f7eaa2219"
1994+
integrity sha512-8VtAZDHLEC9YUw6TBxXOvRgog5xtTwASHEytmzdmywlggYVbgLrRMMnA9x3NCinES5PtYHyUqueeOaqOZgUOFw==
19951995

19961996
crc32-stream@^2.0.0:
19971997
version "2.0.0"

0 commit comments

Comments
 (0)