import { Meteor } from 'meteor/meteor';
import { $ } from 'meteor/jquery';

/* global cloudinary */

/**
 * Opens the Cloudinary Upload widget and sets the value of the input field with name equal to nameAttribute to the
 * resulting URL of the uploaded picture.
 * @param nameAttribute The value of the associated input field's name attribute.
 * @memberOf ui/components/form-fields
 */
export function openCloudinaryWidget(nameAttribute) {
  cloudinary.openUploadWidget(
    {
      cloud_name: Meteor.settings.public.cloudinary.cloud_name,
      upload_preset: Meteor.settings.public.cloudinary.upload_preset,
      sources: ['local', 'url', 'camera'],
      cropping: 'server',
      cropping_aspect_ratio: 1,
      max_file_size: '500000',
      max_image_width: '500',
      max_image_height: '500',
      min_image_width: '300',
      min_image_height: '300',
    },
    (error, result) => {
      if (error) {
        console.log('Error during image upload: ', error);
        return;
      }
      // Otherwise get the form elements
      // console.log('Cloudinary results: ', result);
      const url = result[0].url;
      $(`input[name=${nameAttribute}]`).val(url);
    });
}

import { Meteor } from 'meteor/meteor';
import { $ } from 'meteor/jquery';

/* global cloudinary */

/**
 * Opens the Cloudinary Upload widget and sets the value of the input field with name equal to nameAttribute to the
 * resulting URL of the uploaded picture.
 * @param nameAttribute The value of the associated input field's name attribute.
 * @memberOf ui/components/form-fields
 */
export function openCloudinaryWidget(nameAttribute) {
  cloudinary.openUploadWidget(
    {
      cloud_name: Meteor.settings.public.cloudinary.cloud_name,
      upload_preset: Meteor.settings.public.cloudinary.upload_preset,
      sources: ['local', 'url', 'camera'],
      cropping: 'server',
      cropping_aspect_ratio: 1,
      max_file_size: '500000',
      max_image_width: '500',
      max_image_height: '500',
      min_image_width: '200',
      min_image_height: '200',
    },
    (error, result) => {
      if (error) {
        console.log('Error during image upload: ', error);
        return;
      }
      // Otherwise get the form elements
      // console.log('Cloudinary results: ', result);
      const url = result[0].url;
      $(`input[name=${nameAttribute}]`).val(url);
    });
}

Change min image size to 200x200


define((require) => {

    describe('requester_module', () => {
        beforeEach(() => {
            require('app/views/teacher/modules/assistance_request_list_module');
        });

        define('>RetrieveAssistanceRequests', () => {
            it('should be defined', () => {
                expect(RetrieveAssistanceRequests).toBeDefined();
            });

            [[], ['a'], ['a', 'b', 'random_course_id_42']].forEach(selectedCourseList => {
                it(`should request assistance requests for selected courses (${selectedCourseList})`, () => {
                    const mock_socket = {
                        emit: () => undefined
                    };

                    const spy_emit = spyOn(mock_socket, 'emit').and.callThrough();
                    const spy_getSelectedClassIds = jasmine.createSpy('getSelectedClassIds').and.returnValue(selectedCourseList);
                    selectedClassIds = spy_getSelectedClassIds;

                    expect(RetrieveAssistanceRequests()).toBeUndefined();

                    expect(spy_emit.calls.count()).toEqual(1);
                    expect(spy_emit.calls.argsFor(0)).toEqual([{cids: selectedCourseList, qty: 5}]);

                    expect(spy_getSelectedClassIds.calls.count()).toEqual(1);
                    expect(spy_emit.calls.argsFor(0)).toEqual([]);
                });
            });
        });
    });
});

define((require) => {

    describe('requester_module', () => {
        beforeEach(() => {
            require('app/views/teacher/modules/assistance_request_list_module');
        });

        describe('>RetrieveAssistanceRequests', () => {
            it('should be defined', () => {
                expect(RetrieveAssistanceRequests).toBeDefined();
            });

            [[], ['a'], ['a', 'b', 'random_course_id_42']].forEach(selectedCourseList => {
                it(`should request assistance requests for selected courses (${selectedCourseList})`, () => {
                    const mock_socket = {
                        emit: () => undefined
                    };
                    socket = mock_socket;

                    const spy_emit = spyOn(mock_socket, 'emit').and.callThrough();
                    const spy_getSelectedClassIds = jasmine.createSpy('getSelectedClassIds').and.returnValue(selectedCourseList);
                    getSelectedClassIds = spy_getSelectedClassIds;

                    expect(RetrieveAssistanceRequests()).toBeUndefined();

                    expect(spy_emit.calls.count()).toEqual(1);
                    expect(spy_emit.calls.argsFor(0)).toEqual(['Request_RetrieveAssistanceRequests', {cids: selectedCourseList, qty: 5}]);

                    expect(spy_getSelectedClassIds.calls.count()).toEqual(1);
                    expect(spy_getSelectedClassIds.calls.argsFor(0)).toEqual([]);
                });
            });
        });
    });
});

Fix AR list module test


define([
  'underscore',
  'backbone'
], function(_, Backbone) {
  var TaskView = Backbone.View.extend({
    tagName: 'li',
    className: 'task',
    events: {
      "dragstart" : "on_dragstart",
      "dragover"  : "on_dragover",
      "drop"      : "on_drop"
    },

    initialize: function() {
      this.id = 'id-' + this.model.get('id');
      this.$el.prop('draggable', true);
    },

    render: function() {
      this.$el.html(this.model.get('name'));
      return this;
    },

    on_dragstart: function(event) {
      var id      = this.model.get('id');
      var list_id = this.model.get('list_id');
      var payload = [id, list_id].join(':');
      event.originalEvent.dataTransfer.setData("text/plain", payload);
    },

    on_dragover: function(event) {
      event.preventDefault();
    },

    on_drop: function(event) {
      event.preventDefault();
    },

    close: function() {
      this.off();
      this.remove();
    }
  });

  return TaskView;
});

define([
  'underscore',
  'backbone'
], function(_, Backbone) {
  var TaskView = Backbone.View.extend({
    tagName: 'li',
    className: 'task',
    events: {
      "dragstart"     : "on_dragstart",
      "dragover"      : "on_dragover",
      "drop"          : "on_drop",
      "click .delete" : "delete"
    },

    initialize: function() {
      this.id = 'id-' + this.model.get('id');
      this.$el.prop('draggable', true);
    },

    render: function() {
      this.$el.html(this.model.get('name') + this.delete_link());
      return this;
    },

    delete_link: function() {
      return('x');
    },

    on_dragstart: function(event) {
      var id      = this.model.get('id');
      var list_id = this.model.get('list_id');
      var payload = [id, list_id].join(':');
      event.originalEvent.dataTransfer.setData("text/plain", payload);
    },

    on_dragover: function(event) {
      event.preventDefault();
    },

    on_drop: function(event) {
      event.preventDefault();
    },

    delete: function(event) {
      this.model.destroy();
    },

    close: function() {
      this.off();
      this.remove();
    }
  });

  return TaskView;
});

Add ability to delete a task


import React from 'react'
import { Panel } from 'react-bootstrap'
import MarkdownBlock from '../shared/MarkdownBlock'
import { getCreator, getContent } from '../shared/requestUtils'

const Issue = props => {
  return (
    <Panel
      header={`${props.issue.title} by ${getCreator(props.issue).name || getCreator(props.issue).login}`}
      eventKey={props.issue.id}>
      <MarkdownBlock body={getContent(props.issue)} />
    </Panel>
  )
}

export default Issue

import React from 'react'
import { Panel } from 'react-bootstrap'
import MarkdownBlock from '../shared/MarkdownBlock'
import { getCreator, getContent } from '../shared/requestUtils'

const Issue = props => {
  if (!props.issue)
    return null

  return (
    <Panel
      header={`${props.issue.title} by ${getCreator(props.issue).name || getCreator(props.issue).login}`}
      eventKey={props.issue.id}>
      <MarkdownBlock body={getContent(props.issue)} />
    </Panel>
  )
}

export default Issue

Work around react router bug


// TODO: should the baseScore be stored, and updated at vote time?

// This interface should change and become more OO, this'll do for now
var Scoring = {
  // re-run the scoring algorithm on a single object
  updateObject: function(object) {

    // just count the number of votes for now
    var baseScore = MyVotes.find({votedFor: object._id}).count();

    // now multiply by 'age' exponentiated
    // FIXME: timezones <-- set by server or is getTime() ok?
    var ageInHours = (new Date().getTime() - object.submitted) / (60 * 60 * 1000);

    object.score = baseScore * Math.pow(ageInHours + 2, -0.1375);
  },

  // rerun all the scoring
  updateScores: function() {
    Posts.find().forEach(function(post) {
      Scoring.updateObject(post);
      Posts.update(post._id, {$set: {score: post.score}});
    });
  }
}

Meteor.methods({
  voteForPost: function(post){
    var user = this.userId();
    if(!user) return false;

    var myvote = MyVotes.findOne({votedFor: post._id, user: user});
    if(myvote) return false;

    MyVotes.insert({votedFor: post._id, user: user, vote: 1});

    Scoring.updateObject(post);
    Posts.update(post._id, {$set: {score: post.score}});

    return true;
  }
});

// TODO: should the baseScore be stored, and updated at vote time?

// This interface should change and become more OO, this'll do for now
var Scoring = {
  // re-run the scoring algorithm on a single object
  updateObject: function(object) {

    // just count the number of votes for now
    var baseScore = object.votes;

    // now multiply by 'age' exponentiated
    // FIXME: timezones <-- set by server or is getTime() ok?
    var ageInHours = (new Date().getTime() - object.submitted) / (60 * 60 * 1000);

    object.score = baseScore * Math.pow(ageInHours + 2, -0.1375);
  },

  // rerun all the scoring
  updateScores: function() {
    Posts.find().forEach(function(post) {
      Scoring.updateObject(post);
      Posts.update(post._id, {$set: {score: post.score}});
    });
  }
}

Meteor.methods({
  voteForPost: function(post){
    var userId = this.userId();
    if(!userId) return false;

    // atomically update the post's votes
    var query = {_id: post._id, voters: {$ne: userId}};
    var update = {$push: {voters: userId}, $inc: {votes: 1}};
    Posts.update(query, update);

    // now update the post's score
    post = Posts.findOne(post._id);
    Scoring.updateObject(post);
    Posts.update(post._id, {$set: {score: post.score}});

    return true;
  }
});

Use an atomic update operation


import './base.css';
import './application.css';
import 'suitcss-base';
import 'suitcss-utils-text';
import 'suitcss-components-arrange';
import React from 'react';

module.exports = React.createClass({
  contextTypes: {
    router: React.PropTypes.object.isRequired
  },

  link: function () {
    var router = this.context.router;
    return router.generate.apply(router, arguments);
  },

  getInitialState() {
    var time = new Date().getTime();
    // setInterval(this.updateTime, 1000);
    return { time };
  },

  updateTime() {
    var time = new Date().getTime();
    this.setState({time});
  },

  render: function () {
    return (

          {this.props.children}

Cherrytree Demo. ·
            Cherrytree Repo ·
            Demo Source Code

    );
  }
});

Fix links in the cherry-pick demo


module.exports = function (body) {
  return `
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Minor WOT</title>
    <link rel="stylesheet" href="/style.css" />
    <script src="/app.js" defer></script>
    ${body}
  `;
}

module.exports = function (body) {
  return `
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Minor WOT</title>
    <link rel="stylesheet" href="/style.css" />
    <script src="/app.js" defer></script>
    <div data-root>
      ${body}
    </div>
  `;
}

Add data-root div around main body for later use


import express from 'express';
import bodyParser from 'body-parser';

import AppCache from 'parse-server/lib/cache';

