/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import { DataTablePageReducer } from '../pages/dataTableUtilities/reducer/DataTablePageReducer';
import getPageInitialiser from '../pages/dataTableUtilities/getPageInitialiser';

/**
 * Redux reducer controlling the `pages` field.
 * @param {Object} state  Redux state, `pages` field.
 * @param {Object} action An action object, following the FSA-standard for action objects
 */
export const PagesReducer = (state = {}, action) => {
  const { type } = action;

  switch (type) {
    case 'Navigation/BACK': {
      const { payload } = action;
      const { prevRouteName } = payload || {};
      return { ...state, currentRoute: prevRouteName };
    }
    case 'Navigation/REPLACE':
    case 'Navigation/NAVIGATE': {
      const { routeName, params } = action;
      const { pageObject } = params;

      const pageInitialiser = getPageInitialiser(routeName);
      const pageInitialState = pageInitialiser?.(pageObject);

      return { ...state, [routeName]: pageInitialState, currentRoute: routeName };
    }
    default: {
      const { payload } = action;
      const { route } = payload || {};

      const newState = route
        ? { ...state, [route]: DataTablePageReducer(state[route], action) }
        : state;
      return newState;
    }
  }
};

/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import { DataTablePageReducer } from '../pages/dataTableUtilities/reducer/DataTablePageReducer';
import getPageInitialiser from '../pages/dataTableUtilities/getPageInitialiser';
import { FINALISE_ACTIONS } from '../actions/FinaliseActions';

/**
 * Redux reducer controlling the `pages` field.
 * @param {Object} state  Redux state, `pages` field.
 * @param {Object} action An action object, following the FSA-standard for action objects
 */
export const PagesReducer = (state = {}, action) => {
  const { type } = action;

  switch (type) {
    case 'Navigation/BACK': {
      const { payload } = action;
      const { prevRouteName } = payload || {};
      return { ...state, currentRoute: prevRouteName };
    }

    case FINALISE_ACTIONS.CONFIRM_FINALISE: {
      return { ...state };
    }

    case 'Navigation/REPLACE':
    case 'Navigation/NAVIGATE': {
      const { routeName, params } = action;
      const { pageObject } = params;

      const pageInitialiser = getPageInitialiser(routeName);
      const pageInitialState = pageInitialiser?.(pageObject);

      return { ...state, [routeName]: pageInitialState, currentRoute: routeName };
    }

    default: {
      const { payload } = action;
      const { route } = payload || {};

      const newState = route
        ? { ...state, [route]: DataTablePageReducer(state[route], action) }
        : state;
      return newState;
    }
  }
};

Add finalise action handling in pages reducer


var postmortem = require('./lib');

// Automatically install postmortem when this postmortem/register is required.
postmortem.install();

module.exports = postmortem;

var postmortem = require('./lib');

// Automatically install postmortem when this postmortem/register is required.
postmortem.install({handleUncaughtExceptions: false});

module.exports = postmortem;

Make postmortem play nice with mocha.


import Vue from "vue";
import Router from "vue-router";
import Search from "./views/Search.vue";
import Live from "@/views/Live.vue";
// import Stats from '@/views/Stats.vue'

Vue.use(Router);

export default new Router({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
      path: "/",
      name: "search",
      component: Search,
    },
    {
      path: "/live",
      name: "live",
      component: Live,
    },
    {
      path: "/stats",
      name: "stats",
      component: () =>
        import(/* webpackChunkName: "stats" */ "./views/Stats.vue"),
    },
  ],
});

import Vue from "vue";
import Router from "vue-router";
import Search from "./views/Search.vue";
// import Live from "@/views/Live.vue";
// import Stats from '@/views/Stats.vue';

Vue.use(Router);

export default new Router({
  mode: "history",
  base: process.env.BASE_URL,
  linkActiveClass: "is-active",
  routes: [
    {
      path: "/",
      name: "search",
      component: Search,
    },
    {
      path: "/live",
      name: "live",
      component: () =>
        import(/* webpackChunkName: "live" */ "./views/Live.vue"),
    },
    {
      path: "/stats",
      name: "stats",
      component: () =>
        import(/* webpackChunkName: "stats" */ "./views/Stats.vue"),
    },
  ],
});

Set Live page as a webpack chunk


import React, { Component } from "react"
import { Route } from "react-router"

import AppContainer from "./AppContainer"
import { PageContainer as PhenomicPageContainer } from "phenomic"

import Page from "./layouts/Page"
import PageError from "./layouts/PageError"
import PageLoading from "./layouts/PageLoading"
import Homepage from "./layouts/Homepage"
import Archive from "./layouts/Archive"
import Post from "./layouts/Post"
import Tag from "./layouts/Tag"

class PageContainer extends Component {
  render() {
    const { props } = this
    return (
      <PhenomicPageContainer
        { ...props }
        layouts={ {
          Page,
          PageError,
          PageLoading,
          Homepage,
          Archive,
          Post,
        } }
        logger={ {
          ...console,
          info: () => {},
        } }
      />
    )
  }
}

export default (
  <Route component={ AppContainer }>
    <Route path="tag/:tag" component={ Tag } />
    <Route path="*" component={ PageContainer } />
  </Route>
)

import React, { Component } from "react"
import { Route } from "react-router"

import AppContainer from "./AppContainer"
import { default as PhenomicPageContainer } from "phenomic/lib/PageContainer"
import Page from "./layouts/Page"
import PageError from "./layouts/PageError"
import PageLoading from "./layouts/PageLoading"
import Homepage from "./layouts/Homepage"
import Archive from "./layouts/Archive"
import Post from "./layouts/Post"
import Tag from "./layouts/Tag"

class MainPageContainer extends Component {
  render() {
    const { props } = this
    return (
      <PhenomicPageContainer
        { ...props }
        layouts={ {
          Page,
          PageError,
          PageLoading,
          Homepage,
          Archive,
          Post,
        } }
        logger={ {
          ...console,
          info: () => {},
        } }
      />
    )
  }
}

export default (
  <Route component={ AppContainer }>
    <Route path="tag/:tag" component={ Tag } />
    <Route path="*" component={ MainPageContainer } />
  </Route>
)

Fix import path of PageContainer


var najax = require("najax");
var Promise = require('promise');
var Snowflake = function Snowflake(snowflakeUrl) {
    var instance = this;

    this.snowflakeUrl = snowflakeUrl;
    this.platforms = {};
    this.games = {};
    this.loadGames = function () {
        return this.ajax('', '').then(function (response) {
            instance.games = response;
        }).catch(function (fail) {
            console.log(fail);
        });
    };
    this.loadPlatforms = function () {
    };

    this.ajax = function (namespace, methodname) {
        return Promise.resolve(najax('http://www.google.com'));
    }

}

exports.Snowflake = Snowflake;

var najax = require("najax");
var Promise = require('promise');
var Snowflake = function Snowflake(snowflakeUrl) {
    var instance = this;

    this.snowflakeUrl = snowflakeUrl;
    this.platforms = {};
    this.games = {};
    this.loadGames = function () {
        return this.ajax('Core', 'Game.GetAllGames').then(function (response) {
            instance.games = JSON.parse(response);
        }).catch(function (fail) {
            console.log(fail);
        });
    };
    this.loadPlatforms = function () {
        return this.ajax('Core', 'Platform.GetAllPlatforms').then(function (response) {
            instance.platforms = JSON.parse(response);
        }).catch(function (fail) {
            console.log(fail);
        });
    };

    this.ajax = function (namespace, methodname) {
        return Promise.resolve(najax(snowflakeUrl + '/' + namespace + '/' + methodname));
    }
}

exports.Snowflake = Snowflake;

Make load methods call proper ajax urls


"use strict";

const _ = require("lodash");

/**
 * Set Serverless environment variables for local invocations
 *
 * @param {Serverless} serverless - Serverless Instance
 * @param {Map<String>} environment - Resolved environment
 */
function setEnvVariables(serverless, environment) {
	const functions = _.get(serverless, "service.functions", {});

	// Set the function environment for all functions that define one
	_.each(_.keys(functions), funcName => {
		const funcEnv = functions[funcName].environment;

		if (funcEnv) {
			_.assign(funcEnv, _.reduce(funcEnv, (acc, value, key) => {
				acc[key] = environment[key];
			}, {}));
		}
	});

	// Set the global environment
	_.assign(serverless.service.provider.environment, environment);
}

module.exports = setEnvVariables;

"use strict";

const _ = require("lodash");

/**
 * Set Serverless environment variables for local invocations
 *
 * @param {Serverless} serverless - Serverless Instance
 * @param {Map<String>} environment - Resolved environment
 */
function setEnvVariables(serverless, environment) {
	const functions = _.get(serverless, "service.functions", {});

	// Set the function environment for all functions that define one
	_.each(_.keys(functions), funcName => {
		const funcEnv = functions[funcName].environment;

		if (funcEnv) {
			_.assign(funcEnv, _.reduce(funcEnv, (acc, value, key) => {
				acc[key] = environment[key];
				return acc;
			}, {}));
		}
	});

	// Set the global environment
	_.assign(serverless.service.provider.environment, environment);
}

module.exports = setEnvVariables;

Fix issue with multiple functions


import * as v from '../../styles/variables'

const styles = {
  section: {
    display: 'flex',
    flexWrap: 'wrap',
    justifyContent: 'space-between',
    alignItems: 'stretch',
    alignContent: 'flex-start',
    width: '100%'
  },
  container: {
    position: 'relative',
    width: '100%'
  },
  dataInput: {
    position: 'absolute',
    width: '100%',
    height: '100%',
    resize: 'none',
    wordWrap: 'normal',
    padding: 2
  },
  textAreaSpacer: {
    width: '100%',
    wordWrap: 'normal',
    whiteSpace: 'pre',
    padding: {
      top: 2,
      right: `${v.lnht}rem`,
      bottom: `${v.lnht}rem`,
      left: 2
    },
    borderWidth: 1,
    visibility: 'hidden'
  },
  inputSection: {
    minWidth: `${3 * v.ms6}rem`,
    flexGrow: 2,
    padding: `0 ${v.lnht / 2}rem`
  }
}

export default styles

import * as v from '../../styles/variables'

const styles = {
  section: {
    display: 'flex',
    flexWrap: 'wrap',
    justifyContent: 'space-between',
    alignItems: 'stretch',
    alignContent: 'flex-start',
    width: '100%'
  },
  container: {
    position: 'relative',
    width: '100%',
    height: `calc(100% - ${2 * v.lnht}rem)` /* 100% minus the header height */
  },
  dataInput: {
    position: 'absolute',
    width: '100%',
    height: '100%',
    resize: 'none',
    wordWrap: 'normal',
    padding: 2
  },
  textAreaSpacer: {
    width: '100%',
    height: '100%',
    wordWrap: 'normal',
    whiteSpace: 'pre',
    padding: {
      top: 2,
      right: `${v.lnht}rem`,
      bottom: `${v.lnht}rem`,
      left: 2
    },
    borderWidth: 1,
    visibility: 'hidden'
  },
  inputSection: {
    minWidth: `${3 * v.ms6}rem`,
    flexGrow: 2,
    padding: `0 ${v.lnht / 2}rem`
  }
}

export default styles

Adjust MorphForm textarea boxes to fill available height


const assert = require("assert");
const sinon = require("sinon");
const request = require("request-promise");
const {
  LoadingStrategy
} = require("../../../compilerSupplier/loadingStrategies");
let result;

describe("LoadingStrategy base class", () => {
  beforeEach(() => {
    instance = new LoadingStrategy();
  });
});

