Skip to content

Commit

Permalink
Merge branch 'release/0.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
abought committed Jan 31, 2017
2 parents 76d694e + 94e4fec commit 58cfcd8
Show file tree
Hide file tree
Showing 13 changed files with 6,820 additions and 67 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lts/boron
14 changes: 8 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
---
dist: trusty
language: node_js
node_js:
- "4.6.0"

sudo: false

cache:
yarn: true
directories:
- node_modules
- $HOME/.cache # includes bowers cache

before_install:
- cp .env-travis .env
- export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH
- npm config set spin false
- npm install -g bower
- bower --version

install:
- npm install
- bower install
- curl -o- -L https://yarnpkg.com/install.sh | bash
- export PATH=$HOME/.yarn/bin:$PATH
- yarn install --pure-lockfile
- ./node_modules/bower/bin/bower install --config.interactive=false

script:
- npm run check-style && npm test
- yarn test
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ You will need the following things properly installed on your computer.

* [Git](http://git-scm.com/)
* [Node.js](http://nodejs.org/) (with NPM)
* [Yarn](https://yarnpkg.com/en/docs/install) (a package manager like npm)
* [Bower](http://bower.io/)
* [Ember CLI](http://www.ember-cli.com/)
* [PhantomJS](http://phantomjs.org/)
Expand All @@ -32,7 +33,7 @@ your project.

* `git clone <repository-url>` this repository
* change into the new directory
* `npm install`
* `yarn install --pure-lockfile`
* `bower install`

### Install submodule dependencies
Expand Down Expand Up @@ -67,7 +68,7 @@ WOWZA_ASP='{}'

First:
* make sure jamdb is running, see: https://github.com/CenterForOpenScience/jamdb
* then: `npm run bootstrap`
* then: `yarn run bootstrap`

This:
- Makes the _experimenter_ namespace in jamdb.
Expand All @@ -86,6 +87,17 @@ Then:
* `ember server`
* Visit your app at [http://localhost:4200](http://localhost:4200).

### Adding dependencies on other packages
Sometimes, you will want to install an additional third-party package. In place of npm, this project uses `yarn`.
Most of the [commands](https://yarnpkg.com/en/docs/managing-dependencies) are the same, but this alternative tool
provides a way for two developers to guarantee they are using the same versions of underlying code. (by running
`yarn install --pure-lockfile`) This can help avoid a situation where things break unexpectedly when run on a different
computer.

Whenever you choose to update your dependencies (`yarn add x` or `yarn install`), make sure that code still runs, then
be sure to [commit](https://yarnpkg.com/en/docs/yarn-lock) the modified `yarn.lock` file, which represents the "current
known working state" for your app.

### Code Generators

Make use of the many generators for code, try `ember help generate` for more details
Expand Down
87 changes: 41 additions & 46 deletions app/components/participant-creator/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,6 @@ import { validator, buildValidations } from 'ember-cp-validations';
* @submodule components
*/

// h/t: http://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript
function makeId(len) {
let text = '';
const possible = '0123456789';

for (let i = 0; i < len; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}

const Validations = buildValidations({
batchSize: validator('number', {
gt: 0,
Expand All @@ -30,6 +19,8 @@ const Validations = buildValidations({
})
});

const worker = new Worker('assets/workers/account-generator.js');

/**
* Participant creator page in component form.
*
Expand Down Expand Up @@ -59,20 +50,6 @@ export default Ember.Component.extend(Validations, {
this.set('extra', Ember.A());
this.set('createdAccounts', []);
},
/**
* Generate an array of ID strings
* @param batchSize
* @param tag
* @returns {String[]}
* @private
*/
_generate(batchSize, tag) {
var ret = [];
for (let i = 0; i < batchSize; i++) {
ret.push(`${makeId(10)}${tag ? `-${tag}` : ''}`);
}
return ret;
},

/**
* @method _sendBulkRequest Send a single (bulk) ajax request and return a promise
Expand Down Expand Up @@ -158,42 +135,60 @@ export default Ember.Component.extend(Validations, {

var tag = this.get('tag');
batchSize = parseInt(batchSize) || 0;
var accountIDs = this._generate(batchSize, tag);

var extra = {};
extra.studyId = studyId;
this.get('extra').forEach(item => {
extra[item.key] = item.value;
});
const accounts = accountIDs.map(id => ({id, password: studyId, extra}));

// TODO: Use the server response errors field to identify any IDs that might already be in use:
// as written, we don't retry to create those. If 3 of 100 requested items fail, we just create 97 items and call it a day.
this.set('currentRecord', 1);
this.set('creating', true);

// This step is very slow because each password has to be bcrypted- on the front end (jamdb implementation detail).
// Do that in a separate run loop so that UI status indicator can render while we wait; otherwise
// rerender blocks until after the server request has been sent.
this.rerender();
Ember.run.next(() => {
this._sendBulkRequest('account', accounts)
.then((res) => {
if (res.length > 0) {
// Store all the records that were successfully created,
// adding them to all records from previous requests while on this page.
// Eg, a combined CSV could be generated with 200 records.
this.get('createdAccounts').push(...res);
// This may sometimes be smaller than batchSize, in the rare event that a single record appears
// in res.errors instead, eg because ID was already in use
this.toast.info(`Successfully created ${res.length} accounts!`);
this.send('downloadCSV');
} else {
// Likely, every ID in this request failed to create for some horrible reason (data.length=0
// and errors.length=batchSize after filtering out spurious null entries)
this.get('toast').error('Could not create new account(s). If this error persists, please contact support.');
}
})
.catch(() => this.get('toast').error('Could not create new accounts. Please try again later.'))
.finally(() => this.set('creating', false));
// Handle the worker callback
worker.onmessage = event => {
const {progress, records} = event.data;

if (progress !== undefined) {
this.set('currentRecord', progress + 1);
return;
}

this._sendBulkRequest('account', records)
.then((res) => {
if (res.length > 0) {
// Store all the records that were successfully created,
// adding them to all records from previous requests while on this page.
// Eg, a combined CSV could be generated with 200 records.
this.get('createdAccounts').push(...res);
// This may sometimes be smaller than batchSize, in the rare event that a single record appears
// in res.errors instead, eg because ID was already in use
this.toast.info(`Successfully created ${res.length} accounts!`);
this.send('downloadCSV');
} else {
// Likely, every ID in this request failed to create for some horrible reason (data.length=0
// and errors.length=batchSize after filtering out spurious null entries)
this.get('toast').error('Could not create new account(s). If this error persists, please contact support.');
}
})
.catch(() => this.get('toast').error('Could not create new accounts. Please try again later.'))
.finally(() => this.set('creating', false));
};

// Call the worker
worker.postMessage({
batchSize,
studyId,
tag,
extra
});
});
},
addExtraField() {
Expand Down
4 changes: 3 additions & 1 deletion app/components/participant-creator/template.hbs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<div class="row participant-creator">
<div class="{{if creating 'block-ui' 'block-ui-hidden'}}">
<span class="block-ui-message">
Creating new records. This may take several minutes- please wait.
Creating new records. This may take several minutes--please wait.
<br>
Creating record {{currentRecord}} of {{batchSize}}...
</span>
</div>
<div class="col-md-6">
Expand Down
11 changes: 10 additions & 1 deletion app/styles/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,11 @@ body,
}

.main-navigation {
color: white;
}

.main-navigation.active {
color: $background-dark;
background-color: #c0c0c0;
}

.selected > .fa {
Expand Down Expand Up @@ -131,13 +134,19 @@ a.sub-navigation {
color: white;
text-decoration: none;
padding: 12px;
width: 100%;
}

a.sub-navigation:visited {
color: white;
text-decoration: none;
}

a.sub-navigation.active {
color: $background-dark;
background-color: white;
}

a.sub-navigation:hover {
color: orange;
text-decoration: none;
Expand Down
1 change: 1 addition & 0 deletions app/styles/components/participant-creator.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@
margin-right: -50%;
transform: translate(-50%, -50%);
border-radius: 25px;
text-align: center;
}
10 changes: 10 additions & 0 deletions app/templates/application.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,13 @@
{{outlet}}
{{/if}}
</div>

<footer class="footer">
<div class="container">
<div class="row">
<div class="col-md-12 text-center">
<span class="text-center"><a href="https://github.com/CenterForOpenScience/centerforopenscience.org/blob/master/TERMS_OF_USE.md" target="_blank" rel="nofollow noopener">Terms of Use</a></span>
</div>
</div>
</div>
</footer>
84 changes: 84 additions & 0 deletions app/workers/account-generator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/* jshint worker:true */
/* global dcodeIO */

importScripts('bcryptjs/dist/bcrypt.min.js');
const bcrypt = dcodeIO.bcrypt;

const bounds = [
[49, 57], // 1-9
[65, 90], // A-Z
];

const possible = [];

for (let b = 0; b < bounds.length; b++) {
const bound = bounds[b];
const upperBound = bound[1] + 1;

for (let i = bound[0]; i < upperBound; i++) {
possible.push(String.fromCharCode(i));
}
}

const alphaRegex = /[A-Z]/;

/**
* Generates a new ID
* @param len {Number} The length of the ID
* @param [enforceAlpha=true] {Boolean} Whether or not to enforce alpha characters
* @returns {String} The ID
*/
function makeId(len, enforceAlpha=true) {
const possibleLength = enforceAlpha ? possible.length : 9;
let text = '';

for (let i = 0; i < len; i++) {
text += possible[Math.floor(Math.random() * possibleLength)];
}

// Enforce alpha characters in text if flag is set and recurse if criterion is not met
return enforceAlpha && !alphaRegex.test(text) ? makeId(len) : text;
}

/**
* Web worker message receiver for generating account records
* @param {Object} event
* @param {Object} event.data
* @param {Number} event.data.batchSize The size of the batch (how many accounts to generate)
* @param {String} event.data.studyId The Study ID
* @param {Object} event.data.extra The extra fields
* @param {String} [event.data.tag] The tag
* @returns {Object[]} The account records
* @private
*/
self.onmessage = event => {
const {batchSize, studyId, extra, tag} = event.data;
const records = [];
const idSet = [];

for (let i = 0; i < batchSize; i++) {
postMessage({
progress: i
});

let id;

// Check for duplicated IDs. Unlikely, but possible.
while (!id || ~idSet.indexOf(id)) {
id = `${makeId(7)}${tag ? `-${tag}` : ''}`;
}

idSet.push(id);

const salt = bcrypt.genSaltSync(12);
const password = bcrypt.hashSync(studyId, salt).replace('$2a$', '$2b$');

records[i] = {
id,
password,
extra
};
}

postMessage({records});
};
Loading

0 comments on commit 58cfcd8

Please sign in to comment.