import ParseCliController from './ParseCliController';
import ParseCliRouter from './ParseCliRouter';
import VendorAdapter from './VendorAdapter';

class ParseCliServer {
  constructor({
    config,
    vendorAdapter,
    cloud,
    public_html
  }) {
    if (config) {
      AppCache.put(config.applicationId, config);
      if (config.limit != undefined) {
        this.length_limit = limit;
      } else {
        this.length_limit = '500mb';
      }
    }
    if (!vendorAdapter) {
      vendorAdapter = new VendorAdapter({
        config: config,
        cloud: cloud,
        public_html: public_html
      });
    }
    let controller = new ParseCliController(vendorAdapter);
    this.router = new ParseCliRouter(controller);
  }

  get app() {
    var app = express();
    /*
    parse-cli always send json body but don't send a Content-Type
    header or in some cases send an unexpected value like
    application/octet-stream.
    express request length limit is very low. Change limit value
    for fix 'big' files deploy problems.
    */
    app.use(bodyParser.json({type: function() { return true; }, limit: this.length_limit}));

    this.router.mountOnto(app);
    return app;
  }
}

export default ParseCliServer;
export {
    ParseCliServer
};

import express from 'express';
import bodyParser from 'body-parser';

import AppCache from 'parse-server/lib/cache';

import ParseCliController from './ParseCliController';
import ParseCliRouter from './ParseCliRouter';
import VendorAdapter from './VendorAdapter';

class ParseCliServer {
  constructor({
    config,
    vendorAdapter,
    cloud,
    public_html
  }) {
    if (config) {
      AppCache.put(config.applicationId, config);
      if (config.limit != undefined) {
        this.length_limit = config.limit;
      } else {
        this.length_limit = '500mb';
      }
    }
    if (!vendorAdapter) {
      vendorAdapter = new VendorAdapter({
        config: config,
        cloud: cloud,
        public_html: public_html
      });
    }
    let controller = new ParseCliController(vendorAdapter);
    this.router = new ParseCliRouter(controller);
  }

  get app() {
    var app = express();
    /*
    parse-cli always send json body but don't send a Content-Type
    header or in some cases send an unexpected value like
    application/octet-stream.
    express request length limit is very low. Change limit value
    for fix 'big' files deploy problems.
    */
    app.use(bodyParser.json({type: function() { return true; }, limit: this.length_limit}));

    this.router.mountOnto(app);
    return app;
  }
}

export default ParseCliServer;
export {
    ParseCliServer
};

Fix typo in limit configuration and remove bodyparser urlencoded middleware.


'use strict';

var callable = require('./valid-callable')
  , forEach  = require('./for-each')

  , defaultCb = function (value, key) { return [key, value]; };

module.exports = function (obj, cb/*, thisArg*/) {
	var a = [], thisArg = arguments[2];
	cb = (cb == null) ? defaultCb : callable(cb);

	forEach(obj, function (value, key) {
		a.push(cb.call(thisArg, value, key, this));
	}, obj, arguments[3]);
	return a;
};

'use strict';

var callable = require('./valid-callable')
  , forEach  = require('./for-each')

  , call = Function.prototype.call

  , defaultCb = function (value, key) { return [key, value]; };

module.exports = function (obj, cb/*, thisArg*/) {
	var a = [], thisArg = arguments[2];
	cb = (cb == null) ? defaultCb : callable(cb);

	forEach(obj, function (value, key) {
		a.push(call.call(cb, thisArg, value, key, this));
	}, obj, arguments[3]);
	return a;
};

Support any callable object as callback


var ConsecutiveString = function(){};

/*validate k and length of the array. If the array length is 0 of less than k of k is less thatn or equal to 0
return an empty string.
if k is 1, then return the longest string.
else, loop through the string, */
ConsecutiveString.prototype.longestConsec = function(starr, k){
  var out = "", concat = "";
  if((k <= 0 || starr.length == 0) || k > starr.length){
    return out;
  }
  else if(k === 1){
    var s = starr.sort(function(b, a){
      return a - b;
    });
    out = s[s.length-1]
  }else{
    for(var x = 0; x <= k; x++){
      var f3 = starr[x] + starr[x+1] + starr[x+2];
      var n3 = starr[x+1] + starr[x+2] + starr[x+3];
      if(n3.length > f3.length){
        starr[x] = null;
      }
    }
  }

  return out;
};

module.exports = ConsecutiveString

var ConsecutiveString = function(){};

/*validate k and length of the array. If the array length is 0 of less than k of k is less thatn or equal to 0
return an empty string.
if k is 1, then return the longest string.
else, loop through the string, */
ConsecutiveString.prototype.longestConsec = function(starr, k){
  var out = "", concat = "";
  if((k <= 0 || starr.length == 0) || k > starr.length){
    return out;
  }
  else if(k === 1){
    var s = starr.sort(function(b, a){
      return a - b;
    });
    out = s[s.length-1]
  }else{
    let lens = strarr.map( (_,i,arr) => arr.slice(i,i+k).join('').length ),
        i = lens.indexOf( Math.max(...lens) );
    out = strarr.slice(i,i+k).join('')
    }
  }

  return out;
};

module.exports = ConsecutiveString

Add Consectutive String Description solution


navigate = function (hash) {
  window.location.replace(Meteor.absoluteUrl(null, { secure: true }) + hash);
};

navigate = function (hash) {
  window.location.replace(Meteor.absoluteUrl() + hash);
};

Remove extra option to absoluteUrl


WebFontConfig = {
  custom: { families: ['Alternate Gothic No.3', 'Clarendon FS Medium', 'Proxima Nova Light', 'Proxima Nova Semibold', 'Stub Serif FS', 'Stub Serif FS'],
  urls: [ 'http://f.fontdeck.com/s/css/tEGvGvL6JlBfPjTgf2p9URd+sJ0/' + window.location.hostname + '/5502.css' ] }
};

(function() {
  var wf = document.createElement('script');
  wf.src = '/javascript/webfont.js';
  wf.type = 'text/javascript';
  wf.async = 'true';
  var s = document.getElementsByTagName('script')[0];
  s.parentNode.insertBefore(wf, s);
})();

WebFontConfig = {
  custom: { families: ['Alternate Gothic No.3', 'Clarendon FS Medium', 'Proxima Nova Light', 'Proxima Nova Semibold', 'Stub Serif FS', 'Stub Serif FS', 'Reminder Pro Bold'],
  urls: [ 'http://f.fontdeck.com/s/css/tEGvGvL6JlBfPjTgf2p9URd+sJ0/' + window.location.hostname + '/5502.css' ] }
};

(function() {
  var wf = document.createElement('script');
  wf.src = '/javascript/webfont.js';
  wf.type = 'text/javascript';
  wf.async = 'true';
  var s = document.getElementsByTagName('script')[0];
  s.parentNode.insertBefore(wf, s);
})();

Add new font to JS


import Ember from 'ember';

const {
  Component,
  get
} = Ember;

export default Component.extend({
  tagName: 'input',
  type: 'text',
  attributeBindings: [
    'accept',
    'autocomplete',
    'autosave',
    'dir',
    'formaction',
    'formenctype',
    'formmethod',
    'formnovalidate',
    'formtarget',
    'height',
    'inputmode',
    'lang',
    'list',
    'max',
    'min',
    'multiple',
    'name',
    'pattern',
    'size',
    'step',
    'type',
    'value',
    'width'
  ],
  _sanitizedValue: undefined,

  input() { this._handleChangeEvent(); },
  change() { this._handleChangeEvent(); },

  _handleChangeEvent() {
    this._processNewValue.call(this, this.readDOMAttr('value'));
  },

  _processNewValue(rawValue) {
    const value = this.sanitizeInput(rawValue);

    if (this._sanitizedValue !== value) {
      this._sanitizedValue = value;
      this.attrs.update(value);
    }
  },

  sanitizeInput(input) {
    return input;
  },

  didReceiveAttrs() {
    this._super(...arguments);

    if (!this.attrs.update) {
      throw new Error(`You must provide an \`update\` action to \`{{${this.templateName}}}\`.`);
    }

    this._processNewValue.call(this, get(this, 'value'));
  }
});

import Ember from 'ember';

const {
  Component,
  get
} = Ember;

export default Component.extend({
  tagName: 'input',
  type: 'text',
  attributeBindings: [
    'accept',
    'autocomplete',
    'autosave',
    'dir',
    'formaction',
    'formenctype',
    'formmethod',
    'formnovalidate',
    'formtarget',
    'height',
    'inputmode',
    'lang',
    'list',
    'max',
    'min',
    'multiple',
    'name',
    'pattern',
    'placeholder',
    'size',
    'step',
    'type',
    'value',
    'width'
  ],
  _sanitizedValue: undefined,

  input() { this._handleChangeEvent(); },
  change() { this._handleChangeEvent(); },

  _handleChangeEvent() {
    this._processNewValue.call(this, this.readDOMAttr('value'));
  },

  _processNewValue(rawValue) {
    const value = this.sanitizeInput(rawValue);

    if (this._sanitizedValue !== value) {
      this._sanitizedValue = value;
      this.attrs.update(value);
    }
  },

  sanitizeInput(input) {
    return input;
  },

  didReceiveAttrs() {
    this._super(...arguments);

    if (!this.attrs.update) {
      throw new Error(`You must provide an \`update\` action to \`{{${this.templateName}}}\`.`);
    }

    this._processNewValue.call(this, get(this, 'value'));
  }
});

Add placeholder to attribute bindings


/* eslint-disable react/prop-types */
import React from 'react';
import addons from '@storybook/addons';

import StoryPanel from './StoryPanel';
import { ADDON_ID, PANEL_ID } from '.';

export function register() {
  addons.register(ADDON_ID, api => {
    addons.addPanel(PANEL_ID, {
      title: 'Story',
      render: ({ active, key }) => <StoryPanel key={key} api={api} active={active} />,
      paramKey: 'story',
    });
  });
}

/* eslint-disable react/prop-types */
import React from 'react';
import addons from '@storybook/addons';

import StoryPanel from './StoryPanel';
import { ADDON_ID, PANEL_ID } from '.';

export function register() {
  addons.register(ADDON_ID, api => {
    addons.addPanel(PANEL_ID, {
      title: 'Story',
      render: ({ active, key }) => <StoryPanel key={key} api={api} active={active} />,
      paramKey: 'storysource',
    });
  });
}

CHANGE paramKey for storysource addon disable


var express = require('express');
var router = express.Router();
var request = require('request');

router.get('/', function(req, res) {
  request({
    url: 'https://status.heroku.com/api/v3/current-status',
    method: 'GET'
  }, function(err, res, body) {
    console.log(body);
    res.sendStatus(200).end();
  });
});

module.exports = router;

var express = require('express');
var router = express.Router();
var request = require('request');

router.get('/', function(req, res) {
  request({
    url: 'https://status.heroku.com/api/v3/current-status',
    method: 'GET'
  }, function(error, response, body) {
    console.log(body); // {"status":{"Production":"green","Development":"green"},"issues":[]}
    res.sendStatus(200).end();
  });
});

module.exports = router;

Rename res to not overwrite express res.


import React from 'react'
import PropTypes from 'prop-types'

const EditableTableHeader = ({columnNames}) => {

  return(

      {columnNames.map( columnName => {return
{columnName}
})}

  )

}

EditableTableHeader.propTypes = {
  columnNames: PropTypes.arrayOf(PropTypes.string).isRequired
}

export default EditableTableHeader

import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'

const Div = styled.div`
  display:flex;
  flex-direction:row;
`

const HeaderCell = styled.div`
  flex:1;
`

const EditableTableHeader = ({columnNames}) => {

  return(
    <Div>
      {columnNames.map( columnName => {return <HeaderCell key={columnName}>{columnName}</HeaderCell>})}
    </Div>
  )

}