const assert = require("assert");
const {
  LoadingStrategy
} = require("../../../compilerSupplier/loadingStrategies");
let result, expectedDefaultConfig;

describe("LoadingStrategy base class", () => {
  beforeEach(() => {
    instance = new LoadingStrategy();
    expectedDefaultConfig = {
      versionsUrl: "https://solc-bin.ethereum.org/bin/list.json",
      compilerUrlRoot: "https://solc-bin.ethereum.org/bin/",
      dockerTagsUrl:
        "https://registry.hub.docker.com/v2/repositories/ethereum/solc/tags/",
      cache: true
    };
  });

  it("has a config with some default values", () => {
    const {
      versionsUrl,
      compilerUrlRoot,
      dockerTagsUrl,
      cache
    } = instance.config;
    assert(versionsUrl === expectedDefaultConfig.versionsUrl);
    assert(compilerUrlRoot === expectedDefaultConfig.compilerUrlRoot);
    assert(dockerTagsUrl === expectedDefaultConfig.dockerTagsUrl);
    assert(cache === expectedDefaultConfig.cache);
  });
});

Add basic test for default config values


const { find } = require('lodash')

const { transformInvestmentProjectToListItem } = require('../investment-projects/transformers')
const { transformContactToListItem } = require('../contacts/transformers')
const { buildPagination } = require('../../lib/pagination')
const { buildSearchAggregation } = require('./builders')
const { entities } = require('./services')

function transformResultsToCollection (results, searchEntity, options = {}) {
  const resultsData = results[`${searchEntity}s`]
  if (!resultsData) { return null }

  const entity = find(entities, ['entity', searchEntity])

  let items = resultsData.map(item => Object.assign({}, item, { type: searchEntity }))

  if (searchEntity === 'investment_project') {
    items = items.map(transformInvestmentProjectToListItem)
  }

  if (searchEntity === 'contact') {
    items = items.map(transformContactToListItem)
  }

  return Object.assign({}, {
    items,
    count: results.count,
    countLabel: entity.noun,
    highlightTerm: options.searchTerm,
    pagination: buildPagination(options.query, results),
    aggregations: buildSearchAggregation(results.aggregations),
  })
}

module.exports = {
  transformResultsToCollection,
}

const { find } = require('lodash')

const { transformInvestmentProjectToListItem } = require('../investment-projects/transformers')
const { transformContactToListItem } = require('../contacts/transformers')
const { buildPagination } = require('../../lib/pagination')
const { buildSearchAggregation } = require('./builders')
const { entities } = require('./services')

function transformResultsToCollection (results, searchEntity, options = {}) {
  const resultsItems = results[`${searchEntity}s`] || results.items || results.results
  if (!resultsItems) { return null }

  const entity = find(entities, ['entity', searchEntity])

  let items = resultsItems.map(item => Object.assign({}, item, { type: searchEntity }))

  if (searchEntity === 'investment_project') {
    items = items.map(transformInvestmentProjectToListItem)
  }

  if (searchEntity === 'contact') {
    items = items.map(transformContactToListItem)
  }

  return Object.assign({}, {
    items,
    count: results.count,
    countLabel: entity.noun,
    highlightTerm: options.searchTerm,
    pagination: buildPagination(options.query, results),
    aggregations: buildSearchAggregation(results.aggregations),
  })
}

module.exports = {
  transformResultsToCollection,
}

Allow transformResultsToCollection to fallback to other props


const app         = new (require('koa'))()
const send        = require('koa-send')
const path        = require('path')
const config      = require('./config.json')

app.use(require('koa-static')(path.join(__dirname, '/../dist/')))
app.use(async (ctx, next) => {
  await send(ctx, (path.join(__dirname, '/../dist/index.html')))
})
app.listen(config.port)
console.log(`Start server on port ${config.port}.`)

const app         = new (require('koa'))()
const send        = require('koa-send')
const path        = require('path')
const config      = require('./config.json')

app.use(require('koa-static')(path.join(__dirname, '/../dist/')))
app.use(async (ctx, next) => {
  await send(ctx, (path.join('/../dist/index.html')))
})
app.listen(config.port)
console.log(`Start server on port ${config.port}.`)

Correct error handling route path.


const makeRequest = require('../common/makeRequest')
const PATHS = require('../common/paths')

/**
 * Make a request for street level crimes based on a location.
 * @param {number} latitude - A number representing a latitude value.
 * @param {number} longitude - A number representing a longitude value.
 * @returns {promise}
 */
module.exports = (latitude, longitude) => makeRequest(`${PATHS.STREET_LEVEL}?lat=${latitude}&lng=${longitude}`)

const makeRequest = require('../common/makeRequest')
const PATHS = require('../common/paths')

/**
 * Make a request for street level crimes based on a location.
 * @param {number} latitude - A number representing a latitude value.
 * @param {number} longitude - A number representing a longitude value.
 * @param {string} date - Optional. A date in the format YYYY-MM
 * @returns {promise}
 */
module.exports = (latitude, longitude, date) => makeRequest(`${PATHS.STREET_LEVEL}?lat=${latitude}&lng=${longitude}&date=${date}`)

Add date to street level


export const loadJSON = (url, postData = undefined) => new Promise((resolve, reject) => {
  const xhr = new XMLHttpRequest();
  xhr.open(postData ? 'post' : 'get', url, true);
  xhr.responseType = 'json';
  xhr.onload = () => {
    const { status } = xhr;
    if (status === 200) {
      resolve(xhr.response);
    } else {
      reject(xhr.response);
    }
  };
  xhr.onerror = () => reject();
  if (postData) {
    xhr.setRequestHeader('Content-type', 'application/json');
    xhr.send(JSON.stringify(postData));
  } else {
    xhr.send();
  }
});

export const loadJSON = (url, postData) => new Promise((resolve, reject) => {
  const xhr = new XMLHttpRequest();
  xhr.open(postData ? 'post' : 'get', url, true);
  xhr.responseType = 'json';
  xhr.onload = () => {
    const { status } = xhr;
    if (status === 200) {
      resolve(xhr.response);
    } else {
      reject(xhr.response);
    }
  };
  xhr.onerror = () => reject();
  if (postData) {
    xhr.setRequestHeader('Content-type', 'application/json');
    xhr.send(JSON.stringify(postData));
  } else {
    xhr.send();
  }
});

Remove undefined default parameter value


const qs = require('qs');

module.exports.handler = (event, context, callback) => {
  /** Immediate response for WarmUP plugin */
  if (event.source === 'serverless-plugin-warmup') {
    console.log('WarmUP - Lambda is warm!');
    return callback(null, 'Lambda is warm!');
  }

  const body = qs.parse(event.body);
  const result = body.text === 'coin'
    ? `:coin_${Math.floor((Math.random() * 2) + 1)}:`
    : `:dice_${Math.floor((Math.random() * 6) + 1)}:`;
  const response = {
    statusCode: 200,
    headers: {
      // Required for CORS support to work
      'Access-Control-Allow-Origin': '*',

      // Required for cookies, authorization headers with HTTPS
      'Access-Control-Allow-Credentials': true,
      'Content-type': 'application/json',
    },
    body: JSON.stringify({
      response_type: 'in_channel',
      text: `@${body.user_name || 'You'} just rolled: ${result}`,
    }),
  };

  callback(null, response);
};

const qs = require('qs');

module.exports.handler = (event, context, callback) => {
  /** Immediate response for WarmUP plugin */
  if (event.source === 'serverless-plugin-warmup') {
    console.log('WarmUP - Lambda is warm!');
    return callback(null, 'Lambda is warm!');
  }

  const body = qs.parse(event.body);
  const identity = body.user_name ? `@${body.user_name}` : 'You';
  const result = body.text === 'coin'
    ? `:coin_${Math.floor((Math.random() * 2) + 1)}:`
    : `:dice_${Math.floor((Math.random() * 6) + 1)}:`;
  const response = {
    statusCode: 200,
    headers: {
      // Required for CORS support to work
      'Access-Control-Allow-Origin': '*',

      // Required for cookies, authorization headers with HTTPS
      'Access-Control-Allow-Credentials': true,
      'Content-type': 'application/json',
    },
    body: JSON.stringify({
      response_type: 'in_channel',
      text: `${identity} just rolled: ${result}`,
    }),
  };

  callback(null, response);
};

Remove @ in front of 'You'.


import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import OverviewPageBody from './OverviewPageBody';
import OverviewPageHeader from './OverviewPageHeader';
import Box from '../box';

class OverviewPage extends PureComponent {
  render() {
    const { children, className, ...others } = this.props;

    return <Box {...others}>{children}</Box>;
  }
}

OverviewPage.propTypes = {
  children: PropTypes.node.isRequired,
};

OverviewPage.Body = OverviewPageBody;
OverviewPage.Header = OverviewPageHeader;

export default OverviewPage;

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import OverviewPageBody from './OverviewPageBody';
import OverviewPageHeader from './OverviewPageHeader';
import Box from '../box';

class OverviewPage extends PureComponent {
  render() {
    const { children, className, ...others } = this.props;

    return <Box {...others}>{children}</Box>;
  }
}

OverviewPage.propTypes = {
  children: PropTypes.node.isRequired,
};

OverviewPage.Body = OverviewPageBody;
OverviewPage.Body.displayName = 'OverviewPage.Body';
OverviewPage.Header = OverviewPageHeader;
OverviewPage.Header.displayName = 'OverviewPage.Header';

export default OverviewPage;

Set subcomponent display names for proper documentation


'use strict';

var auth = require('./auth.json');
var sinon = require('sinon');
var expect = require('chai').expect;
var should = require('chai').should();
var request = require('request');

describe('SAP module', function () {
  var sap;

  function initModule(auth) {
    sap = require('../index')(auth);
  }

  beforeEach(function () {
    sap = undefined;
    initModule(auth);
  });

  describe('initialization', function () {
    var error;

    beforeEach(function () {
      error = sinon.stub(console, 'error');
    });

    afterEach(function () {
      error.restore();
    });

    it('sets auth options', sinon.test(function () {
      should.exist(sap.client_id);
      should.exist(sap.client_secret);
      should.exist(sap.refresh_token);
    }));

    describe('when no options are passed', function () {
      it('logs an error to the console', function () {
        sap = undefined;
        initModule();

        sinon.assert.calledOnce(error);
      });
    });

    describe('when insufficient options are passed', function () {
      it('logs an error to the console', function () {
        sap = undefined;
        initModule({ client_id: 'foo', client_secret: 'bar' });

        sinon.assert.calledOnce(error);
      });
    });
  });

  describe('getAccessToken', function() {
    var postRequest;

    beforeEach(function () {
      postRequest = sinon.stub(request, 'post');
    });

    afterEach(function () {
      postRequest.restore();
    });

    it('sends a POST request', function() {
      sap.getAccessToken();

      sinon.assert.calledOnce(postRequest);
    });
  });
});

'use strict';

var auth = require('./auth.json');
var sinon = require('sinon');
var expect = require('chai').expect;
var should = require('chai').should();
var request = require('request');

describe('SAP module', function () {
  var sap;

  function initModule(auth) {
    sap = require('../index')(auth);
  }

  beforeEach(function () {
    sap = undefined;
    initModule(auth);
  });

  describe('initialization', function () {
    var error;

    beforeEach(function () {
      error = sinon.stub(console, 'error');
    });

    afterEach(function () {
      error.restore();
    });

    it('sets auth options', function () {
      should.exist(sap.client_id);
      should.exist(sap.client_secret);
      should.exist(sap.refresh_token);
    });

    describe('when no options are passed', function () {
      it('logs an error to the console', function () {
        sap = undefined;
        initModule();

        sinon.assert.calledOnce(error);
      });
    });

    describe('when insufficient options are passed', function () {
      it('logs an error to the console', function () {
        sap = undefined;
        initModule({ client_id: 'foo', client_secret: 'bar' });

        sinon.assert.calledOnce(error);
      });
    });
  });

  describe('getAccessToken', function() {
    it('sends a POST request', sinon.test(function() {
      var postRequest = this.stub(request, 'post');
      sap.getAccessToken();

      sinon.assert.calledOnce(postRequest);
    }));
  });
});