EditableTableHeader.propTypes = {
  columnNames: PropTypes.arrayOf(PropTypes.string).isRequired
}

export default EditableTableHeader

Add styled components to EditableTableHeader


'use strict';

const ComputationResult = require('./computation-result');
const joi = require('joi');

/* istanbul ignore next */
/**
 * @class RemoteComputationResult
 * @extends ComputationResult
 * @description RemoteComputationResult result container
 * @property {string[]} usernames
 * @property {string} _id extends it's parent `PouchDocument` `_id` requirement
 *                        and mandates the format: `runId`
 */
class RemoteComputationResult extends ComputationResult {
  constructor(opts) {
    super(opts);
    this._idRegex = RemoteComputationResult._idRegex;
  }
}

RemoteComputationResult._idRegex = /^([^-]+$)/; // format: runId
RemoteComputationResult.schema = Object.assign({}, ComputationResult.schema, {
  _id: joi.string().regex(RemoteComputationResult._idRegex).required(),
  complete: joi.boolean().default(false), // DecentralizedComputation complete/halted
  computationInputs: joi.array().items(joi.array()).required(),
  endDate: joi.date().timestamp(),
  startDate: joi.date().timestamp().default(Date.now()),
  usernames: joi.array().items(joi.string()).default([]),
  userErrors: joi.array(),
  // runId - derived from _id, enforced on ComputationResult instantiation
});

module.exports = RemoteComputationResult;

'use strict';

const ComputationResult = require('./computation-result');
const joi = require('joi');

/* istanbul ignore next */
/**
 * @class RemoteComputationResult
 * @extends ComputationResult
 * @description RemoteComputationResult result container
 * @property {string[]} usernames
 * @property {string} _id extends it's parent `PouchDocument` `_id` requirement
 *                        and mandates the format: `runId`
 */
class RemoteComputationResult extends ComputationResult {
  constructor(opts) {
    super(opts);
    this._idRegex = RemoteComputationResult._idRegex;
  }
}

RemoteComputationResult._idRegex = /^([^-]+$)/; // format: runId
RemoteComputationResult.schema = Object.assign({}, ComputationResult.schema, {
  _id: joi.string().regex(RemoteComputationResult._idRegex).required(),
  complete: joi.boolean().default(false), // DecentralizedComputation complete/halted
  computationInputs: joi.array().items(joi.array()).required(),
  endDate: joi.date().timestamp(),
  startDate: joi.date().timestamp()
    .default(() => Date.now(), 'time of creation'),
  usernames: joi.array().items(joi.string()).default([]),
  userErrors: joi.array(),
  // runId - derived from _id, enforced on ComputationResult instantiation
});

module.exports = RemoteComputationResult;

Fix remote computation document's start date bug.


/**
 *  Default upload config
 *
 */

module.exports = {
  uploadStates: [
    'enqueued',
    'pending',
    'importing',
    'uploading',
    'guessing',
    'unpacking',
    'getting',
    'creating',
    'complete'
  ],
  fileExtensions: [
    'csv',
    'xls',
    'xlsx',
    'zip',
    'kml',
    'geojson',
    'json',
    'ods',
    'kmz',
    'tsv',
    'gpx',
    'tar',
    'gz',
    'tgz',
    'osm',
    'bz2',
    'tif',
    'tiff',
    'txt',
    'sql'
  ],
  // How big should file be?
  fileTimesBigger: 3
}

/**
 *  Default upload config
 *
 */

module.exports = {
  uploadStates: [
    'enqueued',
    'pending',
    'importing',
    'uploading',
    'guessing',
    'unpacking',
    'getting',
    'creating',
    'complete'
  ],
  fileExtensions: [
    'csv',
    'xls',
    'xlsx',
    'zip',
    'kml',
    'geojson',
    'json',
    'ods',
    'kmz',
    'tsv',
    'gpx',
    'tar',
    'gz',
    'tgz',
    'osm',
    'bz2',
    'tif',
    'tiff',
    'txt',
    'sql',
    'rar'
  ],
  // How big should file be?
  fileTimesBigger: 3
}

Allow importing RAR files in the UI


const Redis = require('ioredis');

module.exports = url => {
  const redis = new Redis(url);

  redis.setJSON = async (...arguments) => {
    arguments[1] = JSON.stringify(arguments[1]);
    if (typeof arguments[2] === 'object' && arguments[2].asSeconds) {
      arguments[2] = parseInt(arguments[2].asSeconds());
    }
    if (typeof arguments[2] === 'number') {
      arguments[3] = parseInt(arguments[2]);
      arguments[2] = 'EX';
    }
    return await redis.set.apply(redis, arguments);
  }

  redis.getJSON = async key => {
    const value = await redis.get(key);
    if (value) {
      return JSON.parse(value);
    }
    return value;
  }

  return redis;
};

const Redis = require('ioredis');

module.exports = url => {
  const redis = new Redis(url);

  redis.setJSON = async (...arguments) => {
    arguments[1] = JSON.stringify(arguments[1]);
    if (typeof arguments[2] === 'object' && arguments[2].asSeconds) {
      arguments[2] = parseInt(arguments[2].asSeconds());
    }
    if (typeof arguments[2] === 'number') {
      arguments[3] = parseInt(arguments[2]);
      arguments[2] = 'EX';
    }
    if (typeof arguments[2] === 'undefined') {
      arguments = [arguments[0], arguments[1]];
    }
    return await redis.set.apply(redis, arguments);
  }

  redis.getJSON = async key => {
    const value = await redis.get(key);
    if (value) {
      return JSON.parse(value);
    }
    return value;
  }

  return redis;
};

Fix redis syntax error if third argument is undefined


/* eslint-env node */

const path = require('path');

module.exports = {
  description: 'Initialize files needed for typescript compilation',

  files() {
    return [
      path.join(this.path, 'files', 'tsconfig.json'),
      path.join(this.path, 'files', 'app', 'config', 'environment.d.ts'),
    ];
  },

  mapFile() {
    const result = this._super.mapFile.apply(this, arguments);

    const tsconfigPattern = `${path.sep}tsconfig.json`;
    const appPattern = `${path.sep}app${path.sep}`;

    if (result.indexOf(tsconfigPattern) > -1) {
      return 'tsconfig.json';
    } else if (result.indexOf(appPattern) > -1) {
      var pos = result.indexOf(appPattern);
      return result.substring(pos + 1);
    }
  },

  locals() {
    return {
      inRepoAddons: (this.project.pkg['ember-addon'] || {}).paths || [],
    };
  },

  normalizeEntityName() {
    // Entity name is optional right now, creating this hook avoids an error.
  },

  afterInstall() {
    return this.addPackagesToProject([
      { name: 'typescript', target: 'latest' },
      { name: '@types/ember', target: 'latest' },
      { name: '@types/rsvp', target: 'latest' },
      { name: '@types/ember-testing-helpers', target: 'latest' },
    ]);
  },
};

import { existsSync } from 'fs';

/* eslint-env node */

const path = require('path');

module.exports = {
  description: 'Initialize files needed for typescript compilation',

  files() {
    return [
      path.join(this.path, 'files', 'tsconfig.json'),
      path.join(this.path, 'files', 'app', 'config', 'environment.d.ts'),
    ];
  },

  mapFile() {
    const result = this._super.mapFile.apply(this, arguments);

    const tsconfigPattern = `${path.sep}tsconfig.json`;
    const appPattern = `${path.sep}app${path.sep}`;

    if (result.indexOf(tsconfigPattern) > -1) {
      return 'tsconfig.json';
    } else if (result.indexOf(appPattern) > -1) {
      var pos = result.indexOf(appPattern);
      return result.substring(pos + 1);
    }
  },

  locals() {
    return {
      inRepoAddons: (this.project.pkg['ember-addon'] || {}).paths || [],
    };
  },

  normalizeEntityName() {
    // Entity name is optional right now, creating this hook avoids an error.
  },

  async afterInstall() {
    if (existsSync('.gitignore')) {
      await this.insertIntoFile('.gitignore', '\n# Ember CLI TypeScript\n.e-c-ts');
    }

    return this.addPackagesToProject([
      { name: 'typescript', target: 'latest' },
      { name: '@types/ember', target: 'latest' },
      { name: '@types/rsvp', target: 'latest' },
      { name: '@types/ember-testing-helpers', target: 'latest' },
    ]);
  },
};

Add `.e.c-ts` to `.gitignore` on install.


var cmm = require("__buttercup/classes/commands/command.cmm.js");

module.exports = {
    setUp: function(cb) {
        this.command = new cmm();
        (cb)();
    },

    callbackInjected: {
        callsToCallback: function(test) {
            var callbackCalled = false;
            var callback = function(comment) {
                callbackCalled = true;
            };

            this.command.injectCommentCallback(callback);
            this.command.execute({}, "");

            test.strictEqual(callbackCalled, true, "Calls into callback");
            test.done();
        },

        callsToCallbackWithCommentTestCaseOne: function(test) {
            var providedComment = "I am the first test case";

            var callbackCalled = false;
            var callback = function(comment) {
                if (comment === providedComment) {
                    callbackCalled = true;
                }
            };

            this.command.injectCommentCallback(callback);
            this.command.execute({}, providedComment);

            test.strictEqual(callbackCalled, true, "Calls into callback with correct comment (test case one)");
            test.done();
        }
    }
};

var cmm = require("__buttercup/classes/commands/command.cmm.js");

module.exports = {
    setUp: function(cb) {
        this.command = new cmm();
        (cb)();
    },

    callbackInjected: {
        callsToCallback: function(test) {
            var callbackCalled = false;
            var callback = function(comment) {
                callbackCalled = true;
            };

            this.command.injectCommentCallback(callback);
            this.command.execute({}, "");

            test.strictEqual(callbackCalled, true, "Calls into callback");
            test.done();
        },

        callsToCallbackWithCommentTestCaseOne: function(test) {
            var providedComment = "I am the first test case";

            var callbackCalled = false;
            var callback = function(comment) {
                if (comment === providedComment) {
                    callbackCalled = true;
                }
            };

            this.command.injectCommentCallback(callback);
            this.command.execute({}, providedComment);

            test.strictEqual(callbackCalled, true, "Calls into callback with correct comment (test case one)");
            test.done();
        },

        callsToCallbackWithCommentTestCaseTwo: function(test) {
            var providedComment = "The second test case, that is what I am";

            var callbackCalled = false;
            var callback = function(comment) {
                if (comment === providedComment) {
                    callbackCalled = true;
                }
            };

            this.command.injectCommentCallback(callback);
            this.command.execute({}, providedComment);

            test.strictEqual(callbackCalled, true, "Calls into callback with correct comment (test case two)");
            test.done();
        }
    }
};

Test case two for comment contents


var five = require("../lib/johnny-five.js"),
  board = new five.Board();

board.on("ready", function() {
  // Creates a piezo object and defines the pin to be used for the signal
  var piezo = new five.Piezo(3);

  // Injects the piezo into the repl
  board.repl.inject({
    piezo: piezo
  });

  // Plays a song
  piezo.play({
    // song is composed by an array of pairs of notes and beats
    // The first argument is the note (null means "no note")
    // The second argument is the length of time (beat) of the note (or non-note)
    song: [
      ["C4", 1],
      ["D4", 1],
      ["F4", 1],
      ["D4", 1],
      ["A4", 1],
      [null, 1],
      ["A4", 4],
      ["G4", 4],
      [null, 2],
      ["C4", 1],
      ["D4", 1],
      ["F4", 1],
      ["D4", 1],
      ["G4", 1],
      [null, 1],
      ["G4", 4],
      ["F4", 4],
      [null, 2]
    ],
    tempo: 250
  });

});