Refactor unit test to use `sinon.test` for automatic cleanup


export function responseOk(response) {
  if(response.status >= 200 && response.status < 300) {
    return response
  } else {
    throw new Error(response.statusText)
  }
}

export function responseToJson(response) {
  return response.json()
}

// Store array with mappings from numerical to hex representation
const _hexMap = Array.from(Array(0xff), (_, i) => (i + 0x100).toString(16).substr(1))

export function generateUUID() {
  let rand = new Uint8Array(16)

  crypto.getRandomValues(rand)

  rand[6] = (rand[6] & 0x0f) | 0x40;
  rand[8] = (rand[8] & 0x3f) | 0x80;

  return _hexMap[rand[0]] + _hexMap[rand[1]] +
         _hexMap[rand[2]] + _hexMap[rand[3]] + '-' +
         _hexMap[rand[4]] + _hexMap[rand[5]] + '-' +
         _hexMap[rand[6]] + _hexMap[rand[7]] + '-' +
         _hexMap[rand[8]] + _hexMap[rand[9]] + '-' +
         _hexMap[rand[10]] + _hexMap[rand[11]] +
         _hexMap[rand[12]] + _hexMap[rand[13]] +
         _hexMap[rand[14]] + _hexMap[rand[15]];
}

export function responseOk(response, throwError=Error) {
  if(response.status >= 200 && response.status < 300) {
    return response
  } else {
    throw new throwError(response.statusText)
  }
}

export function responseToJson(response) {
  return response.json()
}

// Store array with mappings from numerical to hex representation
const _hexMap = Array.from(Array(0xff), (_, i) => (i + 0x100).toString(16).substr(1))

export function generateUUID() {
  let rand = new Uint8Array(16)

  crypto.getRandomValues(rand)

  rand[6] = (rand[6] & 0x0f) | 0x40;
  rand[8] = (rand[8] & 0x3f) | 0x80;

  return _hexMap[rand[0]] + _hexMap[rand[1]] +
         _hexMap[rand[2]] + _hexMap[rand[3]] + '-' +
         _hexMap[rand[4]] + _hexMap[rand[5]] + '-' +
         _hexMap[rand[6]] + _hexMap[rand[7]] + '-' +
         _hexMap[rand[8]] + _hexMap[rand[9]] + '-' +
         _hexMap[rand[10]] + _hexMap[rand[11]] +
         _hexMap[rand[12]] + _hexMap[rand[13]] +
         _hexMap[rand[14]] + _hexMap[rand[15]];
}

Support responseOk for custom errors


const tag = require('forms/lib/tag');

const MULTIPLE_FIELD_TYPES = ['multipleCheckbox', 'multipleRadio'];

function isMultipleField(type) {
  return MULTIPLE_FIELD_TYPES.includes(type);
}

module.exports = function (name, field, options = { }) {
  const widgetHtml = field.widget.toHTML(name, field);
  const innerContent = isMultipleField(field.widget.type) ? tag('div', { }, widgetHtml) : widgetHtml;

  return tag('div', { classes: field.classes(), required: !!field.required }, [
    field.labelHTML(name, field.id),
    field.note ? tag('p', { classes: ['form-control-note'] }, field.note) : '',
    field.errorHTML(),
    innerContent,
  ].join(''));
};

const tag = require('forms/lib/tag');

const MULTIPLE_FIELD_TYPES = ['multipleCheckbox', 'multipleRadio'];

function isMultipleField(type) {
  return MULTIPLE_FIELD_TYPES.includes(type);
}

module.exports = function (name, field, options = { }) {
  const widgetHtml = field.widget.toHTML(name, field);
  const innerContent = isMultipleField(field.widget.type) ? tag('fieldset', { }, widgetHtml) : widgetHtml;

  return tag('fieldset', { classes: field.classes(), required: !!field.required }, [
    tag('legend',{ classes: ['form-control-legend'] },field.label),
    field.note ? tag('p', { classes: ['form-control-note'] }, field.note) : '',
    field.errorHTML(),
    innerContent,
  ].join(''));
};

Use fieldsets rather than divs


import {
  BRICK,
  MAIN_BRICK,
  PRIMITIVE,
  SELECTABLE_PIPE
} from '../../utils/componentNames'

import {
  elementInputValueIds,
  evalBrick
} from '../../utils/evalUtils'

export const evaluateAllWorkspaces = (workspace, elementId) => {
  const brick = workspace.entities[elementId]
  const valueIds = elementInputValueIds(brick)

  const outputSlotId = Object.keys(brick.outputSlots)[0]
  const outputSlot = brick.outputSlots[outputSlotId]

  const newUnitTests = workspace.unitTests.map((unitTest) => {
    const brickOutput = evalBrick(brick, valueIds, unitTest)

    return Object.assign({}, unitTest, {
      values: {
        ...unitTest.values,
        [outputSlotId]: {
          componentName: BRICK,
          type: typeof brickOutput,
          value: brickOutput.toString()
        }
      }
    })
  })

  return Object.assign({}, workspace, {
    ...workspace,
    unitTests: newUnitTests
  })
}

const evalNewPipe = (workspace, pipe) => {
  const { input, output } = pipe
  const inputElement = workspace.entities[input.elementId]

  let newEntities = workspace.entities
  let newUnitTests = workspace.unitTests

  if(inputElement.componentName == PRIMITIVE) {
    newEntities = Object.assign({}, workspace.entities, {
      [pipe.id]: {
        ...pipe,
        type: inputElement.type,
        value: inputElement.value
      }
    })
  }

  if(inputElement.componentName == MAIN_BRICK) {
    const slotIndex = inputElement.inputSlots.findIndex((inputSlot) =>
      inputSlot.id == input.slotId)

    newUnitTests = newUnitTests.map((unitTest) => {
      const testInputId = unitTest.testInputIds[slotIndex]
      const testInput = workspace.entities[testInputId]

      return {
        ...unitTest,
        values: {
          ...unitTest.values,
          [pipe.id]: {
            type: testInput.type,
            value: testInput.value
          }
        }
      }
    })
  }

  return {
    newEntities,
    newUnitTests
  }
}

Delete function 'evalNewPipe' not used anymore


export const MAX_PATCH_CHARS = 10240;

export function filter(original, fileSet) {
  let accumulating = false;
  let accumulated = '';
  let includedChars = 0;

  let index = 0;
  while (index !== -1) {
    let include = true;

    const result = original.indexOf('\ndiff --git ', index);
    const nextIndex = result !== -1 ? result + 1 : -1;
    const patchEnd = nextIndex !== -1 ? nextIndex : original.length;

    // Exclude this patch if its inclusion would cause the patch to become too large.
    const patchChars = patchEnd - index + 1;
    if (includedChars + patchChars > MAX_PATCH_CHARS) {
      include = false;
    }

    if (include) {
      // Avoid copying large buffers of text around if we're including everything anyway.
      if (accumulating) {
        accumulated += original.slice(index, patchEnd);
      }
      includedChars += patchChars;
    } else {
      // If this is the first excluded patch, start by copying everything before this into "accumulated."
      if (!accumulating) {
        accumulating = true;
        accumulated = original.slice(0, index);
      }
    }

    index = nextIndex;
  }

  return accumulating ? accumulated : original;
}

export const MAX_PATCH_CHARS = 10 * 1024 * 1024;

export function filter(original) {
  let accumulating = false;
  let accumulated = '';
  let includedChars = 0;

  let index = 0;
  while (index !== -1) {
    let include = true;

    const result = original.indexOf('\ndiff --git ', index);
    const nextIndex = result !== -1 ? result + 1 : -1;
    const patchEnd = nextIndex !== -1 ? nextIndex : original.length;

    // Exclude this patch if its inclusion would cause the patch to become too large.
    const patchChars = patchEnd - index + 1;
    if (includedChars + patchChars > MAX_PATCH_CHARS) {
      include = false;
    }

    if (include) {
      // Avoid copying large buffers of text around if we're including everything anyway.
      if (accumulating) {
        accumulated += original.slice(index, patchEnd);
      }
      includedChars += patchChars;
    } else {
      // If this is the first excluded patch, start by copying everything before this into "accumulated."
      if (!accumulating) {
        accumulating = true;
        accumulated = original.slice(0, index);
      }
    }

    index = nextIndex;
  }

  return accumulating ? accumulated : original;
}

Remove unused argument and increase patch size limit


import ActiveModelAdapter from "active-model-adapter";
import ActiveModelSerializer from "active-model-adapter/active-model-serializer";

export default {
  name: 'active-model-adapter',
  initialize: function(registry, application) {
    registry.register('adapter:-active-model', ActiveModelAdapter);
    registry.register('serializer:-active-model', ActiveModelSerializer.extend({ isNewSerializerAPI: true }));
  }
};

import ActiveModelAdapter from "active-model-adapter";
import ActiveModelSerializer from "active-model-adapter/active-model-serializer";

export default {
  name: 'active-model-adapter',
  initialize: function() {
    var application = arguments[1] || arguments[0];
    application.register('adapter:-active-model', ActiveModelAdapter);
    application.register('serializer:-active-model', ActiveModelSerializer.extend({ isNewSerializerAPI: true }));
  }
};

Fix initializer deprecation warning for >= ember 2.1.0


'use strict';

// MODULES //

var gamma = require( '@stdlib/math/base/special/gamma' );
var PINF = require( '@stdlib/math/constants/float64-pinf' );
var FACTORIALS = require( './factorials.json' );

// VARIABLES //

var MAX_FACTORIAL = 170;

// FACTORIAL //

/**
* FUNCTION: factorial( x )
*	Computes the factorial of x.
*
* @param {Number} x - input value
* @returns {Number} factorial
*/
function factorial( x ) {
	// Check for `NaN`:
	if ( x !== x ) {
		return NaN;
	}
	// Check for `integer`:
	if ( x%1 === 0 ) {
		if ( x < 0 ) {
			return NaN;
		}
		if ( x <= MAX_FACTORIAL ) {
			return FACTORIALS[ x ];
		}
		return PINF;
	}
	return gamma( x + 1 );
} // end FUNCTION factorial()

// EXPORTS //

module.exports = factorial;

'use strict';

// MODULES //

var isnan = require( '@stdlib/math/base/utils/is-nan' );
var isInteger = require( '@stdlib/math/base/utils/is-integer' );
var gamma = require( '@stdlib/math/base/special/gamma' );
var PINF = require( '@stdlib/math/constants/float64-pinf' );
var FACTORIALS = require( './factorials.json' );

// VARIABLES //

var MAX_FACTORIAL = 170; // FIXME: make into constant

// FACTORIAL //

/**
* FUNCTION: factorial( x )
*	Computes the factorial of x.
*
* @param {Number} x - input value
* @returns {Number} factorial
*/
function factorial( x ) {
	if ( isnan( x ) ) {
		return NaN;
	}
	if ( isInteger( x ) ) {
		if ( x < 0 ) {
			return NaN;
		}
		if ( x <= MAX_FACTORIAL ) {
			return FACTORIALS[ x ];
		}
		return PINF;
	}
	return gamma( x + 1 );
} // end FUNCTION factorial()