var five = require("../lib/johnny-five.js"),
  board = new five.Board();

board.on("ready", function() {
  // Creates a piezo object and defines the pin to be used for the signal
  var piezo = new five.Piezo(3);

  // Injects the piezo into the repl
  board.repl.inject({
    piezo: piezo
  });

  // Plays a song
  piezo.play({
    // song is composed by an array of pairs of notes and beats
    // The first argument is the note (null means "no note")
    // The second argument is the length of time (beat) of the note (or non-note)
    song: [
      ["C4", 1/4],
      ["D4", 1/4],
      ["F4", 1/4],
      ["D4", 1/4],
      ["A4", 1/4],
      [null, 1/4],
      ["A4", 1],
      ["G4", 1],
      [null, 1/2],
      ["C4", 1/4],
      ["D4", 1/4],
      ["F4", 1/4],
      ["D4", 1/4],
      ["G4", 1/4],
      [null, 1/4],
      ["G4", 1],
      ["F4", 1],
      [null, 1/2]
    ],
    tempo: 100
  });

});

Change beat to fractional values


var hoverZoomPlugins = hoverZoomPlugins || [];
hoverZoomPlugins.push({
    name: 'wallhaven.cc',
    prepareImgLinks: function (callback) {
        var res = [];
        hoverZoom.urlReplace(res,
            'img[src*="/th.wallhaven.cc/"]',
            /\/th.wallhaven.cc\/(.*)\/(.*)\/(.*)\.(.*)$/,
            '/w.wallhaven.cc/full/$2/wallhaven-$3.jpg'
        );
        $('body').on('mouseenter', 'a[href*="/wallhaven.cc/w/"]', function () {
            hoverZoom.prepareFromDocument($(this), this.href, function (doc) {
                var img = doc.getElementById('wallpaper');
                return img ? img.src : false;
            });
        });
        callback($(res));
    },
});

var hoverZoomPlugins = hoverZoomPlugins || [];
hoverZoomPlugins.push({
    name: 'wallhaven.cc',
    version:'3.0',
    prepareImgLinks: function (callback) {

        var res = [];

        hoverZoom.urlReplace(res,
            'img[src*="/th.wallhaven.cc/"]',
            /\/th.wallhaven.cc\/(.*)\/(.*)\/(.*)\.(.*)$/,
            '/w.wallhaven.cc/full/$2/wallhaven-$3.jpg'
        );

        hoverZoom.urlReplace(res,
            'img[src]',
            /\/avatar\/\d+\//,
            '/avatar/200/'
        );

        $('body').on('mouseenter', 'a[href*="/wallhaven.cc/w/"]', function () {
            hoverZoom.prepareFromDocument($(this), this.href, function (doc) {
                var img = doc.getElementById('wallpaper');
                return img ? img.src : false;
            });
        });

        callback($(res));
    },
});

Update for plug-in : wallhaven.cc


// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

function load() {
  $('unlock-passphrase-button').onclick = function(event) {
    chrome.send('checkPassphrase', [$('passphrase-entry').value]);
  };
  $('cancel-passphrase-button').onclick = function(event) {
    // TODO(akuegel): Replace by closeDialog.
    chrome.send('DialogClose');
  };
  $('passphrase-entry').oninput = function(event) {
    $('unlock-passphrase-button').disabled = $('passphrase-entry').value == '';
    $('incorrect-passphrase-warning').hidden = true;
  };
}

function passphraseCorrect() {
  chrome.send('DialogClose', ['true']);
}

function passphraseIncorrect() {
  $('incorrect-passphrase-warning').hidden = false;
  $('passphrase-entry').focus();
}

window.addEventListener('DOMContentLoaded', load);

// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

function load() {
  var checkPassphrase = function(event) {
    chrome.send('checkPassphrase', [$('passphrase-entry').value]);
  };
  var closeDialog = function(event) {
    // TODO(akuegel): Replace by closeDialog.
    chrome.send('DialogClose');
  };
  // Directly set the focus on the input box so the user can start typing right
  // away.
  $('passphrase-entry').focus();
  $('unlock-passphrase-button').onclick = checkPassphrase;
  $('cancel-passphrase-button').onclick = closeDialog;
  $('passphrase-entry').oninput = function(event) {
    $('unlock-passphrase-button').disabled = $('passphrase-entry').value == '';
    $('incorrect-passphrase-warning').hidden = true;
  };
  $('passphrase-entry').onkeypress = function(event) {
    if (!event)
      return;
    // Check if the user pressed enter.
    if (event.keyCode == 13)
      checkPassphrase(event);
  };

  // Pressing escape anywhere in the frame should work.
  document.onkeyup = function(event) {
    if (!event)
      return;
    // Check if the user pressed escape.
    if (event.keyCode == 27)
      closeDialog(event);
  };
}

function passphraseCorrect() {
  chrome.send('DialogClose', ['true']);
}

function passphraseIncorrect() {
  $('incorrect-passphrase-warning').hidden = false;
  $('passphrase-entry').focus();
}

window.addEventListener('DOMContentLoaded', load);

Improve usability in passphrase input dialog.


 'use strict';
  require('./cookie-functions.js');
  require('govuk_publishing_components/components/cookie-banner.js');

  window.GOVUK.DEFAULT_COOKIE_CONSENT = {
    'essential': true,
    'usage': false,
    'brexit': false
  }

  window.GOVUK.setDefaultConsentCookie = function () {
    var defaultConsent = {
      'essential': true,
      'usage': false,
      'brexit': false
    }
    window.GOVUK.setCookie('cookie_policy', JSON.stringify(defaultConsent), { days: 365 })
  }

  window.GOVUK.approveAllCookieTypes = function () {
    var approvedConsent = {
      'essential': true,
      'usage': true,
      'brexit': true
    }

    window.GOVUK.setCookie('cookie_policy', JSON.stringify(approvedConsent), { days: 365 })
  }

  window.GOVUK.Modules.CookieBanner.prototype.isInCookiesPage = function () {
    return window.location.pathname === '/cookie-settings'
  }

Remove cookie banner from cookie settings page


'use strict';

const cookieParser = require('cookie-parser');
const csrf = require('csurf');

module.exports = function(server) {
  let secure = true;
  if (process.env.NODE_ENV === 'development') {
    secure = false;
  }
  server.use(cookieParser());
    server.use(csrf({cookie: {
        key: 'XSRF-SECRET',
        secure,
        httpOnly: true,
        path: '/<%= applicationFolder %>'
    }}));

  server.use((err, req, res, next) => {  //eslint-disable-line
    if (err.code !== 'EBADCSRFTOKEN') {
      return next(err);
    }
    // handle CSRF token errors here
    res.status(403);  //eslint-disable-line no-magic-numbers
    res.send('invalid csrf token');
  });

  server.use((req, res, next) => {
    const token = req.csrfToken();
    res.header('x-powered-by', '');
    res.cookie('XSRF-TOKEN', token, { secure, path: '/<%= applicationFolder %>' });
    next();
  });
};

'use strict';

const cookieParser = require('cookie-parser');
const csrf = require('csurf');

module.exports = function(server) {
  let secure = true;
  if (process.env.NODE_ENV === 'development') {
    secure = false;
  }

  server.use(cookieParser());
    server.use(csrf({cookie: {
        key: 'XSRF-SECRET',
        secure,
        httpOnly: true,
        path: '/<%= applicationFolder %>'
    }}));

  server.use((err, req, res, next) => { // eslint-disable-line
    if (err.code !== 'EBADCSRFTOKEN') {
      return next(err);
    }

    // handle CSRF token errors here
    res.status(403); // eslint-disable-line no-magic-numbers
    res.send('invalid csrf token');
  });

  server.use((req, res, next) => {
    const token = req.csrfToken();
    res.header('x-powered-by', '');
    res.cookie('XSRF-TOKEN', token, { secure, path: '/<%= applicationFolder %>' });
    next();
  });
};

Fix linting errors on server


'use strict';

/* Controllers */

var cvAppControllers = angular.module('cvAppControllers', []);

cvAppControllers.controller('HomeController', ['$scope', '$http', function($scope, $http) {
	$http.get('/app/info/info.json').success(function(data) {
		$scope.stuff = data;
	});
}]);

cvAppControllers.controller('AboutController', ['$scope', '$http', function($scope, $http) {
	$http.get('/app/info/info.json').success(function(data) {
		$scope.stuff = data;
	});
}]);

'use strict';

/* Controllers */

var cvAppControllers = angular.module('cvAppControllers', []);

cvAppControllers.controller('HomeController', ['$scope', '$http', function($scope, $http) {
	$http.get('/app/info/info.json').success(function(data) {
		$scope.stuff = data;
	});
	$scope.backgroundImg = "/app/img/kugghjulet.png"
}]);

cvAppControllers.controller('AboutController', ['$scope', '$http', function($scope, $http) {
	$http.get('/app/info/info.json').success(function(data) {
		$scope.stuff = data;
	});
}]);

Add kugghjulet.png to home scope


'use strict';

/**
* A list of English stopwords.
*
* @module @stdlib/datasets/smart-stopwords-en
*
* @example
* var STOPWORDS = require( '@stdlib/datasets/smart-stopwords-en' )
* // returns [ "a", "a's", "able", "about", ... ]
*/

var STOPWORDS = require( './../data/words.json' );

// EXPORTS //

module.exports = STOPWORDS;

'use strict';

/**
* A list of English stopwords.
*
* @module @stdlib/datasets/stopwords-en
*
* @example
* var STOPWORDS = require( '@stdlib/datasets/stopwords-en' )
* // returns [ "a", "a's", "able", "about", ... ]
*/

var STOPWORDS = require( './../data/words.json' );

// EXPORTS //

module.exports = STOPWORDS;

Revert name change of stopwords-english


import 'whatwg-fetch';

const apiBaseURL = 'http://localhost:1337';

function parseJSONFromResponse(response) {
  return response.json();
}

export const bookshelfApi = {
  getBooks() {
    return fetch(`${apiBaseURL}/books`)
      .then(parseJSONFromResponse)
      .catch(error =>
        console.error(
`Something went wrong trying to fetch books from bookshelf-server.
Make sure the bookshelf-server is started.`, error));
  }
};

import 'whatwg-fetch';

const apiBaseURL = 'http://localhost:1337';

function parseJSONFromResponse(response) {
  return response.json();
}

function stringIsEmpty(str) {
  return str === '' || str === undefined || str === null;
}

export const bookshelfApi = {
  getBooks() {
    return fetch(`${apiBaseURL}/books`)
      .then(parseJSONFromResponse)
      .catch(error =>
        console.error('something went wrong attempting to fetch books:', error));
  },

  createBook(title, author) {
    if (stringIsEmpty(title) || stringIsEmpty(author)) {
      return false;
    }

    return fetch(`${apiBaseURL}/books`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: JSON.stringify({
        title,
        author,
      }),
    }).then(() => true)
      .catch(() => false);
  },
};

Add method createBook to bookshelfApi connection.


const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(4);
});

Change test result to test travis ci notification


const Q = require('q');
const RegularUsers = require('./../models/userSchema.js');
const BusinessUsers = require('./../models/businessUserSchema.js');
const Events = require('./../models/eventSchema.js');
const utils = require('../utils/utilities.js');

// Promisify a few mongoose methods with the `q` promise library
const findUser = Q.nbind(RegularUsers.findOne, RegularUsers);
const findBusUser = Q.nbind(BusinessUsers.findOne, BusinessUsers);
const findAllEvents = Q.nbind(Events.find, Events);
const postAnEvent = Q.nbind(Events.create, Events);