// EXPORTS //

module.exports = factorial;

Use base is-nan and is-integer modules


// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')

module.exports = {
  build: {
    env: require('./prod.env'),
    index: path.resolve(__dirname, '../dist/index.html'),
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    productionSourceMap: true,
    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],
    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  },
  dev: {
    env: require('./dev.env'),
    port: 8080,
    autoOpenBrowser: true,
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},
    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    cssSourceMap: false
  }
}

// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')

module.exports = {
  build: {
    env: require('./prod.env'),
    index: path.resolve(__dirname, '../dist/index.html'),
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    productionSourceMap: true,
    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],
    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  },
  dev: {
    env: require('./dev.env'),
    port: 8090,
    autoOpenBrowser: true,
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},
    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    cssSourceMap: false
  }
}

Change listen port due to conflicts


import Service from 'ember-service';
import get from 'ember-metal/get';
import set from 'ember-metal/set';
import computed from 'ember-computed';
import getOwner from 'ember-owner/get';
import isMobile from 'ismobilejs';

export default Service.extend({

  fastboot: computed(function() {
    return getOwner(this).lookup('service:fastboot');
  }),

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

    let queries;
    if (get(this, 'fastboot.isFastBoot')) {
      const headers = get(this, 'fastboot.request.headers');
      const userAgent = headers.get('user-agent');
      queries = isMobile(userAgent);
    } else {
      queries = isMobile;
    }

    for (let media in queries) {
      set(this, media, queries[media]);
    }
  }
});

import Service from 'ember-service';
import get, { getProperties } from 'ember-metal/get';
import set from 'ember-metal/set';
import computed from 'ember-computed';
import getOwner from 'ember-owner/get';
import isMobile from 'ismobilejs';

export default Service.extend({

  fastboot: computed(function() {
    return getOwner(this).lookup('service:fastboot');
  }),

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

    let queries;
    if (get(this, 'fastboot.isFastBoot')) {
      const headers = get(this, 'fastboot.request.headers');
      const userAgent = headers.get('user-agent');
      queries = getProperties(isMobile(userAgent), ['any', 'phone', 'tablet', 'apple', 'android', 'amazon', 'windows', 'seven_inch', 'other']);
    } else {
      queries = isMobile;
    }

    for (let media in queries) {
      set(this, media, queries[media]);
    }
  }
});

Whitelist params returned from isMobile


/* eslint-disable import/no-unresolved */
import SimpleSchema from 'simpl-schema';
import MessageBox from 'message-box';

MessageBox.defaults({
    messages: {
        en: {
            Untrusted: 'Inserts/Updates from untrusted code not supported',
        },
    },
});

SimpleSchema.denyUntrusted = function denyUntrusted() {
    if (this.isSet) {
        const autoValue = this.definition.autoValue && this.definition.autoValue.call(this);
        const defaultValue = this.definition.defaultValue;

        if (this.value !== defaultValue && this.value !== autoValue && !this.isFromTrustedCode) {
            return 'Untrusted';
        }
    }
    return undefined;
};

/* eslint-disable import/no-unresolved */
import { Meteor } from 'meteor/meteor';
import SimpleSchema from 'simpl-schema';
import MessageBox from 'message-box';

/* We check for server code here to deal with a buffer issue in meteor-message-box
 * This shouldn't be a major issue as I doubt we will need to display this error
 * on the client at this point. Should be fixed though.
 * https://github.com/aldeed/meteor-message-box/issues/1
*/
if (Meteor.isServer) {
    MessageBox.defaults({
        messages: {
            en: {
                Untrusted: 'Inserts/Updates from untrusted code not supported',
            },
        },
    });
}

SimpleSchema.denyUntrusted = function denyUntrusted() {
    if (this.isSet) {
        const autoValue = this.definition.autoValue && this.definition.autoValue.call(this);
        const defaultValue = this.definition.defaultValue;

        if (this.value !== defaultValue && this.value !== autoValue && !this.isFromTrustedCode) {
            return 'Untrusted';
        }
    }
    return undefined;
};

Fix issue with MessageBox causing a Buffer is not defined on client.


define(['mac/roman'], function(macintoshRoman) {

  'use strict';

  return function(resource) {
    var dataDV = new DataView(resource.data.buffer, resource.data.byteOffset, resource.data.byteLength);
    resource.dataObject = {
      initialRectangle: {
        top: dataDV.getInt16(0, false),
        left: dataDV.getInt16(2, false),
        bottom: dataDV.getInt16(4, false),
        right: dataDV.getInt16(6, false),
      },
      definitionID: dataDV.getInt16(8, false),
      visible: dataDV.getInt16(10, false),
      closeBox: dataDV.getInt16(12, false),
      referenceConstant: dataDV.getInt32(14, false),
    };
    resource.dataObject.title = macintoshRoman(resource.data, 19, resource.data[18]);
    var pos = 19 + resource.data[18];
    if (pos+2 <= resource.data.length) {
      resource.dataObject.positioning = dataDV.getInt16(pos);
    }
  };

});

define(['mac/roman'], function(macintoshRoman) {

  'use strict';

  return function(item) {
    return item.getBytes().then(function(bytes) {
      var dataDV = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
      var dataObject = {
        initialRectangle: {
          top: dataDV.getInt16(0, false),
          left: dataDV.getInt16(2, false),
          bottom: dataDV.getInt16(4, false),
          right: dataDV.getInt16(6, false),
        },
        definitionID: dataDV.getInt16(8, false),
        visible: dataDV.getInt16(10, false),
        closeBox: dataDV.getInt16(12, false),
        referenceConstant: dataDV.getInt32(14, false),
      };
      dataObject.title = macintoshRoman(bytes, 19, bytes[18]);
      var pos = 19 + bytes[18];
      if (pos+2 <= bytes.length) {
        dataObject.positioning = dataDV.getInt16(pos);
      }
      item.setDataObject(dataObject);
    });
  };

});

Update WIND to new loader format


scene = new Athena.Entities.Scene('test');

entity1 = scene.create("Entity1");

CHECK_EQUAL(0, entity1.nbChildren);

entity2 = scene.create("Entity2");

CHECK(entity2.parent === undefined);

entity1.addChild(entity2);

CHECK_EQUAL(1, entity1.nbChildren);
CHECK(entity2.parent !== undefined);
CHECK_EQUAL('Entity1', entity2.parent.name);

entity1.removeChild(entity2);

CHECK_EQUAL(0, entity1.nbChildren);
CHECK(entity2.parent === undefined);

entity1.addChild(entity2);

entity1.destroyAllChildren();

CHECK_EQUAL(0, entity1.nbChildren);
CHECK(entity2.parent === undefined);

Remove an incorrectly copy/pasted line


var xenstat = require('../build/Release/xenstat.node');

module.exports = {
  'Collect asynchronous': function(test) {
    xenstat.collect(function(err, stats) {
      test.ifError(err);
      test.ok(stats && typeof stats === 'object');
      test.ok(stats.num_domains >= 1);
      test.strictEqual(Object.keys(stats).length, 8);
      test.done();
    });
  },

  'Collect synchronous': function(test) {
    var stats = xenstat.collectSync();
    test.ok(stats && typeof stats === 'object');
    test.ok(stats.num_domains >= 1);
    test.strictEqual(Object.keys(stats).length, 8);
    test.done();
  },
};

var xenstat = require('../build/Release/xenstat.node');

module.exports = {
  'Collect asynchronous': function(test) {
    xenstat.collect(function(err, stats) {
      test.ifError(err);
      test.ok(stats && typeof stats === 'object');
      test.ok(stats.num_domains >= 1);
      test.ok(stats.domains && stats.domains.length === stats.num_domains);
      test.strictEqual(Object.keys(stats).length, 8);
      test.done();
    });
  },

  'Collect synchronous': function(test) {
    var stats = xenstat.collectSync();
    test.ok(stats && typeof stats === 'object');
    test.ok(stats.num_domains >= 1);
    test.ok(stats.domains && stats.domains.length === stats.num_domains);
    test.strictEqual(Object.keys(stats).length, 8);
    test.done();
  },
};

Add test to check length of domains array matches num_domains


"use strict";

angular.module('arethusa.morph').directive('morphFormAttributes', [
  'morph',
  '$document',
  function(morph, $document) {
    return {
      restrict: 'A',
      scope: {
        form: '=morphFormAttributes',
        tokenId: '='
      },
      link: function(scope, element, attrs) {
        var id = scope.tokenId;

        scope.m = morph;
        scope.attrs = morph.sortAttributes(scope.form.attributes);
        scope.inv = scope.form.lexInv;

        scope.askForRemoval = function() {
          scope.confirmationRequested = true;
        };

        scope.skipRemoval = function() {
          scope.confirmationRequested = false;
        };

        scope.removeForm = function() {
          if (scope.form.selected) {
            morph.unsetState(id);
          }
          morph.removeForm(id, scope.form);
        };
      },
      templateUrl: 'templates/arethusa.morph/morph_form_attributes.html'
    };
  }
]);

"use strict";

angular.module('arethusa.morph').directive('morphFormAttributes', [
  'morph',
  function(morph) {
    return {
      restrict: 'A',
      scope: {
        form: '=morphFormAttributes',
        tokenId: '='
      },
      link: function(scope, element, attrs) {
        var id = scope.tokenId;

        scope.m = morph;
        scope.attrs = morph.sortAttributes(scope.form.attributes);
        scope.inv = scope.form.lexInv;

        scope.askForRemoval = function() {
          scope.confirmationRequested = true;
        };

        scope.skipRemoval = function() {
          scope.confirmationRequested = false;
        };

        scope.removeForm = function() {
          if (scope.form.selected) {
            morph.unsetState(id);
          }
          morph.removeForm(id, scope.form);
        };
      },
      templateUrl: 'templates/arethusa.morph/morph_form_attributes.html'
    };
  }
]);

Remove more unneeded DI in mFA


// volontary written in ES5, so that it works with Node 4.x
var path = require('path');
var webpack = require('webpack');
var ProgressBarPlugin = require('progress-bar-webpack-plugin');
var buildResolver = require('haste-map-webpack-resolver');

var currentDir = path.resolve(__dirname, '.');

module.exports = {
    context: currentDir,
    entry: './entry-point.js',
    output: {
        path: './dist',
        filename: 'entry-point.bundle.js',
        library: 'entry-point',
        libraryTarget: 'umd',
        umdNamedDefine: true
    },
    devtool: 'sourcemap',
    target: 'node',
    module: {
        rules: [
            {
                test: /\.js/,
                exclude: /(node_modules)/,
                use: [
                    {
                        loader: 'babel-loader',
                        query: {
                            presets: ['babel-preset-es2015']
                        },
                    }
                ]
            }
        ],
    },
    resolve: {
        plugins: [buildResolver({
            rootPath: path.resolve(__dirname, '.'),
        })],
    },
    plugins: [
        new webpack.BannerPlugin(
            {
                banner: 'require("source-map-support").install();',
                raw: true,
                entryOnly: false,
            }
        ),
        new ProgressBarPlugin(),
    ],
};

// volontary written in ES5, so that it works with Node 4.x
var path = require('path');
var webpack = require('webpack');
var ProgressBarPlugin = require('progress-bar-webpack-plugin');
var HasteMapWebPackResolver = require('haste-map-webpack-resolver');

var currentDir = path.resolve(__dirname, '.');