module.exports = {

  getEvents: (req, res, next) => {
    findAllEvents({})
      .then((events) => {
        res.json(events);
      })
      .fail((error) => {
        next(error);
      });
  },

  postEvent: (req, res, next) => {
    const body = req.body;

    postAnEvent({
      username: body.username,
      eventTime: body.eventTime,
      location: body.location,
      createdAt: new Date().toLocaleString(),
      tags: body.tags.split(' '),
      businessName: body.businessName,
      picLink: body.picLink,
      busLink: body.busLink,
      description: body.dscription,
    })
    .then(() => {
      res.setStatus(201).end();
    })
    .fail(err => err);
  },

  // getUser: (req, res, next) => {
  //
  // },

};

const Q = require('q');
const RegularUsers = require('./../models/userSchema.js');
const BusinessUsers = require('./../models/businessUserSchema.js');
const Events = require('./../models/eventSchema.js');
const utils = require('../utils/utilities.js');

// Promisify a few mongoose methods with the `q` promise library
const findUser = Q.nbind(RegularUsers.findOne, RegularUsers);
const findBusUser = Q.nbind(BusinessUsers.findOne, BusinessUsers);
const findAllEvents = Q.nbind(Events.find, Events);
const postAnEvent = Q.nbind(Events.create, Events);

module.exports = {

  getEvents: (req, res, next) => {
    findAllEvents({})
      .then((events) => {
        res.json(events);
      })
      .fail((error) => {
        next(error);
      });
  },

  postEvent: (req, res, next) => {
    const body = req.body;
    const tags = body.tags ?  body.tags.split(' ') : '';
    postAnEvent({
      username: body.username,
      eventTime: body.eventTime,
      location: body.location,
      createdAt: new Date().toLocaleString(),
      tags,
      businessName: body.businessName,
      picLink: body.picLink,
      busLink: body.busLink,
      description: body.dscription,
    })
    .then(() => {
      res.setStatus(201).end();
    })
    .fail(err => err);
  },

  // getUser: (req, res, next) => {
  //
  // },

};

Debug and tested server routes, successful post and get


var monorouter = require('monorouter');
var reactRouting = require('monorouter-react');
var PetList = require('./views/PetList');
var PetDetail = require('./views/PetDetail');
var Preloader = require('./views/Preloader');

module.exports = monorouter()
  .setup(reactRouting())
  .route('index', '/', function(req) {
    this.renderInitial(Preloader, function() {
      // Artificially make the next render take a while.
      setTimeout(function() {
        this.render(PetList);
      }.bind(this), 1000);
    });
  })
  .route('pet', '/pet/:name', function(req) {
    this.renderInitial(Preloader, function() {
      // Artificially make the next render take a while.
      setTimeout(function() {
        this.render(PetDetail, {petName: req.params.name});
      }.bind(this), 1000);
    });
  });

var monorouter = require('monorouter');
var reactRouting = require('monorouter-react');
var PetList = require('./views/PetList');
var PetDetail = require('./views/PetDetail');
var Preloader = require('./views/Preloader');

module.exports = monorouter()
  .setup(reactRouting())
  .route('index', '/', function(req) {
    this.renderInitial(Preloader, function() {
      // Artificially make the next render take a while.
      setTimeout(function() {
        this.render(PetList);
      }.bind(this), 1000);
    });
  })
  .route('pet', '/pet/:name', function(req) {
    // NOTE: Because we're calling `this.renderInitial()`, we lose the
    // opportunity to have the server send a 404, and the client will have to
    // display a 'missing' view. If you want the server to send 404s, you have
    // to call `this.unhandled()` before `this.renderInitial()`.
    this.renderInitial(Preloader, function() {
      // Artificially make the next render take a while.
      setTimeout(function() {
        this.render(PetDetail, {petName: req.params.name});
      }.bind(this), 1000);
    });
  });

Add note about 404 pitfall with renderInitial example


'use strict';

/**
 * Module dependencies.
 */

/**
 * Find device by id
 */
exports.command = function(req, res) {
    var client = exports.client;
    var channel = '/control/' + req.params.deviceId + '/' + req.params.command;
    client.publish(channel, req.body);
    console.log('Publish to channel: ' + channel);
    res.jsonp({result: 'OK'});
};

exports.setClient = function(client) {
	exports.client = client;
};

'use strict';

/**
 * Module dependencies.
 */

/**
 * Find device by id
 */
exports.command = function(req, res) {
    var client = exports.client;
    var channel = '/control/' + req.params.deviceId;
    var data = {
    	command: req.params.command,
    	body: req.body
    }
    client.publish(channel, data);
    console.log('Publish to channel: ' + channel);
    res.jsonp({result: 'OK'});
};

exports.setClient = function(client) {
	exports.client = client;
};

Send command in the published message


import Ember from 'ember';
import PaginationMixin from '../../mixins/pagination';

const { computed } = Ember;

export default Ember.Controller.extend(PaginationMixin, {
    queryParams: ['page', 'per_page', 'sort'],
    page: '1',
    per_page: 10,
    sort: 'alpha',

    totalItems: computed.readOnly('model.meta.total'),

    currentSortBy: computed('sort', function() {
        return (this.get('sort') === 'downloads') ? 'Downloads' : 'Alphabetical';
    }),
});

import Ember from 'ember';
import PaginationMixin from '../../mixins/pagination';

const { computed } = Ember;

export default Ember.Controller.extend(PaginationMixin, {
    queryParams: ['page', 'per_page', 'sort'],
    page: '1',
    per_page: 10,
    sort: 'downloads',

    totalItems: computed.readOnly('model.meta.total'),

    currentSortBy: computed('sort', function() {
        return (this.get('sort') === 'downloads') ? 'Downloads' : 'Alphabetical';
    }),
});

Sort crates within a category by downloads by default


/**
 * public/src/js/logger.js - delish
 *
 * Licensed under MIT license.
 * Copyright (C) 2017 Karim Alibhai.
 */

const util = require('util')
    , debounce = require('debounce')

let currentLog = document.querySelector('.lead')
  , nextLog = document.querySelector('.lead.next')

/**
 * Updates the current log status.
 * @param {String} message the string with formatting
 * @param {...Object} values any values to plug in
 */
export default debounce(function () {
  let text = util.format.apply(util, arguments)

  // transition to the new log
  nextLog.innerText = text
  currentLog.classList.add('hide')
  nextLog.classList.remove('next')

  // after the CSS transition completes, swap
  // the log elements out
  setTimeout(() => {
    let parent = currentLog.parentElement

    parent.removeChild(currentLog)
    currentLog = nextLog

    nextLog = document.createElement('div')
    nextLog.classList.add('lead')
    nextLog.classList.add('next')
    parent.appendChild(nextLog)
  }, 700)
}, 700)

/**
 * public/src/js/logger.js - delish
 *
 * Licensed under MIT license.
 * Copyright (C) 2017 Karim Alibhai.
 */

const util = require('util')
    , debounce = require('debounce')

let currentLog = document.querySelector('.lead')
  , nextLog = document.querySelector('.lead.next')

/**
 * Updates the current log status.
 * @param {String} message the string with formatting
 * @param {...Object} values any values to plug in
 */
export default debounce(function () {
  let text = util.format.apply(util, arguments)

  // transition to the new log
  nextLog.innerText = text
  currentLog.classList.add('hide')
  nextLog.classList.remove('next')

  // after the CSS transition completes, swap
  // the log elements out
  setTimeout(() => {
    let parent = currentLog.parentElement

    parent.removeChild(currentLog)
    currentLog = nextLog

    nextLog = document.createElement('p')
    nextLog.classList.add('lead')
    nextLog.classList.add('next')
    parent.appendChild(nextLog)
  }, 700)
}, 700)

Change div to p when creating new logs


export default function getPreloadedState(position, cb) {
  chrome.storage.local.get([
    'monitor' + position, 'slider' + position, 'dispatcher' + position,
    'test-templates', 'test-templates-sel'
  ], options => {
    cb({
      monitor: {
        selected: options['monitor' + position],
        sliderIsOpen: options['slider' + position] || false,
        dispatcherIsOpen: options['dispatcher' + position] || false,
      },
      test: {
        selected: options['test-templates-sel'] || 0,
        templates: options['test-templates']
      }
    });
  });
}

const getIfExists = (sel, template) => (
  typeof sel === 'undefined' ||
  typeof template === 'undefined' ||
  typeof template[sel] === 'undefined' ?
    0 : sel
);

export default function getPreloadedState(position, cb) {
  chrome.storage.local.get([
    'monitor' + position, 'slider' + position, 'dispatcher' + position,
    'test-templates', 'test-templates-sel'
  ], options => {
    cb({
      monitor: {
        selected: options['monitor' + position],
        sliderIsOpen: options['slider' + position] || false,
        dispatcherIsOpen: options['dispatcher' + position] || false,
      },
      test: {
        selected: getIfExists(options['test-templates-sel'], options['test-templates']),
        templates: options['test-templates']
      }
    });
  });
}

Select the test template only if exists


let EnvironmentVariables = {
  description: 'Set environment variables for each task your service launches in addition to those set by Mesos.',
  type: 'object',
  title: 'Environment Variables',
  properties: {
    variables: {
      type: 'array',
      duplicable: true,
      addLabel: 'Add Environment Variable',
      getter: function (service) {
        let variableMap = service.getEnvironmentVariables();
        if (variableMap == null) {
          return [{
            key: null,
            value: null,
            usingSecret: null
          }];
        }
        return Object.keys(variableMap).map(function (key) {
          let value = variableMap[key];
          let usingSecret = null;

          if (typeof value === 'object' && value != null) {
            usingSecret = true;
            value = service.get('secrets')[value.secret].source;
          }

          return {
            key,
            value,
            usingSecret
          };
        });
      },
      itemShape: {
        properties: {
          key: {
            title: 'Key',
            type: 'string'
          },
          value: {
            title: 'Value',
            type: 'string'
          }
        }
      }
    }
  }
};

module.exports = EnvironmentVariables;

import {Hooks} from 'PluginSDK';

let EnvironmentVariables = {
  description: 'Set environment variables for each task your service launches in addition to those set by Mesos.',
  type: 'object',
  title: 'Environment Variables',
  properties: {
    variables: {
      type: 'array',
      duplicable: true,
      addLabel: 'Add Environment Variable',
      getter: function (service) {
        let variableMap = service.getEnvironmentVariables();
        if (variableMap == null) {
          return [{}];
        }
        return Object.keys(variableMap).map(function (key) {
          return Hooks.applyFilter('variablesGetter', {
            key,
            value: variableMap[key]
          }, service);
        });
      },
      itemShape: {
        properties: {
          key: {
            title: 'Key',
            type: 'string'
          },
          value: {
            title: 'Value',
            type: 'string'
          }
        }
      }
    }
  }
};

module.exports = EnvironmentVariables;

Move unnecessary things out of env schema


const loaders = require("./loaders");
const webpack = require("webpack");
const path = require("path");

module.exports = {
  context: path.join(__dirname, ".."),
  entry: ["./src/test.ts"],
  output: {
    filename: "build.js",
    path: path.resolve("temp"),
    publicPath: "/"
  },
  resolve: {
    extensions: [
      ".ts",
      ".js",
      ".json"
    ]
  },
  resolveLoader: {
    modules: ["node_modules"]
  },
  devtool: "source-map-inline",
  module: {
    loaders: loaders
    /* postLoaders: [
      {
        test: /^((?!\.spec\.ts).)*.ts$/,
        exclude: /(node_modules)/,
        loader: "istanbul-instrumenter"
      }
    ] */
  }
};

const rules = require("./rules");
const webpack = require("webpack");
const path = require("path");

module.exports = {
  context: path.join(__dirname, ".."),
  entry: ["./src/test.ts"],
  output: {
    filename: "build.js",
    path: path.resolve("temp"),
    publicPath: "/"
  },
  resolve: {
    extensions: [
      ".ts",
      ".js",
      ".json"
    ]
  },
  resolveLoader: {
    modules: ["node_modules"]
  },
  devtool: "source-map-inline",
  module: {
    rules: rules
  }
};