module.exports = {
    context: currentDir,
    entry: './entry-point.js',
    output: {
        path: './dist',
        filename: 'entry-point.bundle.js',
        library: 'entry-point',
        libraryTarget: 'umd',
        umdNamedDefine: true
    },
    devtool: 'sourcemap',
    target: 'node',
    module: {
        rules: [
            {
                test: /\.js/,
                exclude: /(node_modules)/,
                use: [
                    {
                        loader: 'babel-loader',
                        query: {
                            presets: ['babel-preset-es2015']
                        },
                    }
                ]
            }
        ],
    },
    resolve: {
        plugins: [new HasteMapWebPackResolver({
            rootPath: path.resolve(__dirname, '.'),
        })],
    },
    plugins: [
        new webpack.BannerPlugin(
            {
                banner: 'require("source-map-support").install();',
                raw: true,
                entryOnly: false,
            }
        ),
        new ProgressBarPlugin(),
    ],
};

Use more natural constructor call


/* jshint mocha: true */
'use strict';

const assert = require('assert');
const jenkins = require('../lib/jenkins');

describe('jenkins', function() {
  describe('getComputers()', function() {
    it('returns all nodes', function(done) {
      this.timeout(10000);

      jenkins.getComputers(function(err, nodes) {
        assert.ifError(err);
        console.log(nodes);
        console.log(nodes.length);
        done();
      });
    });
  });

  describe('getOffline()', function() {
    it('returns offline nodes', function(done) {
      this.timeout(10000);

      jenkins.getOffline(function(err, offline) {
        assert.ifError(err);
        console.log(offline);
        done();
      });
    });
  });
});

/* jshint mocha: true */
'use strict';

const assert = require('assert');
const jenkins = require('../lib/jenkins');

describe('jenkins', function() {
  describe('getComputers()', function() {
    it('returns all nodes', function(done) {
      this.timeout(10000);

      jenkins.getComputers(function(err, nodes) {
        assert.ifError(err);
        assert.equal(nodes.length, 68);
        done();
      });
    });
  });

  describe('getOffline()', function() {
    it('returns offline nodes', function(done) {
      this.timeout(10000);

      jenkins.getOffline(function(err, offline) {
        assert.ifError(err);
        assert.equal(offline.length, 5);
        done();
      });
    });
  });
});

Substitute console logs with proper asserts


import Ember from 'ember';
import Remarkable from 'remarkable';
import hljs from 'hljs';

export default Ember.Component.extend({
  text: '',
  typographer: false,
  linkify: false,
  html: false,

  parsedMarkdown: function() {
    var md = new Remarkable({
      typographer: this.get('typographer'),
      linkify: this.get('linkify'),
      html: this.get('html'),

      highlight: function(str, lang) {
        if (lang && hljs.getLanguage(lang)) {
          try {
            return hljs.highlight(lang, str).value;
          } catch (err) {}
        }

        try {
          return hljs.highlightAuto(str).value;
        } catch (err) {}

        return '';
      }
    });

    var html = md.render(this.get('text'));
    return new Ember.Handlebars.SafeString(html);
  }.property('text', 'highlight', 'typographer', 'linkify')
});

import Ember from 'ember';
import Remarkable from 'remarkable';
import hljs from 'hljs';

const { computed } = Ember;

export default Ember.Component.extend({
  text: '',
  typographer: false,
  linkify: false,
  html: false,

  parsedMarkdown: computed('text', 'html', 'typographer', 'linkify', function() {
    var md = new Remarkable({
      typographer: this.get('typographer'),
      linkify: this.get('linkify'),
      html: this.get('html'),

      highlight: function(str, lang) {
        if (lang && hljs.getLanguage(lang)) {
          try {
            return hljs.highlight(lang, str).value;
          } catch (err) {}
        }

        try {
          return hljs.highlightAuto(str).value;
        } catch (err) {}

        return '';
      }
    });

    var html = md.render(this.get('text'));
    return new Ember.Handlebars.SafeString(html);
  })
});

Remove use of prototype extension


const emptyDir = require('../');
const expect = require('chai').expect;

describe('emptyDir', function () {
  it('should call back with true if a directory is empty', function (done) {
    expect(emptyDir.sync('./')).to.be.false;
    emptyDir('./', function (err, result) {
      expect(result).to.be.false;
      done();
    });
  });

  it('should call back with false if a directory is not empty', function (done) {
    expect(emptyDir.sync('./test/empty')).to.be.true;
    emptyDir('./test/empty', function (err, result) {
      expect(result).to.be.true;
      done();
    });
  });
});

const emptyDir = require('../');
const expect = require('chai').expect;
const fs = require('fs');

try {
  fs.mkdirSync('./test/empty');
} catch (e) {}

describe('emptyDir', function () {
  it('should call back with true if a directory is empty', function (done) {
    expect(emptyDir.sync('./')).to.be.false;
    emptyDir('./', function (err, result) {
      expect(result).to.be.false;
      done();
    });
  });

  it('should call back with false if a directory is not empty', function (done) {
    expect(emptyDir.sync('./test/empty')).to.be.true;
    emptyDir('./test/empty', function (err, result) {
      expect(result).to.be.true;
      done();
    });
  });
});

Create the fixture directory for tests


module.exports = function(params) {
  const { crashReporter } = require('electron');
  const platformRelease = require('os').release();
  const { uploadToServer } = params;

  crashReporter.start({
    productName: 'Atom',
    companyName: 'GitHub',
    submitURL: 'https://atom.io/crash_reports',
    uploadToServer,
    extra: { platformRelease }
  });
};

module.exports = function(params) {
  const { crashReporter } = require('electron');
  const os = require('os');
  const platformRelease = os.release();
  const arch = os.arch();
  const { uploadToServer } = params;

  crashReporter.start({
    productName: 'Atom',
    companyName: 'GitHub',
    submitURL: 'https://atom.io/crash_reports',
    uploadToServer,
    extra: { platformRelease, arch }
  });
};

Include architecture in data posted to Atom.io with crashes


define("dojox/data/JsonQueryRestStore", ["dojo", "dojox", "dojox/data/JsonRestStore", "dojox/data/util/JsonQuery"], function(dojo, dojox) {

dojo.requireIf(!!dojox.data.ClientFilter,"dojox.json.query"); // this is so we can perform queries locally

// this is an extension of JsonRestStore to convert object attribute queries to
// JSONQuery/JSONPath syntax to be sent to the server. This also enables
//	JSONQuery/JSONPath queries to be performed locally if dojox.data.ClientFilter
//	has been loaded
dojo.declare("dojox.data.JsonQueryRestStore",[dojox.data.JsonRestStore,dojox.data.util.JsonQuery],{
	matchesQuery: function(item,request){
		return item.__id && (item.__id.indexOf("#") == -1) && this.inherited(arguments);
	}
});

return dojox.data.JsonQueryRestStore;
});

define("dojox/data/JsonQueryRestStore", ["dojo", "dojox", "dojox/data/JsonRestStore", "dojox/data/util/JsonQuery", "dojox.data.ClientFilter", "dojox.json.query"], function(dojo, dojox) {

// this is an extension of JsonRestStore to convert object attribute queries to
// JSONQuery/JSONPath syntax to be sent to the server. This also enables
//	JSONQuery/JSONPath queries to be performed locally if dojox.data.ClientFilter
//	has been loaded
dojo.declare("dojox.data.JsonQueryRestStore",[dojox.data.JsonRestStore,dojox.data.util.JsonQuery],{
	matchesQuery: function(item,request){
		return item.__id && (item.__id.indexOf("#") == -1) && this.inherited(arguments);
	}
});

return dojox.data.JsonQueryRestStore;
});

Remove requireIf and always load dependent modules


// Execute JavaScript on page load
$(function () {
    $("#userNumber, #salesNumber").intlTelInput({
        responsiveDropdown: true,
        autoFormat: true
    });
    var $form = $("#contactform"),
        $submit = $("#contactform input[type=submit]");

    // Intercept form submission
    $form.on("submit", function (e) {
        // Prevent form submission and repeat clicks
        e.preventDefault();
        $submit.attr("disabled", "disabled");

        // Submit the form via AJAX
        $.post("/CallCenter/Call", $form.serialize(), null, "json")
        .done(function (data) {
            alert(data.message);
            if (data.success) {
                $form.reset();
            }
        }).fail(function () {
            alert("There was a problem calling you - please try again later.");
        }).always(function () {
            $submit.removeAttr("disabled");
        });
    });
});

// Execute JavaScript on page load
$(function () {
    $("#userNumber, #salesNumber").intlTelInput({
        responsiveDropdown: true,
        autoFormat: true
    });
    var $form = $("#contactform"),
        $submit = $("#contactform input[type=submit]");

    // Intercept form submission
    $form.on("submit", function (e) {
        // Prevent form submission and repeat clicks
        e.preventDefault();
        $submit.attr("disabled", "disabled");

        // Submit the form via AJAX
        $.post("/CallCenter/Call", $form.serialize(), null, "json")
        .done(function (data) {
            alert(data.message);
            if (data.success) {
                $form[0].reset();
            }
        }).fail(function () {
            alert("There was a problem calling you - please try again later.");
        }).always(function () {
            $submit.removeAttr("disabled");
        });
    });
});

Fix the issue when reseting the form on successful calls


import React from 'react'
import 'css/markdown-styles.css'
import Helmet from 'react-helmet'
import { config } from 'config'

module.exports = React.createClass({
  propTypes () {
    return {
      router: React.PropTypes.object,
    }
  },
  render () {
    const post = this.props.route.page.data
    return (

        <Helmet
          title={`${config.siteTitle} | ${post.title}`}
        />

{post.title}

    )
  },
})

import React from 'react'
import 'css/markdown-styles.css'
import Helmet from 'react-helmet'
import { config } from 'config'

module.exports = React.createClass({
  propTypes () {
    return {
      router: React.PropTypes.object,
    }
  },
  render () {
    const post = this.props.route.page.data
    return (

        <Helmet
          title={`${config.siteTitle} | ${post.title}`}
        />

{post.title}

        {typeof(post.author) !== 'undefined' && (

            {post.author}

        )}

    )
  },
})

Add author support to the markdown wrapper


var UglifyJS = require('../../');
var assert = require("assert");

describe("String literals", function() {
    it("Should throw syntax error if a string literal contains a newline", function() {
        var inputs = [
            "'\n'",
            "'\r'",
            '"\r\n"',
            "'\u2028'",
            '"\u2029"'
        ];

        var test = function(input) {
            return function() {
                var ast = UglifyJS.parse(input);
            }
        };

        var error = function(e) {
            return e instanceof UglifyJS.JS_Parse_Error &&
                e.message === "Unterminated string constant"
        };

        for (var input in inputs) {
            assert.throws(test(inputs[input]), error);
        }
    });
});

var UglifyJS = require('../../');
var assert = require("assert");

describe("String literals", function() {
    it("Should throw syntax error if a string literal contains a newline", function() {
        var inputs = [
            "'\n'",
            "'\r'",
            '"\r\n"',
            "'\u2028'",
            '"\u2029"'
        ];

        var test = function(input) {
            return function() {
                var ast = UglifyJS.parse(input);
            };
        };

        var error = function(e) {
            return e instanceof UglifyJS.JS_Parse_Error &&
                e.message === "Unterminated string constant";
        };

        for (var input in inputs) {
            assert.throws(test(inputs[input]), error);
        }
    });

    it("Should not throw syntax error if a string has a line continuation", function() {
        var output = UglifyJS.parse('var a = "a\\\nb";').print_to_string();
        assert.equal(output, 'var a="ab";');
    });
});

Add test case for line continuation


"use strict";