Replace loaders -> rules. Delete useless configs


/*! http://mths.be/endswith v0.1.0 by @mathias */
if (!String.prototype.endsWith) {
	(function() {
		'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
		var toString = {}.toString;
		String.prototype.endsWith = function(search) {
			var string = String(this);
			if (
				this == null ||
				(search && toString.call(search) == '[object RegExp]')
			) {
				throw TypeError();
			}
			var stringLength = string.length;
			var searchString = String(search);
			var searchLength = searchString.length;
			var pos = stringLength;
			if (arguments.length > 1) {
				var position = arguments[1];
				if (position !== undefined) {
					// `ToInteger`
					pos = position ? Number(position) : 0;
					if (pos != pos) { // better `isNaN`
						pos = 0;
					}
				}
			}
			var end = Math.min(Math.max(pos, 0), stringLength);
			var start = end - searchLength;
			if (start < 0) {
				return false;
			}
			var index = -1;
			while (++index < searchLength) {
				if (string.charAt(start + index) != searchString.charAt(index)) {
					return false;
				}
			}
			return true;
		};
	}());
}

/*! http://mths.be/endswith v0.1.0 by @mathias */
if (!String.prototype.endsWith) {
	(function() {
		'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
		var toString = {}.toString;
		String.prototype.endsWith = function(search) {
			var string = String(this);
			if (
				this == null ||
				(search && toString.call(search) == '[object RegExp]')
			) {
				throw TypeError();
			}
			var stringLength = string.length;
			var searchString = String(search);
			var searchLength = searchString.length;
			var pos = stringLength;
			if (arguments.length > 1) {
				var position = arguments[1];
				if (position !== undefined) {
					// `ToInteger`
					pos = position ? Number(position) : 0;
					if (pos != pos) { // better `isNaN`
						pos = 0;
					}
				}
			}
			var end = Math.min(Math.max(pos, 0), stringLength);
			var start = end - searchLength;
			if (start < 0) {
				return false;
			}
			var index = -1;
			while (++index < searchLength) {
				if (string.charCodeAt(start + index) != searchString.charCodeAt(index)) {
					return false;
				}
			}
			return true;
		};
	}());
}

Use `charCodeAt` instead of `charAt`


// Copyright 2015, EMC, Inc.

'use strict';

module.exports = GraphModelFactory;

GraphModelFactory.$provide = 'Models.GraphObject';
GraphModelFactory.$inject = [
    'Model'
];

function GraphModelFactory (Model) {
    return Model.extend({
        connection: 'mongo',
        identity: 'graphobjects',
        attributes: {
            instanceId: {
                type: 'string',
                required: true,
                unique: true,
                uuidv4: true
            },
            context: {
                type: 'json',
                required: true,
                json: true
            },
            definition: {
                type: 'json',
                required: true,
                json: true
            },
            tasks: {
                type: 'json',
                required: true,
                json: true
            },
            node: {
                model: 'nodes'
            }
        }
    });
}

// Copyright 2015, EMC, Inc.

'use strict';

module.exports = GraphModelFactory;

GraphModelFactory.$provide = 'Models.GraphObject';
GraphModelFactory.$inject = [
    'Model',
    'Constants'
];

function GraphModelFactory (Model, Constants) {
    return Model.extend({
        connection: 'mongo',
        identity: 'graphobjects',
        attributes: {
            instanceId: {
                type: 'string',
                required: true,
                unique: true,
                uuidv4: true
            },
            context: {
                type: 'json',
                required: true,
                json: true
            },
            definition: {
                type: 'json',
                required: true,
                json: true
            },
            tasks: {
                type: 'json',
                required: true,
                json: true
            },
            node: {
                model: 'nodes'
            },
            active: function() {
                var obj = this.toObject();
                return obj._status === Constants.Task.States.Pending;
            }
        }
    });
}

Add active() method to graph object model


import { moduleForModel, test } from 'ember-qunit';

moduleForModel('ilm-session', 'Unit | Serializer | ilm session', {
  // Specify the other units that are required for this test.
  needs: ['serializer:ilm-session']
});

// Replace this with your real tests.
test('it serializes records', function(assert) {
  let record = this.subject();

  let serializedRecord = record.serialize();

  assert.ok(serializedRecord);
});

import { moduleForModel, test } from 'ember-qunit';

moduleForModel('ilm-session', 'Unit | Serializer | ilm session', {
  // Specify the other units that are required for this test.
  needs: [
    'model:session',
    'model:learner-group',
    'model:instructor-group',
    'model:user',
    'serializer:ilm-session'
  ]
});

// Replace this with your real tests.
test('it serializes records', function(assert) {
  let record = this.subject();

  let serializedRecord = record.serialize();

  assert.ok(serializedRecord);
});

Fix ilm-session serializer test dependancies


var overload = require('../src/overload');
var should = require('should');

describe('overload', function() {
    describe('arity', function() {
        it('should exist', function() {
            overload.should.have.property('arity');
        });

        it('should work', function() {
            var two_or_three = overload.arity(2, 3);
            var obj = {};

            var res = 0;

            overload.add(obj, 'work', function(one) {
                res += 1;
            });

            overload.add(obj, 'work', two_or_three, function(one, two, three) {
                res += 2;
            });

            obj.work('one');
            res.should.eql(1);

            obj.work('one', 'two');
            res.should.eql(3);

            obj.work('one', 'two', 'three');
            res.should.eql(5);

            obj.work('one', 'two', 'three', 'four');
            res.should.eql(5);
        });
    });
});

var overload = require('../src/overload');
var should = require('should');

describe('overload', function() {
    describe('arity', function() {
        it('should exist', function() {
            overload.should.have.property('arity');
        });

        it('should work', function() {
            var two_or_three = overload.arity(2, 3);
            var obj = {};

            var res = 0;

            overload.add(obj, 'work', function(one) {
                res += 1;
            });

            overload.add(obj, 'work', two_or_three, function(one, two, three) {
                res += 2;
            });

            obj.work('one');
            res.should.eql(1);

            obj.work('one', 'two');
            res.should.eql(3);

            obj.work('one', 'two', 'three');
            res.should.eql(5);

            obj.work('one', 'two', 'three', 'four');
            res.should.eql(5);
        });

        it('should work with saying more than or equal to', function() {
            var obj = {};
            var res = 0;

            overload.add(obj, 'work', overload.arity(3), function() {
                res++;
            });

            obj.work();
            res.should.eql(0);

            obj.work('one', 'two', 'three');
            res.should.eql(1);

            obj.work('one', 'two', 'three', 'four');
            res.should.eql(2);
        });
    });
});

Add tests for saying more than


const R = require("ramda");
const Promise = require("bluebird");
const pubsub = require("../")("amqp://docker");

const publish = (event, index) => {
    const data = { number: index + 1 };
    console.log(` [-] Published event '${ event.name }' with data:`, data);
    event.publish(data)
  };

// Create the event.
const events = {
  foo: pubsub.event("foo"),
  bar: pubsub.event("bar")
};

// Read in the command-line args, to determine which event to fire
// and how many events to fire it.
const args = process.argv.slice(2);
const eventName = args[0] || "foo";
const count = parseInt(args[1]) || 1;

if (events[eventName]) {
  const promises = R.times((index) => publish(events[eventName], index), count);
  Promise.all(promises)
    .then(() => {
      console.log("");
      setTimeout(() => process.exit(0), 200);
    })

} else {
  console.log(`Event named '${ eventName }' does not exist.\n`);
}

const R = require("ramda");
const Promise = require("bluebird");
const pubsub = require("../")("amqp://docker");

const publish = (event, index) => {
    const data = { number: index + 1 };
    console.log(` [-] Published event '${ event.name }' with data:`, data);
    event.publish(data)
  };

// Create the event.
const events = {
  foo: pubsub.event("foo"),
  bar: pubsub.event("bar")
};

// Read in the command-line args, to determine which event to fire
// and how many events to fire it.
const args = process.argv.slice(2);
const eventName = args[0] || "foo";
const count = parseInt(args[1]) || 1;

if (events[eventName]) {
  const promises = R.times((index) => publish(events[eventName], index), count);
  Promise.all(promises)
    .then(() => {
      console.log("");
      setTimeout(() => process.exit(0), 500);
    })

} else {
  console.log(`Event named '${ eventName }' does not exist.\n`);
}

Extend timeout before killing process.


'use strict';

var CustomError = require('es5-ext/error/custom')
  , logger      = require('../lib/logger')();

module.exports = function (t, a) {
	t = t(logger);
	t(true, true, 'foo');
	t.ok(false, 'bar');
	t.not(false, true, 'not');
	t.deep([1, 2], [1, 2], 'deep');
	t.notDeep([1, 2], [2, 1], 'not deep');
	t.throws(function () { throw new CustomError('Test', 'TEST'); }, 'TEST',
		'throws');

	a.deep([logger[0].type, logger[0].data], ['pass', 'foo']);
	a.deep([logger[1].type, logger[1].data.message], ['fail', 'bar']);
	a.deep([logger[2].type, logger[2].data], ['pass', 'not'], "'not' support");
	a.deep([logger[3].type, logger[3].data], ['pass', 'deep'], "'deep' support");
	a.deep([logger[4].type, logger[4].data], ['pass', 'not deep'],
		"'not deep' support");
	a.deep([logger[5].type, logger[5].data], ['pass', 'throws'],
		"custom trhows support");
};

'use strict';

var customError = require('es5-ext/error/custom')
  , logger      = require('../lib/logger')();

module.exports = function (t, a) {
	t = t(logger);
	t(true, true, 'foo');
	t.ok(false, 'bar');
	t.not(false, true, 'not');
	t.deep([1, 2], [1, 2], 'deep');
	t.notDeep([1, 2], [2, 1], 'not deep');
	t.throws(function () { throw customError('Test', 'TEST'); }, 'TEST',
		'throws');

	a.deep([logger[0].type, logger[0].data], ['pass', 'foo']);
	a.deep([logger[1].type, logger[1].data.message], ['fail', 'bar']);
	a.deep([logger[2].type, logger[2].data], ['pass', 'not'], "'not' support");
	a.deep([logger[3].type, logger[3].data], ['pass', 'deep'], "'deep' support");
	a.deep([logger[4].type, logger[4].data], ['pass', 'not deep'],
		"'not deep' support");
	a.deep([logger[5].type, logger[5].data], ['pass', 'throws'],
		"custom trhows support");
};

Update up to changes in es5-ext


import express from 'express';
import { apolloExpress, graphiqlExpress } from 'apollo-server';
import bodyParser from 'body-parser';
import cors from 'cors';
import { createServer } from 'http';
import { SubscriptionServer } from 'subscriptions-transport-ws';

import { subscriptionManager } from './data/subscriptions';
import schema from './data/schema';

const GRAPHQL_PORT = 8080;
const WS_PORT = 8090;

const graphQLServer = express().use('*', cors());

graphQLServer.use('/graphql', bodyParser.json(), apolloExpress({
  schema,
  context: {},
}));

graphQLServer.use('/graphiql', graphiqlExpress({
  endpointURL: '/graphql',
}));

graphQLServer.listen(GRAPHQL_PORT, () => console.log(
  `GraphQL Server is now running on http://localhost:${GRAPHQL_PORT}/graphql`
));

// WebSocket server for subscriptions
const websocketServer = createServer((request, response) => {
  response.writeHead(404);
  response.end();
});

websocketServer.listen(WS_PORT, () => console.log( // eslint-disable-line no-console
  `Websocket Server is now running on http://localhost:${WS_PORT}`
));

// eslint-disable-next-line
new SubscriptionServer(
  { subscriptionManager },
  websocketServer
);

import express from 'express';
import { apolloExpress, graphiqlExpress } from 'apollo-server';
import bodyParser from 'body-parser';
import cors from 'cors';
import { createServer } from 'http';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { printSchema } from 'graphql/utilities/schemaPrinter'

import { subscriptionManager } from './data/subscriptions';
import schema from './data/schema';

const GRAPHQL_PORT = 8080;
const WS_PORT = 8090;

const graphQLServer = express().use('*', cors());

graphQLServer.use('/graphql', bodyParser.json(), apolloExpress({
  schema,
  context: {},
}));

graphQLServer.use('/graphiql', graphiqlExpress({
  endpointURL: '/graphql',
}));

graphQLServer.use('/schema', function(req, res, _next) {
  res.set('Content-Type', 'text/plain');
  res.send(printSchema(schema));
});

graphQLServer.listen(GRAPHQL_PORT, () => console.log(
  `GraphQL Server is now running on http://localhost:${GRAPHQL_PORT}/graphql`
));

// WebSocket server for subscriptions
const websocketServer = createServer((request, response) => {
  response.writeHead(404);
  response.end();
});

websocketServer.listen(WS_PORT, () => console.log( // eslint-disable-line no-console
  `Websocket Server is now running on http://localhost:${WS_PORT}`
));

// eslint-disable-next-line
new SubscriptionServer(
  { subscriptionManager },
  websocketServer
);

Add /schema path that serves schema as plain text


// server.js

'use strict';

const express        = require('express');
const app            = express();
const mongoose       = require('mongoose');
const morgan         = require('morgan');
const bodyParser     = require('body-parser');     // Pull info from HTML POST
const methodOverride = require('method-override'); // Simulate DELETE and PUT
const argv           = require('yargs');

// =============================================================================
// Configuration

// DB Config
const DB_URL = argv.db || process.env.DATABASE_URL;
mongoose.connect(DB_URL);

// Set static files location /public/img will be /img for users
app.use(express.static(__dirname + '/public'));

// Log every request to the console
app.use(morgan('dev'));

// Parse application/x-www-form-unlencoded, JSON, and vnd.api+json as JSON
app.use(bodyParser.urlencoded({'extended':'true'}));
app.use(bodyParser.json());
app.use(bodyParser.json({ type: 'application/vnd.api+json' }));

app.use(methodOverride());

// =============================================================================
// Initilize app
app.listen(8080);
console.log('App is running on port 8080');

// server.js

//jscs:disable requireTrailingComma, disallowQuotedKeysInObjects

'use strict';

const express        = require('express');
const app            = express();
const mongoose       = require('mongoose');
const morgan         = require('morgan');
const bodyParser     = require('body-parser');     // Pull info from HTML POST
const methodOverride = require('method-override'); // Simulate DELETE and PUT
const argv           = require('yargs');

// =============================================================================
// Configuration

// DB Config
const DB_URL = argv.db || process.env.DATABASE_URL;
mongoose.connect(DB_URL);

// Set static files location /public/img will be /img for users
app.use(express.static(__dirname + '/public'));

// Log every request to the console
app.use(morgan('dev'));

// Parse application/x-www-form-unlencoded, JSON, and vnd.api+json as JSON
app.use(bodyParser.urlencoded({'extended':'true'}));
app.use(bodyParser.json());
app.use(bodyParser.json({ type: 'application/vnd.api+json' }));

app.use(methodOverride());

// =============================================================================
// Initilize app
app.listen(8080);
console.log('App is running on port 8080');

Disable some annoying JSCS stuff


'use strict';

const app = require('./app');
require('./api.js')(app);
require('./static.js')(app);

const PORT = process.env.PORT || 4000;

app.listen(PORT, () => {
  console.log(`App listening on port ${PORT}!`);
});

'use strict';

const app = require('./app');

// remove the automatically added `X-Powered-By` header
app.disable('x-powered-by');

require('./api.js')(app);
require('./static.js')(app);

const PORT = process.env.PORT || 4000;

app.listen(PORT, () => {
  console.log(`App listening on port ${PORT}!`);
});

Remove the `X-Powered-By` header added by Express