angular.module('arethusa.relation').directive('labelSelector', [
  'relation',
  '$timeout',
  function(relation, $timeout) {
    return {
      restrict: 'A',
      scope: {
        obj: '=',
        change: '&'
      },
      link: function(scope, element, attrs) {
        scope.plugin = relation;
        scope.showMenu = true;

        scope.$watch('plugin.mode', function(newVal, oldVal) {
          scope.showMenu = relation.canEdit();
        });

        scope.$on('nestedMenuSelection', function(event, obj) {
          $timeout(function() {
            relation.changeState(obj);
          });
        });
      },
      templateUrl: 'templates/arethusa.relation/label_selector.html'
    };
  }
]);

"use strict";

angular.module('arethusa.relation').directive('labelSelector', [
  'relation',
  '$timeout',
  function(relation, $timeout) {
    return {
      restrict: 'A',
      scope: {
        obj: '=',
        change: '&'
      },
      link: function(scope, element, attrs) {
        scope.plugin = relation;
        scope.showMenu = true;

        scope.$watch('plugin.mode', function(newVal, oldVal) {
          scope.showMenu = relation.canEdit();
        });

        scope.$on('nestedMenuSelection', function(event, obj) {
          var oldAncestors = angular.copy(obj.ancestors);
          $timeout(function() {
            relation.changeState(obj, oldAncestors);
          });
        });
      },
      templateUrl: 'templates/arethusa.relation/label_selector.html'
    };
  }
]);

Fix reference to oldAncestors in labelSelector


const remote = require('electron').remote;
const Menu = remote.Menu;
const MenuItem = remote.MenuItem;

module.exports = {
    build: function(target) {
        var template = [
            {
                label: 'Copy Link Address',
                role: 'copy'
            }
        ];

        if (process.platform == 'darwin') {
            template = [...template,
                {
                    type: 'separator'
                },
                {
                    label: 'Services',
                    role: 'services',
                    submenu: []
                }
            ];
        }
        return Menu.buildFromTemplate(template);
    }
};

const remote = require('electron').remote;
const clipboard = require('electron').clipboard;
const Menu = remote.Menu;
const MenuItem = remote.MenuItem;

module.exports = {
    build: function(target) {
        var template = [
            {
                label: 'Copy Link Address',
                click: function (e, focusedWindow) {
                  clipboard.writeText(target.href);
                }
            }
        ];

        if (process.platform == 'darwin') {
            template = [...template,
                {
                    type: 'separator'
                },
                {
                    label: 'Services',
                    role: 'services',
                    submenu: []
                }
            ];
        }
        return Menu.buildFromTemplate(template);
    }
};

Copy role doesn't work, need to manually copy the link target


import React from "react";
import { Button, Popup, Input, Heading, Divider } from 'semantic-ui-react';
export default class DayEditBox extends React.Component {

    render(){
        var myId = "dayedit-" + this.props.id + "-text";
        var content = (

                <Input focus placeholder={this.props.fillerText} id={myId}/>
                <Divider/>
                <Popup
                    trigger={<Button negative>Delete</Button>}
                    content={

                            <Heading as="h1">Hey there!</Heading>

Deleting the day will also delete any cells in the day.

                            <Button negative onClick={this.props.onDelete}>I understand. Delete it!</Button>

                    }
                    on="click"
                 />
                <Button
                    positive
                    onClick={
                        (event, data) => {
                            this.props.onClick(event, data, myId);
                        }
                    }
                >
                    Save
                </Button>

        );
        return(
        <Popup
            trigger={this.props.trigger}
            content={content}
            on="click"
            header="Edit Day"
            />
        );
    }
}

import React from "react";
import { Button, Popup, Input, Header, Divider } from 'semantic-ui-react';
export default class DayEditBox extends React.Component {

    render(){
        var myId = "dayedit-" + this.props.id + "-text";
        var content = (

                <Input focus placeholder={this.props.fillerText} id={myId}/>
                <Divider/>
                <Popup
                    trigger={<Button negative>Delete</Button>}
                    content={

                            Hey there!

Deleting the day will also delete any cells in the day.

                            <Button negative onClick={this.props.onDelete}>I understand. Delete it!</Button>

                    }
                    on="click"
                 />
                <Button
                    positive
                    onClick={
                        (event, data) => {
                            this.props.onClick(event, data, myId);
                        }
                    }
                >
                    Save
                </Button>

        );
        return(
        <Popup
            trigger={this.props.trigger}
            content={content}
            on="click"
            header="Edit Day"
            />
        );
    }
}

Delete button issue now fixed


import Ember from 'ember';

export default Ember.Route.extend({
  teamQuery: Ember.inject.service(),

  model({ domain }) {
    return this.get('teamQuery').findByDomain(domain);
  },

  actions: {
    didCreateCanvas(canvas) {
      this.transitionTo('canvases.show', canvas);
    }
  }
});

import Ember from 'ember';

export default Ember.Route.extend({
  teamQuery: Ember.inject.service(),

  model({ domain }) {
    return this.get('teamQuery').findByDomain(domain);
  },

  actions: {
    didCreateCanvas(canvas) {
      this.transitionTo('canvases.show',
        canvas.get('team.domain'), canvas.get('id'));
    }
  }
});

Fix redirect for new canvas (must not include model)


'use strict';

/* jasmine specs for controllers go here */
describe('Real Racing Telemetry Client controllers', function() {

  describe('carController', function(){

    beforeEach(module('rrtClientApp'));

    it('should create "cars" model with 4 cars', inject(function($controller) {
      var scope = {},
          ctrl = $controller('carController', {$scope:scope});

      expect(scope.cars.length).toBe(4);
    }));

    it('should create title', inject(function($controller) {
      var scope = {},
          ctrl = $controller('carController', {$scope:scope});

      expect(scope.title).toBe("Admin for Cars and Manufacturers.");
    }));

  });
});

'use strict';

/* jasmine specs for controllers go here */
describe('Real Racing Telemetry Client controllers', function() {

  describe('carController', function(){

    beforeEach(module('rrtClientApp'));

    it('should create "cars" model with 4 cars', inject(function($controller) {
      var scope = {},
          ctrl = $controller('carController', {$scope:scope});

      expect(scope.cars.length).toBe(4);
    }));

    it('should create title', inject(function($controller) {
      var scope = {},
          ctrl = $controller('carController', {$scope:scope});

      expect(scope.title).toBe("Admin for Cars and Manufacturers.");
    }));

    it('should set the default value of orderProp model', function() {
      expect(scope.orderProp).toBe('model');
    });

  });
});

Test the order of cars is by model as selected by the orderProp default.


var authcommon = require('../authcommon'),
    common = require('../common'),
    config = require('../config'),
    db = require('../db');

exports.can_access = function (ident, board) {
	if (is_admin_ident(ident))
		return true; // including graveyard
	return db.is_board(board);
};

function is_mod_ident(ident) {
	return ident && (ident.auth === 'Admin' || ident.auth === 'Moderator');
}
exports.is_mod_ident = is_mod_ident;

function is_admin_ident(ident) {
	return ident && ident.auth === 'Admin';
}
exports.is_admin_ident = is_admin_ident;

function denote_priv(header, data) {
	if (data.priv)
		header.push(' (priv)');
	return header;
}

exports.augment_oneesama = function (oneeSama, ident) {
	if (is_mod_ident(ident))
		oneeSama.hook('header', authcommon.ip_mnemonic);
	if (is_admin_ident(ident))
		oneeSama.hook('header', denote_priv);
};

var authcommon = require('../authcommon'),
    common = require('../common'),
    config = require('../config'),
    db = require('../db');

exports.can_access = function (ident, board) {
	if (board == 'graveyard' && is_admin_ident(ident))
		return true;
	return db.is_board(board);
};

function is_mod_ident(ident) {
	return ident && (ident.auth === 'Admin' || ident.auth === 'Moderator');
}
exports.is_mod_ident = is_mod_ident;

function is_admin_ident(ident) {
	return ident && ident.auth === 'Admin';
}
exports.is_admin_ident = is_admin_ident;

function denote_priv(header, data) {
	if (data.priv)
		header.push(' (priv)');
	return header;
}

exports.augment_oneesama = function (oneeSama, ident) {
	if (is_mod_ident(ident))
		oneeSama.hook('header', authcommon.ip_mnemonic);
	if (is_admin_ident(ident))
		oneeSama.hook('header', denote_priv);
};

Fix can_access allowing admin all possible boards


var sagui = require('sagui')

module.exports = sagui().webpack({
  sagui: {
    /**
     * Different application entry-points
     * Each page is a combination of a JavaScript file and a HTML file
     *
     * Example: 'index' -> 'index.html' and 'index.js'
     */
    pages: ['index'],

    /**
     * List of Sagui plugins to disable
     */
    disabledPlugins: []
  }

  /**
   * Additional webpack configuration
   * Will ovewrite and extend the default Sagui configuration
   * see: http://webpack.github.io/docs/configuration.html
   */
})

var sagui = require('sagui')

module.exports = sagui().webpack({
  sagui: {
    /**
     * Different application entry-points
     * Each page is a combination of a JavaScript file and a HTML file
     *
     * Example: 'index' -> 'index.html' and 'index.js'
     */
    pages: ['index'],

    /**
     * List of Sagui presets to disable
     */
    disabledSaguiPresets: []
  }

  /**
   * Additional webpack configuration
   * Will ovewrite and extend the default Sagui configuration
   * see: http://webpack.github.io/docs/configuration.html
   */
})

Update the template on preset wording


'use strict'

const {
  env: {
    GITHUB_RELEASE_OAUTH,
    GIT_REPO_TAG,
    GIT_REPO_OWNER,
    GIT_REPO_NAME
  },
  stdout,
  stderr,
  exit
} = require('process')

const GitHubAPIs = require('github')

const github = new GitHubAPIs()

github.authenticate({
  type: 'token',
  token: GITHUB_RELEASE_OAUTH
})

const {repos} = github

new Promise(
  (resolve, reject) => {
    const DESC = {
      user: GIT_REPO_OWNER,
      repo: GIT_REPO_NAME,
      name: GIT_REPO_TAG,
      tag_name: GIT_REPO_TAG
    }
    repos.editRelease(DESC)
      .then(
        () => {
          repos.uploadAsset()
        }
      )
      .catch(reject)
  }
)
  .then(
    () => {
      stdout.write('Deploying succeed.\n')
      exit(0)
    }
  )
  .catch(
    () => {
      stderr.write('Deploying failed.\n')
      exit(1)
    }
  )

'use strict'

const {join} = require('path')

const {
  env: {
    GITHUB_RELEASE_OAUTH,
    GIT_REPO_TAG,
    GIT_REPO_OWNER,
    GIT_REPO_NAME,
    ARTIFACTS_DIRECTORY
  },
  stdout,
  stderr,
  exit
} = require('process')

const {readdirSync} = require('fs')

const co = require('co')

const GitHubAPIs = require('github')

const {assign} = Object

co(main)

function * main () {
  const github = new GitHubAPIs()
  github.authenticate({
    type: 'token',
    token: GITHUB_RELEASE_OAUTH
  })
  const {repos} = github
  const DESC = {
    user: GIT_REPO_OWNER,
    repo: GIT_REPO_NAME,
    name: GIT_REPO_TAG,
    tag_name: GIT_REPO_TAG,
    tag: GIT_REPO_TAG,
    __proto__: null
  }
  try {
    DESC.id = (yield repos.getReleaseByTag(DESC)).id
  } catch (error) {
    throw halt(error, 'Getting release id failed')
  }
  try {
    const EDIT_RELEASE = assign({}, DESC, {
      draft: (/.*\-alpha[0-9]{0,}$/).test(GIT_REPO_TAG),
      prerelease: (/.*\-beta[0-9]{0,}$/).test(GIT_REPO_TAG),
      __proto__: null
    })
    DESC.id = (yield repos.editRelease(EDIT_RELEASE)).id
  } catch (error) {
    throw halt(error, 'Editing release failed')
  }
  try {
    const all = readdirSync(ARTIFACTS_DIRECTORY)
      .map(
        name =>
          assign({}, DESC, {
            name,
            filePath: join(ARTIFACTS_DIRECTORY, name),
            __proto__: null
          })
      )
      .map(
        request =>
          repos.uploadAsset(request)
      )
    yield Promise.all(all)
  }
}