module("Hide department children", {
  setup: function() {
    this.$departments = $(
      '
'
    +   '
'
    +     '
'
    +       '
child content
'
    +     '
'
    +   '
'
    + '
');

    $('#qunit-fixture').append(this.$departments);
  }
});

test("should create toggle link before department list", function() {
  GOVUK.hideDepartmentChildren.init();
  equals(this.$departments.find('.view-all').length, 1);
  console.log(this.$departments.html());
});

test("should toggle class when clicking view all link", function() {
  GOVUK.hideDepartmentChildren.init();

  ok(this.$departments.find('.department').hasClass('js-hiding-children'));
  this.$departments.find('.view-all').click();
  ok(!this.$departments.find('.department').hasClass('js-hiding-children'));
});

module("Hide department children", {
  setup: function() {
    this.$departments = $(
      '
'
    +   '
'
    +     '
'
    +       '
child content
'
    +     '
'
    +   '
'
    + '
');

    $('#qunit-fixture').append(this.$departments);
  }
});

test("should create toggle link before department list", function() {
  GOVUK.hideDepartmentChildren.init();
  equals(this.$departments.find('.view-all').length, 1);
});

test("should toggle class when clicking view all link", function() {
  GOVUK.hideDepartmentChildren.init();

  ok(this.$departments.find('.department').hasClass('js-hiding-children'));
  this.$departments.find('.view-all').click();
  ok(!this.$departments.find('.department').hasClass('js-hiding-children'));
});

Remove left over console log


import app from 'ampersand-app';
import Model from 'ampersand-model';

import querystring from 'querystring';

export default Model.extend({
  urlRoot: '/api/experiments',
  extraProperties: 'allow',
  props: {
    enabled: {type: 'boolean', default: false}
  },
  // This shouldn't be necessary; see comments in collections/experiments.js
  ajaxConfig: { headers: { 'Accept': 'application/json' }},

  // TODO(DJ): this will be removed when we start tracking
  // install state through the User Installation model.
  // https://github.com/mozilla/testpilot/issues/195
  initialize() {
    app.on('addon-install:install-ended', addon => {
      if (addon.id === this.id) {
        this.enabled = true;
      }
    });

    app.on('addon-uninstall:uninstall-ended', addon => {
      if (addon.id === this.id) {
        this.enabled = false;
      }
    });
  },

  buildSurveyURL(ref) {
    const installed = Object.keys(app.me.installed);
    const queryParams = querystring.stringify({
      ref: ref,
      experiment: this.title,
      installed: installed
    });
    return `${this.survey_url}?${queryParams}`;
  }
});

import app from 'ampersand-app';
import Model from 'ampersand-model';

import querystring from 'querystring';

export default Model.extend({
  urlRoot: '/api/experiments',
  extraProperties: 'allow',
  props: {
    enabled: {type: 'boolean', default: false}
  },
  // This shouldn't be necessary; see comments in collections/experiments.js
  ajaxConfig: { headers: { 'Accept': 'application/json' }},

  // TODO(DJ): this will be removed when we start tracking
  // install state through the User Installation model.
  // https://github.com/mozilla/testpilot/issues/195
  initialize() {
    app.on('addon-install:install-ended', addon => {
      if (addon.id === this.id) {
        this.enabled = true;
      }
    });

    app.on('addon-uninstall:uninstall-ended', addon => {
      if (addon.id === this.id) {
        this.enabled = false;
      }
    });
  },

  buildSurveyURL(ref) {
    const queryParams = querystring.stringify({
      ref: ref,
      experiment: this.title,
      installed: app.me.installed ? Object.keys(app.me.installed) : []
    });
    return `${this.survey_url}?${queryParams}`;
  }
});

Fix fail on Object.keys(app.me.installed) which was holding up page loads.


// Script executed every time the devtools are opened.

// custom panel
chrome.devtools.panels.create("Backbone Debugger", "img/panel.png", "panel.html");

// custom sidebar pane in the elements panel
chrome.devtools.panels.elements.createSidebarPane("Backbone Debugger", function(sidebar) {
    chrome.devtools.panels.elements.onSelectionChanged.addListener(function() {
        sidebar.setHeight("35px");
        sidebar.setPage("elementsSidebar.html");
    });
});

// Script executed every time the devtools are opened.

// custom panel
chrome.devtools.panels.create("Backbone", "img/panel.png", "panel.html");

// custom sidebar pane in the elements panel
chrome.devtools.panels.elements.createSidebarPane("Backbone", function(sidebar) {
    chrome.devtools.panels.elements.onSelectionChanged.addListener(function() {
        sidebar.setHeight("35px");
        sidebar.setPage("elementsSidebar.html");
    });
});

Change the name of Panel and Elements sidebar pane to 'Backbone'


// Problem: We need a simple way to look at a user's badge count and JavaScript point from a web browser
// Solution: Use Node.js to perform the profile look ups and server our template via HTTP

// 1. Create a web server
var http = require('http');
http.createServer(function (request, response) {
  response.writeHead(200, {'Content-Type': 'text/plain'});
  response.write('This is before the end\n');
  response.end('Hello World\n');
}).listen(3000);
console.log('Server is running on port 3000');

// 2. Handle HTTP route GET / and POST / i.e. Home
	// if url == "/" && GET
	  // show search
	// if url == "/" && POST
	  // redirect to /:username

// 3. Handle HTTP route GET /:username i.e. /chalkers
	// if url == "/...."
	  // get json from Treehouse
	    // on "end"
	      // show profile
	    // on "error"
	      // show error

// 4. Function that handles the reading of files and merge in value.
	// read from file and get a string
	  // merge values in to string

// Problem: We need a simple way to look at a user's badge count and JavaScript point from a web browser
// Solution: Use Node.js to perform the profile look ups and server our template via HTTP

// 1. Create a web server
var http = require('http');
http.createServer(function (request, response) {
  homeRoute(request, response);
}).listen(3000);
console.log('Server is running on port 3000');

// 2. Handle HTTP route GET / and POST / i.e. Home
function homeRoute(request, response) {
    // if url == "/" && GET
    if (request.url === "/") {
	   // show search
	   response.writeHead(200, {'Content-Type': 'text/plain'});
       response.write('Header\n');
       response.write('Search\n');
       response.end('Footer\n');
     }
	// if url == "/" && POST
	  // redirect to /:username
}
// 3. Handle HTTP route GET /:username i.e. /chalkers
	// if url == "/...."
	  // get json from Treehouse
	    // on "end"
	      // show profile
	    // on "error"
	      // show error

// 4. Function that handles the reading of files and merge in value.
	// read from file and get a string
	  // merge values in to string

Create Web Server & Handle route GET and POST


var express = require('express');
var bunyan = require('bunyan');
var log = bunyan.createLogger({ name: 'app' });

var app = express();

var webhooks = require('datashaman-webhooks');
webhooks.boot(app, 'somesecret');

app.post('/', webhooks.router(function(req, res, event) {
    log.info(req.body, event);

    switch (event) {
    case 'ping':
        res.send('Ping');
    }
}));

app.listen(8080);

var express = require('express');
var bunyan = require('bunyan');
var log = bunyan.createLogger({ name: 'app' });

var app = express();

var webhooks = require('datashaman-webhooks');
webhooks.boot(app, 'somesecret');

app.post('/', webhooks.router(function(req, res, event) {
    log.info(req.body, event);

    switch (event) {
    case 'ping':
        res.send('Ping');
    break;
    default:
        res.send('Unhandled event: ' + event);
    }
}));

app.listen(8080);

Add default case to example file


const Sequelize = require('sequelize');
const db = require('../db.js');

const Users = db.define('users',
  {
    id: {
      type: Sequelize.UUID,
      defaultValue: Sequelize.UUIDV1,
      primaryKey: true,
    },
    facebook_id: {
      type: Sequelize.STRING,
    },
    first_name: {
      type: Sequelize.STRING,
    },
    last_name: {
      type: Sequelize.STRING,
    },
    photo_url: {
      type: Sequelize.STRING,
    },
    description: {
      type: Sequelize.STRING(256),
      defaultValue: 'Click here to edit!',
    },
    // would like to add counter cache for request count and connection count
  },
  {
    freezeTableName: true,
  }
);

module.exports = Users;

const Sequelize = require('sequelize');
const db = require('../db.js');

const Users = db.define('users',
  {
    id: {
      type: Sequelize.UUID,
      defaultValue: Sequelize.UUIDV1,
      primaryKey: true,
    },
    facebook_id: {
      type: Sequelize.STRING,
    },
    first_name: {
      type: Sequelize.STRING,
    },
    last_name: {
      type: Sequelize.STRING,
    },
    photo_url: {
      type: Sequelize.STRING,
    },
    description: {
      type: Sequelize.STRING(256),
    },
    // would like to add counter cache for request count and connection count
  },
  {
    freezeTableName: true,
  }
);

module.exports = Users;

Remove default description on user model


'use strict'

const mongoose = require('mongoose')

const PermissionSchema = new mongoose.Schema({
  regex: { type: String, required: true, unique: true },
  roles: { type: [ String ], required: true },
  dateCreated: { type: Date, default: Date.now }
})

PermissionSchema.statics.findMatches = function (email, cb) {
  const collected = []

  this.find({}, function (err, permissions) {
    if (err) cb(err)

    for (let i = 0; i < permissions.length; i++) {
      const regex = new RegExp(permissions[i].regex)

      if (email.search(regex) > -1) collected.push(permissions[i])
    }

    cb(null, collected)
  })
}

module.exports = mongoose.model('Permission', PermissionSchema)

'use strict'

const mongoose = require('mongoose')

const PermissionSchema = new mongoose.Schema({
  regex: { type: Object, required: true, unique: true },
  roles: { type: [ String ], required: true },
  dateCreated: { type: Date, default: Date.now }
})

PermissionSchema.statics.findMatches = function (email, cb) {
  this.find({}, (err, permissions) => {
    if (err) cb(err)

    // return only permissions whose regex match the email
    cb(null, permissions ? permissions.filter(permission => permission.regex.test(email)) : null)
  })
}

PermissionSchema.path('regex').set(function (regex) {
  return regex instanceof RegExp ? regex : new RegExp(regex)
})

module.exports = mongoose.model('Permission', PermissionSchema)

Save RegExp object to database instead of string, and more concise filter


#!/usr/bin/env node
'use strict';

/** Load modules. */
var fs = require('fs'),
    path = require('path');

/** Resolve program params. */
var args = process.argv.slice(process.argv[0] === process.execPath ? 2 : 0),
    filePath = path.resolve(args[1]);

var pattern = (function() {
  var result = args[0],
      delimiter = result.charAt(0),
      lastIndex = result.lastIndexOf(delimiter);

  return RegExp(result.slice(1, lastIndex), result.slice(lastIndex + 1));
}());

/** Used to match lines of code. */
var reLine = /.*/gm;

/*----------------------------------------------------------------------------*/

fs.writeFileSync(filePath, fs.readFileSync(filePath, 'utf8').replace(pattern, function(match) {
  return match.replace(reLine, '');
}));

#!/usr/bin/env node
'use strict';

var fs = require('fs'),
    path = require('path');

var args = (args = process.argv)
  .slice((args[0] === process.execPath || args[0] === 'node') ? 2 : 0);

var filePath = path.resolve(args[1]),
    reLine = /.*/gm,
    slice = Array.prototype.slice;

var pattern = (function() {
  var result = args[0],
      delimiter = result.charAt(0),
      lastIndex = result.lastIndexOf(delimiter);

  return RegExp(result.slice(1, lastIndex), result.slice(lastIndex + 1));
}());

/*----------------------------------------------------------------------------*/

fs.writeFileSync(filePath, fs.readFileSync(filePath, 'utf8').replace(pattern, function(match) {
  var snippet = slice.call(arguments, -3, -1)[0];
  return match.replace(snippet, snippet.replace(reLine, ''));
}));

Add support for removing the last capture group.


Vue.component('spark-kiosk-notify', {
    props: [
    ],
    data() {
        return {
            'notifications': [],
            'users': [],
            'newNotification': {
                "user_id": null
            }
        };
    },
    ready(){
        this.getNotifications();
        this.getUsers();
    },
    methods: {
        /**
         * Get all of the announcements.
         */
        getNotifications: function(){
            this.$http.get('/skn/notifications')
                .then(response => {
                    this.notifications = response.data;
                });
        },

        /**
         * Get all of the users.
         */
        getUsers: function(){
            this.$http.get('/skn/users')
                .then(response => {
                    this.users = response.data;
                });
        },

        /**
         * Create Notification.
         */
        createNotification: function(){
            this.$http.get('/skn/notifications/create', this.createNotification)
                .then(response => {
                    this.createNotification = {};
                    this.getNotifications();
                });
        }

    }
});

Vue.component('spark-kiosk-notify', {
    props: [
    ],
    data() {
        return {
            'notifications': [],
            'users': [],
            'newNotification': {
                "user_id": null
            }
        };
    },
    ready(){
        this.getNotifications();
        this.getUsers();
    },
    methods: {
        /**
         * Get all of the announcements.
         */
        getNotifications: function(){
            this.$http.get('/skn/notifications')
                .then(response => {
                    this.notifications = response.data;
                });
        },

        /**
         * Get all of the users.
         */
        getUsers: function(){
            this.$http.get('/skn/users')
                .then(response => {
                    this.users = response.data;
                });
        },

        /**
         * Create Notification.
         */
        createNotification: function(){
            this.$http.post('/skn/notifications/create', this.newNotification)
                .then(response => {
                    this.newNotification = {};
                    this.getNotifications();
                });
        }

    }
});

Change to 'newNotification', change to post


hqDefine('domain/js/case_search_main', [
    'jquery',
    'hqwebapp/js/initial_page_data',
    'domain/js/case_search',
], function(
    $,
    initialPageData,
    caseSearch
) {
    $(function () {
        var viewModel = caseSearch.CaseSearchConfig({
            values: initialPageData.get('values'),
            caseTypes: initialPageData.get('case_types'),
        });
        $('#case-search-config').koApplyBindings(viewModel);
        $('#case-search-config').on('change', viewModel.change).on('click', ':button', viewModel.change);
    });
});

hqDefine('domain/js/case_search_main', [
    'jquery',
    'hqwebapp/js/initial_page_data',
    'domain/js/case_search',
    'hqwebapp/js/knockout_bindings.ko',     // save button
], function(
    $,
    initialPageData,
    caseSearch
) {
    $(function () {
        var viewModel = caseSearch.CaseSearchConfig({
            values: initialPageData.get('values'),
            caseTypes: initialPageData.get('case_types'),
        });
        $('#case-search-config').koApplyBindings(viewModel);
        $('#case-search-config').on('change', viewModel.change).on('click', ':button', viewModel.change);
    });
});

Add knockout bindings as dependency to case search


/**
 * Created by Neil on 29/09/13.
 */
function signin(mode, callback, clientID) { // clientID filled in by template, immediate = true because we should not need to ask permission again
    gapi.auth.authorize({client_id: clientID,
            scope: ["https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"], immediate: true,
            response_type: 'token'}, // Can't use id tokens as we can't get the user if from an id token
        callback);
}

function userAuthed() {

    var request =
        gapi.client.oauth2.userinfo.get().execute(function (resp) { // Check the token by calling userinfo, if it's ok call our end point
            if (!resp.code) {
                var token = gapi.auth.getToken();
                gapi.client.mylatitude.location.last().execute(function (resp) { // this does not do anything yet it's just a test.
//                    console.log(resp);
                });
            }
        });
}

/**
 * Created by Neil on 29/09/13.
 */
function signin(mode, callback, clientID) { // clientID filled in by template, immediate = true because we should not need to ask permission again
    gapi.auth.authorize({client_id: clientID,
            scope: ["https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"], immediate: true,
            response_type: 'token'}, // Can't use id tokens as we can't get the user if from an id token
        callback);
}

function userAuthed() {

    var request =
        gapi.client.oauth2.userinfo.get().execute(function (resp) { // Check the token by calling userinfo, if it's ok call our end point
            if (!resp.code) {
                var token = gapi.auth.getToken();
                gapi.client.mylatitude.locations.latest().execute(function (resp) { // this does not do anything yet it's just a test.
//                    console.log(resp);
                });
            }
        });
}

Change the name of the endpoint


'use babel';

export default {
  binary: {
    title: 'Binary path',
    description: 'Path for elm-format',
    type: 'string',
    default: '/usr/local/bin/elm-format',
    order: 1,
  },
  formatOnSave: {
    title: 'Format on save',
    description: 'Do we format when you save files?',
    type: 'boolean',
    default: true,
    order: 2,
  },
  showNotifications: {
    title: 'Show notifications on save',
    description: 'Do you want to see the bar when we save?',
    type: 'boolean',
    default: false,
    order: 3,
  },
  showErrorNotifications: {
    title: 'Show error notifications on save',
    description: 'Do you want to see the bar when we save?',
    type: 'boolean',
    default: true,
    order: 4,
  },
};

'use babel';

export default {
  binary: {
    title: 'Binary path',
    description: 'Path for elm-format',
    type: 'string',
    default: 'elm-format',
    order: 1,
  },
  formatOnSave: {
    title: 'Format on save',
    description: 'Do we format when you save files?',
    type: 'boolean',
    default: true,
    order: 2,
  },
  showNotifications: {
    title: 'Show notifications on save',
    description: 'Do you want to see the bar when we save?',
    type: 'boolean',
    default: false,
    order: 3,
  },
  showErrorNotifications: {
    title: 'Show error notifications on save',
    description: 'Do you want to see the bar when we save?',
    type: 'boolean',
    default: true,
    order: 4,
  },
};

Use "elm-format" as the default binary path


import styleBody from 'ghost/mixins/style-body';
import AuthenticatedRoute from 'ghost/routes/authenticated';

var paginationSettings = {
    status: 'all',
    staticPages: 'all',
    page: 1,
    limit: 15
};

var PostsRoute = AuthenticatedRoute.extend(styleBody, {
    classNames: ['manage'],

    model: function () {
        // using `.filter` allows the template to auto-update when new models are pulled in from the server.
        // we just need to 'return true' to allow all models by default.
        return this.store.filter('post', paginationSettings, function () {
            return true;
        });
    },

    setupController: function (controller, model) {
        this._super(controller, model);
        controller.set('paginationSettings', paginationSettings);
    },

    actions: {
        openEditor: function (post) {
            this.transitionTo('editor', post);
        }
    }
});

export default PostsRoute;

import styleBody from 'ghost/mixins/style-body';
import AuthenticatedRoute from 'ghost/routes/authenticated';

var paginationSettings = {
    status: 'all',
    staticPages: 'all',
    page: 1
};

var PostsRoute = AuthenticatedRoute.extend(styleBody, {
    classNames: ['manage'],

    model: function () {
        // using `.filter` allows the template to auto-update when new models are pulled in from the server.
        // we just need to 'return true' to allow all models by default.
        return this.store.filter('post', paginationSettings, function () {
            return true;
        });
    },

    setupController: function (controller, model) {
        this._super(controller, model);
        controller.set('paginationSettings', paginationSettings);
    },

    actions: {
        openEditor: function (post) {
            this.transitionTo('editor', post);
        }
    }
});

export default PostsRoute;

Remove limit from ember post API calls