function halt (error, message) {
  stderr.write(message + '\n')
  stdout.write(JSON.stringify(error))
  exit(1)
}

Complete deployment script (for GitHub Release)


import { isEmpty, pickBy, transform } from 'lodash';
import request from './request';

const findMatchingPermissions = (userPermissions, permissions) => {
  return transform(
    userPermissions,
    (result, value) => {
      const associatedPermission = permissions.find(
        perm => perm.action === value.action && perm.subject === value.subject
      );

      if (associatedPermission) {
        result.push(value);
      }
    },
    []
  );
};

const formatPermissionsForRequest = permissions =>
  permissions.map(permission =>
    pickBy(permission, (value, key) => {
      return ['action', 'subject', 'fields'].includes(key) && !isEmpty(value);
    })
  );

const shouldCheckPermissions = permissions =>
  !isEmpty(permissions) && permissions.every(perm => !isEmpty(perm.conditions));

const hasPermissions = async (userPermissions, permissions, signal) => {
  if (!permissions || !permissions.length) {
    return true;
  }

  const matchingPermissions = findMatchingPermissions(userPermissions, permissions);

  if (shouldCheckPermissions(matchingPermissions)) {
    let hasPermission = false;

    try {
      const { data } = await request('/admin/permissions/check', {
        method: 'POST',
        body: {
          permissions: formatPermissionsForRequest(matchingPermissions),
        },
        signal,
      });

      hasPermission = data.every(v => v === true);
    } catch (err) {
      console.error('Error while checking permissions', err);
    }

    return hasPermission;
  }

  return matchingPermissions.length > 0;
};

export default hasPermissions;
export { findMatchingPermissions, formatPermissionsForRequest, shouldCheckPermissions };

import { isEmpty, pickBy, transform } from 'lodash';
import request from './request';

const findMatchingPermissions = (userPermissions, permissions) => {
  return transform(
    userPermissions,
    (result, value) => {
      const associatedPermission = permissions.find(
        perm => perm.action === value.action && perm.subject === value.subject
      );

      if (associatedPermission) {
        result.push(value);
      }
    },
    []
  );
};

const formatPermissionsForRequest = permissions =>
  permissions.map(permission =>
    pickBy(permission, (value, key) => {
      return ['action', 'subject'].includes(key) && !isEmpty(value);
    })
  );

const shouldCheckPermissions = permissions =>
  !isEmpty(permissions) && permissions.every(perm => !isEmpty(perm.conditions));

const hasPermissions = async (userPermissions, permissions, signal) => {
  if (!permissions || !permissions.length) {
    return true;
  }

  const matchingPermissions = findMatchingPermissions(userPermissions, permissions);

  if (shouldCheckPermissions(matchingPermissions)) {
    let hasPermission = false;

    try {
      const { data } = await request('/admin/permissions/check', {
        method: 'POST',
        body: {
          permissions: formatPermissionsForRequest(matchingPermissions),
        },
        signal,
      });

      hasPermission = data.every(v => v === true);
    } catch (err) {
      console.error('Error while checking permissions', err);
    }

    return hasPermission;
  }

  return matchingPermissions.length > 0;
};

export default hasPermissions;
export { findMatchingPermissions, formatPermissionsForRequest, shouldCheckPermissions };

Fix param sent to the API


const Backbone = require('backbone');
const IconModel = require('./organization-icon-model');
const checkAndBuildOpts = require('builder/helpers/required-opts');

const REQUIRED_OPTS = [
  'orgId',
  'configModel'
];

module.exports = Backbone.Collection.extend({
  initialize: function (models, options) {
    checkAndBuildOpts(options, REQUIRED_OPTS, this);
  },

  model: function (attrs, opts) {
    const options = { ...opts, configModel: opts.collection._configModel };

    return new IconModel(attrs, options);
  },

  url: function (method) {
    const version = this._configModel.urlVersion('organization-assets', method);

    return `/api/${version}/organization/${this._orgId}/assets`;
  }
});

const Backbone = require('backbone');
const IconModel = require('./organization-icon-model');
const checkAndBuildOpts = require('builder/helpers/required-opts');

const REQUIRED_OPTS = [
  'orgId',
  'configModel'
];

module.exports = Backbone.Collection.extend({
  initialize: function (models, options) {
    checkAndBuildOpts(options, REQUIRED_OPTS, this);
  },

  model: function (attrs, opts) {
    const options = { ...opts, configModel: this._configModel };

    return new IconModel(attrs, options);
  },

  url: function (method) {
    const version = this._configModel.urlVersion('organization-assets', method);

    return `/api/${version}/organization/${this._orgId}/assets`;
  }
});

Use this._configModel instead of opts.collection.configModel in icon-collection


/*global jQuery, document */
/*jslint nomen: true, unparam: true, bitwise: true*/

(function ($) {
    "use strict";

    var run, register_click_event;

    run = function (event) {
        $('#my_form').remove();
        var button = $(event.currentTarget),
            code_block = button.prev();
        button.after('

');
        eval(code_block.text());
    };

    register_click_event = function (node) {
        var button = $('<button class="coderunner">Run</button>');
        node.after(button);
        button.click(run);

    };

    $(document).ready(function () {
        $.each(
            $('.highlight-javascript').add($('.highlight-js')),
            function (index, node) {
                register_click_event($(node));
            }
        );
    });
}(jQuery));

/*global jQuery, document */
/*jslint nomen: true, unparam: true, bitwise: true*/

(function ($) {
    "use strict";

    var run, register_click_event;

    run = function (event) {
        $('#my_form').remove();
        var button = $(event.currentTarget),
            code_block = button.prev();
        button.after('

');
        eval(code_block.text());
    };

    register_click_event = function (node) {
        var button = $('<button class="btn btn-neutral coderunner">Run</button>');
        node.after(button);
        button.click(run);

    };

    $(document).ready(function () {
        $.each(
            $('.highlight-javascript').add($('.highlight-js')),
            function (index, node) {
                register_click_event($(node));
            }
        );
    });
}(jQuery));

Use same styling for "Run" button as is used by buttons in Sphinx.


const { pick } = require('lodash')
const { authorisedRequest } = require('../../lib/authorised-request')
const config = require('../../config')

function fetchRawCompanyListItems(token, id) {
  return authorisedRequest(token, {
    method: 'GET',
    url: `${config.apiRoot}/v4/company-list/${id}/item`,
  })
}

const fetchRawCompanyList = (token) =>
  authorisedRequest(token, {
    method: 'GET',
    url: `${config.apiRoot}/v4/company-list`,
  })

/* eslint-disable-next-line camelcase */
const transformRawCompany = ({ latest_interaction, company }) => ({
  company: pick(company, ['id', 'name']),
  latestInteraction: pick(latest_interaction, ['date', 'id', 'subject']),
  /* eslint-disable-next-line camelcase */
  ditParticipants: latest_interaction
    ? latest_interaction.dit_participants.map((participant) => ({
        name: participant.adviser ? participant.adviser.name : '',
        team: participant.team ? participant.team.name : '',
      }))
    : [],
})

const fetchCompanyLists = (token) =>
  fetchRawCompanyList(token).then(({ results }) =>
    Promise.all(
      results.map((list) =>
        fetchRawCompanyListItems(token, list.id).then(({ results }) => ({
          ...pick(list, ['id', 'name']),
          companies: results.map(transformRawCompany),
        }))
      )
    )
  )

module.exports = {
  fetchCompanyLists,
}

const { pick } = require('lodash')
const { authorisedRequest } = require('../../lib/authorised-request')
const config = require('../../config')

function fetchRawCompanyListItems(token, id) {
  return authorisedRequest(token, {
    method: 'GET',
    url: `${config.apiRoot}/v4/company-list/${id}/item`,
  })
}

const fetchRawCompanyList = (token) =>
  authorisedRequest(token, {
    method: 'GET',
    url: `${config.apiRoot}/v4/company-list`,
  })

const transformRawCompany = ({ latest_interaction, company }) => ({
  company: pick(company, ['id', 'name']),
  latestInteraction: pick(latest_interaction, ['date', 'id', 'subject']),
  ditParticipants: latest_interaction
    ? latest_interaction.dit_participants.map((participant) => ({
        name: participant.adviser ? participant.adviser.name : '',
        team: participant.team ? participant.team.name : '',
      }))
    : [],
})

const fetchCompanyLists = (token) =>
  fetchRawCompanyList(token).then(({ results }) =>
    Promise.all(
      results.map((list) =>
        fetchRawCompanyListItems(token, list.id).then(({ results }) => ({
          ...pick(list, ['id', 'name']),
          companies: results.map(transformRawCompany),
        }))
      )
    )
  )

module.exports = {
  fetchCompanyLists,
}

Remove comments for ignoring eslint


import resolveNode from "./resolveNode";
// import {resolve} from "path";

export default function (filename, regexps, root, extensions) {
	for(let redirectPair of regexps) {
		const regexp = redirectPair[0];

		if(regexp.test(filename)) {
			console.log("FOUND MATCH", regexp);
			let redirect = redirectPair[1];
			console.log("REDIRECTING TO", redirect);
			// if redirect is of "different/path/$1.js" form
			if(/\$\d/.test(redirect)) {
				// "abs/path/to/path/lib.js".match(/path/(\w+).js$/)[0] ->
				// "path/lib.js".replace(/path/(\w+).js$/, "different/path/$1.js") ->
				// "different/path/lib.js"
				redirect = filename.match(regexp)[0].replace(regexp, redirect);
			}

			// return resolve(root, redirect);
			return {
				redirected: resolveNode(root, redirect, extensions),
				redirect
			};
		}
	}

	return null;
}

import resolveNode from "./resolveNode";
// import {resolve} from "path";

export default function (filename, regexps, root, extensions) {
	const redirectPair = regexps.find(([regexp]) => regexp.test(filename));

	if(redirectPair) {
		let [regexp, redirect] = redirectPair;
		console.log("FOUND MATCH", regexp);
		console.log("REDIRECTING TO", redirect);
		// if redirect is of "different/path/$1.js" form
		if(/\$\d/.test(redirect)) {
			// "abs/path/to/path/lib.js".match(/path/(\w+).js$/)[0] ->
			// "path/lib.js".replace(/path/(\w+).js$/, "different/path/$1.js") ->
			// "different/path/lib.js"
			redirect = filename.match(regexp)[0].replace(regexp, redirect);
		}

		// return resolve(root, redirect);
		return {
			redirected: resolveNode(root, redirect, extensions),
			redirect
		};
	}

	return null;
}

Change for loop to Array.find


import React from 'react';

import Utility from '../../shared/util/Utility';
import * as Security from '../../shared/reducers/Security';
import * as Lessons from '../../shared/reducers/Lessons';

export const Reducers = [Security, Lessons];

export function requireAuthentication(Component) {
  class AuthenticatedComponent extends React.Component {

    componentWillMount() {
      this.checkAuth(this.props.loggedIn);
    }

    componentWillReceiveProps(nextProps) {
      this.checkAuth(nextProps.loggedIn);
    }

    checkAuth(isAuthenticated) {
      if (!isAuthenticated) {
        const redirectAfterLogin = this.props.location.pathname;
        this.props.setPageByName('login', { redirectUrl: redirectAfterLogin });
        // this.props.dispatch(pushState(null, `/login?next=${redirectAfterLogin}`));
      }
    }

    render() {
      return (
        <div>
          {this.props.loggedIn
                        ? <Component {...this.props} />
                        : null
                    }
        </div>
      );
    }
    }

  AuthenticatedComponent.defaultProps = Utility.reduxEnabledDefaultProps({

  }, Reducers);

  AuthenticatedComponent.propTypes = Utility.reduxEnabledPropTypes({

  }, Reducers);

  return Utility.superConnect(this, Reducers)(AuthenticatedComponent);
}

import React from 'react';

import Utility from '../../shared/util/Utility';
import * as Security from '../../shared/reducers/Security';
import * as Lessons from '../../shared/reducers/Lessons';

export const Reducers = [Security, Lessons];

export function requireAuthentication(Component) {
  class AuthenticatedComponent extends React.Component {

    componentWillMount() {
      this.checkAuth(this.props.loggedIn);
    }

    componentWillReceiveProps(nextProps) {
      this.checkAuth(nextProps.loggedIn);
    }

    checkAuth(isAuthenticated) {
      if (!isAuthenticated) {
        const redirectAfterLogin = this.props.location.pathname;
        this.props.setPageByName('start', { redirectUrl: redirectAfterLogin });
        // this.props.dispatch(pushState(null, `/login?next=${redirectAfterLogin}`));
      }
    }

    render() {
      return (
        <div>
          {this.props.loggedIn
                        ? <Component {...this.props} />
                        : null
                    }
        </div>
      );
    }
    }

  AuthenticatedComponent.defaultProps = Utility.reduxEnabledDefaultProps({

  }, Reducers);

  AuthenticatedComponent.propTypes = Utility.reduxEnabledPropTypes({

  }, Reducers);

  return Utility.superConnect(this, Reducers)(AuthenticatedComponent);
}

Change so that users are redirected to new landing page


import { replace } from './actions'
import { NAVIGATE } from './constants'

export default class History {
  constructor (store) {
    this.store = store
  }

  listen () {
    window.addEventListener('popstate', event => {
      this.onPopHref(event.state)
    }, false)
  }

  update (action) {
    const href = this.getCurrentHref()

    if (action.type === NAVIGATE) {
      if (href && action.href !== href) {
        this.pushHref(action.href)
      } else {
        this.replaceHref(action.href)
      }
    }
  }

  pushHref (href) {
    window.history.pushState(href, null, href)
  }

  replaceHref (href) {
    window.history.replaceState(href, null, href)
  }

  onPopHref (href) {
    this.store.dispatch(replace(href))
  }

  getCurrentHref () {
    return window.history.state
  }
}

import { replace } from './actions'
import { NAVIGATE } from './constants'

export default class History {
  constructor (store) {
    this.store = store
  }

  listen () {
    window.addEventListener('popstate', event => {
      const { state } = event

      if (typeof state === 'string') {
        this.onPopHref(state)
      }
    }, false)
  }

  update (action) {
    const href = this.getCurrentHref()

    if (action.type === NAVIGATE) {
      if (href && action.href !== href) {
        this.pushHref(action.href)
      } else {
        this.replaceHref(action.href)
      }
    }
  }

  pushHref (href) {
    window.history.pushState(href, null, href)
  }

  replaceHref (href) {
    window.history.replaceState(href, null, href)
  }

  onPopHref (href) {
    this.store.dispatch(replace(href))
  }

  getCurrentHref () {
    return window.history.state
  }
}

Fix popstate issue in Safari


var sys = require('util');
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function() {
	sys.puts("State: " + this.readyState);

	if (this.readyState === 4) {
		sys.puts("Complete.\nBody length: " + this.responseText.length);
		sys.puts("Body:\n" + this.responseText);
	}
};

xhr.open("GET", "http://driverdan.com");
xhr.send();

var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function() {
	console.log("State: " + this.readyState);

	if (this.readyState === 4) {
		console.log("Complete.\nBody length: " + this.responseText.length);
		console.log("Body:\n" + this.responseText);
	}
};

xhr.open("GET", "http://driverdan.com");
xhr.send();

Use console.log() instead of util.puts()


window.onerror = function (message, source, lineno, colno, error) {
  logError(error);
};

window.onerror = function (message, source, lineno, colno, error) {
  logError(error instanceof Error ? error : new Error(error || message));
};

Improve logging of errors in renderer


import * as types from './constants';

export function form(config) {
  const initialState = {
    id: '',
    isActive: false,
    lastUpdated: 0,
    form: {},
    url: '',
    error: '',
  };

  return (state = initialState, action) => {
    // Only proceed for this form.
    if (action.name !== config.name) {
      return state;
    }
    switch (action.type) {
      case types.FORM_CLEAR_ERROR:
        return {
          ...state,
          error: '',
        };
      case types.FORM_REQUEST:
        return {
          ...state,
          isActive: true,
          id: action.id,
          url: action.url,
          error: '',
        };
      case types.FORM_SUCCESS:
        return {
          ...state,
          id: action.form._id,
          form: action.form,
          isActive: false,
          url: action.url || state.url,
          error: '',
        };
      case types.FORM_FAILURE:
        return {
          ...state,
          isActive: false,
          isInvalid: true,
          error: action.error,
        };
      case types.FORM_SAVE:
        return {
          ...state,
          isActive: true,
        };
      case types.FORM_RESET:
        return initialState;
      default:
        return state;
    }
  };
}

import * as types from './constants';

export function form(config) {
  const initialState = {
    id: '',
    isActive: false,
    lastUpdated: 0,
    form: {},
    url: '',
    error: '',
  };

  return (state = initialState, action) => {
    // Only proceed for this form.
    if (action.name !== config.name) {
      return state;
    }
    switch (action.type) {
      case types.FORM_CLEAR_ERROR:
        return {
          ...state,
          error: '',
        };
      case types.FORM_REQUEST:
        return {
          ...state,
          isActive: true,
          id: action.id,
          form: {},
          url: action.url,
          error: '',
        };
      case types.FORM_SUCCESS:
        return {
          ...state,
          isActive: false,
          id: action.form._id,
          form: action.form,
          url: action.url || state.url,
          error: '',
        };
      case types.FORM_FAILURE:
        return {
          ...state,
          isActive: false,
          isInvalid: true,
          error: action.error,
        };
      case types.FORM_SAVE:
        return {
          ...state,
          isActive: true,
        };
      case types.FORM_RESET:
        return initialState;
      default:
        return state;
    }
  };
}

Clear form on form request.


const path = require('path')
const fs = require('fs-extra')
const dataDir = require('data-dir')
const logger = require('./logger')

/**
 * Get json form given directory or file. Return empty
 * data if nothing is found.
 *
 * @param {string} src source to read data from
 *
 * @returns {object}
 */
module.exports = src => {
  const dataSource = path.resolve(src)
  if (fs.existsSync(dataSource)) {
    try {
      if (fs.lstatSync(dataSource).isDirectory()) {
        return dataDir(dataSource)
      } else {
        return fs.readJsonSync(dataSource)
      }
    } catch (err) {
      logger.fail(`Error reading data from ${src}`, err)
    }
  } else {
    logger.warn(`Using empty global data`)
    return {}
  }
}

const path = require('path')
const fs = require('fs-extra')
const dataDir = require('data-dir')
const logger = require('./logger')

/**
 * Get json form given directory or file. Return empty
 * data if nothing is found.
 *
 * @param {string} src source to read data from
 *
 * @returns {object}
 */
module.exports = src => {
  if (src) {
    try {
      const source = path.resolve(src)
      if (fs.existsSync(source)) {
        if (fs.lstatSync(source).isDirectory()) {
          return dataDir(source)
        } else {
          return fs.readJsonSync(source)
        }
      }
    } catch (err) {
      logger.fail(`Error reading data from ${src}`, err)
    }
  }
  logger.warn(`Using empty global data`)
  return {}
}

Fix crash when no data option is passed


/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller from '@ember/controller';
import {inject as service} from '@ember/service';

export default class ApplicationController extends Controller {
    @service billing;
    @service customViews;
    @service config;
    @service dropdown;
    @service router;
    @service session;
    @service settings;
    @service ui;

    get showBilling() {
        return this.config.get('hostSettings.billing.enabled');
    }

    get showNavMenu() {
        // if we're in fullscreen mode don't show the nav menu
        if (this.ui.isFullScreen) {
            return false;
        }

        // we need to defer showing the navigation menu until the session.user
        // promise has fulfilled so that gh-user-can-admin has the correct data
        if (!this.session.isAuthenticated || !this.session.user.isFulfilled) {
            return false;
        }

        return (this.router.currentRouteName !== 'error404' || this.session.isAuthenticated)
                && !this.router.currentRouteName.match(/(signin|signup|setup|reset)/);
    }
}

/* eslint-disable ghost/ember/alias-model-in-controller */
import Controller from '@ember/controller';
import {computed} from '@ember/object';
import {inject as service} from '@ember/service';

export default Controller.extend({
    billing: service(),
    customViews: service(),
    config: service(),
    dropdown: service(),
    router: service(),
    session: service(),
    settings: service(),
    ui: service(),

    showBilling: computed.reads('config.hostSettings.billing.enabled'),
    showNavMenu: computed('router.currentRouteName', 'session.{isAuthenticated,user.isFulfilled}', 'ui.isFullScreen', function () {
        let {router, session, ui} = this;

        // if we're in fullscreen mode don't show the nav menu
        if (ui.isFullScreen) {
            return false;
        }

        // we need to defer showing the navigation menu until the session.user
        // promise has fulfilled so that gh-user-can-admin has the correct data
        if (!session.isAuthenticated || !session.user.isFulfilled) {
            return false;
        }

        return (router.currentRouteName !== 'error404' || session.isAuthenticated)
                && !router.currentRouteName.match(/(signin|signup|setup|reset)/);
    })
});

Revert "Refactored ApplicationController to use native class"


/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import { AppRegistry } from 'react-native';
import { Provider } from 'react-redux';
import { ErrorHandler } from 'redux-persist-error-handler';
import { Client as BugsnagClient } from 'bugsnag-react-native';
import { name as appName } from '../app.json';
import { store, persistedStore } from './Store';
import MSupplyMobileApp from './mSupplyMobileApp';

// eslint-disable-next-line no-unused-vars
const bugsnagClient = new BugsnagClient();

function App() {
  return (
    <ErrorHandler persistedStore={persistedStore}>
      <Provider store={store}>
        <MSupplyMobileApp />
      </Provider>
    </ErrorHandler>
  );
}

AppRegistry.registerComponent(appName, () => App);

/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import { AppRegistry, YellowBox } from 'react-native';
import { Provider } from 'react-redux';
import { ErrorHandler } from 'redux-persist-error-handler';
import { Client as BugsnagClient } from 'bugsnag-react-native';
import { name as appName } from '../app.json';
import { store, persistedStore } from './Store';
import MSupplyMobileApp from './mSupplyMobileApp';

// eslint-disable-next-line no-unused-vars
const bugsnagClient = new BugsnagClient();

function App() {
  return (
    <ErrorHandler persistedStore={persistedStore}>
      <Provider store={store}>
        <MSupplyMobileApp />
      </Provider>
    </ErrorHandler>
  );
}

YellowBox.ignoreWarnings(['Setting a timer']);
AppRegistry.registerComponent(appName, () => App);

Add ignore warnings for setting a timer