<commit_before>const { dialog } = require('electron').remote
const fs = require('fs')
const bubbleTxt = require('../helpers/bubbleTxt.js')

let threatsArray = []
let constraintsArray = []
let mechanismsArray = []
let vulnerabilityArray = []
/**
 * generate security report
 *
 * @param {Object} cy cytoscape instance
 */
module.exports = function generateReport (cy) {
  cy.nodes().map(node => {
    if (node.data().asto.concept === 'threat') {
      threatsArray.push(node)
    }
    if (node.data().asto.concept === 'constraint') {
      constraintsArray.push(node)
    }
  })

  const reportTittle = `# Security Report\n\n`
  const numberOfThreats = `Number of threats: ${threatsArray.length}\n`
  const numberOfTConstraints = `Number of constraints: ${
    constraintsArray.length
  }\n`
  const numberOfMechanisms = `Number of threats: ${mechanismsArray.length}\n`
  const numberOfVulnerabilities = `Number of threats: ${
    vulnerabilityArray.length
  }\n`

  const dataToWrite = reportTittle
    .concat(numberOfThreats)
    .concat(numberOfTConstraints)
    .concat(numberOfMechanisms)
    .concat(numberOfVulnerabilities)

  let dialogOptions = []
  process.platform === 'darwin'
    ? (dialogOptions = ['openFile', 'openDirectory'])
    : (dialogOptions = ['openFile'])
  dialog.showSaveDialog(
    {
      properties: [...dialogOptions],
      filters: [{ name: 'markdown', extensions: ['md'] }]
    },
    filename => {
      fs.writeFile(`${filename}`, dataToWrite, err => {
        if (err) {
          dialog.showErrorBox(
            'There was an error while saving the file',
            err.message
          )
        }
        bubbleTxt('security report generated\n👍')
      })
    }
  )
}
<commit_msg>update security report<commit_after>const { dialog } = require('electron').remote
const fs = require('fs')
const bubbleTxt = require('../helpers/bubbleTxt.js')
const bubbleHTML = require('../helpers/bubbleHTML.js')

let threatsArray = []
let constraintsArray = []
let mechanismsArray = []
let vulnerabilityArray = []
let buttonCounter = 0
/**
 * generate security report
 *
 * @param {Object} cy cytoscape instance
 */
module.exports = function generateReport (cy) {
  cy.nodes().map(node => {
    if (node.data().asto.concept === 'threat') {
      threatsArray.push(node)
    }
    if (node.data().asto.concept === 'constraint') {
      constraintsArray.push(node)
    }
  })

  const reportTittle = `# Security Report\n\n`
  const numberOfThreats = `Number of threats: ${threatsArray.length}\n`
  const numberOfTConstraints = `Number of constraints: ${
    constraintsArray.length
  }\n`
  const numberOfMechanisms = `Number of threats: ${mechanismsArray.length}\n`
  const numberOfVulnerabilities = `Number of threats: ${
    vulnerabilityArray.length
  }\n`

  const dataToWrite = reportTittle
    .concat(numberOfThreats)
    .concat(numberOfTConstraints)
    .concat(numberOfMechanisms)
    .concat(numberOfVulnerabilities)

  const saveFile = () => {
    dialog.showSaveDialog(
      { filters: [{ name: 'markdown', extensions: ['md'] }] },
      filename => {
        // requestVulnerableData(filename, nodesKeywords)
        fs.writeFile(filename, dataToWrite, err => {
          if (err) console.error(`Error: ${err.message}`)
        })
        bubbleTxt('security report generated\n👍')
      }
    )
  }

  const reportButton = `Do you want to save the security report?<button id='reportButton-${buttonCounter}' class='menu-btn' style='color: var(--main-tx-color); background-color: var(--main-bg-color); width: 45px; height: 25px;'>yes</button>`

  bubbleHTML(reportButton)
  document
    .getElementById(`reportButton-${buttonCounter}`)
    .addEventListener('click', () => {
      saveFile()
    })

  buttonCounter += 1
}
<|endoftext|>


<commit_before>// adopted from: https://github.com/shannonmoeller/handlebars-registrar
// Copyright Shannon Moeller <me@shannonmoeller.com> (MIT)

'use strict'

// Requirements
// ///////////////////////////////////////////////////////////////////////////////

var requireGlob = require('./requireGlobFilez')
var pathSepPatterns = /[ \/.-]+/g // matches spaces, forward slashes, dots and hyphens

function extend (target) {
  var arg
  var key
  var len = arguments.length
  var i = 0

  while (++i < len) {
    arg = arguments[i]
    if (!arg) continue

    for (key in arg) {
      if (arg.hasOwnProperty(key)) target[key] = arg[key]
    }
  }
  return target
}

// Utility functions
// ///////////////////////////////////////////////////////////////////////////////

function parseHelperName (file) {
  var shortPath = file && file.shortPath
  return shortPath.replace(pathSepPatterns, '-')
}

function parsePartialName (file) {
  return file && file.shortPath
}

function reducer (obj, file) {
  var handlebars = this.handlebars
  var content = file && file.exports

  if (!content) return obj
  if (typeof content.register === 'function') {
    content.register(handlebars)
    return obj
  }

  switch (typeof content) {
    case 'function' :
      obj[this.keygen(file)] = content
      break
    case 'object'   :
      extend(obj, content)
      break
    // no default
  }
  return obj
}

// Effortless wiring of Handlebars helpers and partials
// ///////////////////////////////////////////////////////////////////////////////

/**
 * @type {Function}
 * @param {Object} [handlebars]                  : Handlebars instance.
 * @param {Object=} [options]                    : Plugin options.
 * @param {Boolean=} [options.bustCache]         : Whether to force the reload of modules by deleting them from the cache.
 * @param {String=} [options.cwd]                : Current working directory. Defaults to `process.cwd()`.
 * @param {Object
 *        |String
 *        |Array.<String>
 *        |Function=} [options.helpers]          : One or more glob strings matching helpers.
 * @param {Function=} [options.parseHelperName]  : Custom name generator for helpers.
 * @param {Function=} [options.helpersReducer]   : Custom reducer for registering helpers.
 * @param {Object
 *        |String
 *        |Array.<String>
 *        |Function=} [options.partials]         : One or more glob strings matching partials.
 * @param {Function=} [options.parsePartialName] : Custom name generator for partials.
 * @param {Function=} [options.partialsReducer]  : Custom reducer for registering partials.
 * @return {Object} Handlebars instance.
 */

function registrar (handlebars, options) {
  options = options || {}

  var helpers = options.helpers
  var partials = options.partials

  // expose handlebars to custom reducers
  options.handlebars = handlebars

  // register helpers
  if (helpers) {
    options.keygen = options.parseHelperName || parseHelperName
    options.reducer = options.helpersReducer || reducer.bind(options)
    helpers = requireGlob.sync(helpers, options)
    handlebars.registerHelper(helpers)
  }

  // register partials
  if (partials) {
    options.keygen = options.parsePartialName || parsePartialName
    options.reducer = options.partialReducer || reducer.bind(options)
    partials = requireGlob.sync(partials, options)
    handlebars.registerPartial(partials)
  }

  return handlebars
}

module.exports = registrar
<commit_msg>remove file app/src/registrarHandlebars -- moved to toolz<commit_after><|endoftext|>


<commit_before>'use strict';

/**
 * SubApp for the Browser
 *
 * @module juju
 * @submodule subapps
 *
 */
YUI.add('subapp-browser', function(Y) {
  var ns = Y.namespace('juju.subapps'),
      models = Y.namespace('juju.models');

  /**
   * Browser Sub App for the Juju Gui.
   *
   * @class Browser
   * @extends {juju.SubApp}
   *
   */
  ns.Browser = Y.Base.create('subapp-browser', Y.juju.SubApp, [], {
    /**
        Show or hide the details panel.

        @method _detailsVisible
        @param {Boolean} visible set the panel to hide or show.

     */
    _detailsVisible: function(visible) {
      var detailsNode = Y.one('.bws-view-data');
      if (detailsNode) {
        if (visible) {
          detailsNode.show();
        }
        else {
          detailsNode.hide();
        }
      }
    },

    /**
        Given the current subapp state, generate a url to pass up to the
        routing code to route to.

        @method _getStateUrl
        @param {Object} change the values to change in the current state.

     */
    _getStateUrl: function(change) {
      var urlParts = ['/bws'];
      this._oldState = this._viewState;
      this._viewState = Y.merge(this._viewState, change);

      urlParts.push(this._viewState.viewmode);
      if (this._viewState.search) {
        urlParts.push('search');
      }
      if (this._viewState.charmID) {
        urlParts.push(this._viewState.charmID);
      }
      var url = urlParts.join('/');
      if (this._viewState.querystring) {
        url = Y.Lang.sub('{ url }?{ qs }', {
          url: url,
          qs: this._viewState.querystring
        });
      }
      return url;
    },

    /**
     * Generate a standard shared set of cfg all Views can expect to see.
     *
     * @method _getViewCfg
     * @param {Object} cfg additional config to merge into the default view
     * config.
     *
     */
    _getViewCfg: function(cfg) {
      return Y.merge(cfg, {
        db: this.get('db'),
        store: this.get('store')
      });
    },

    /**
       Create an initial subapp state for later url generation.

       @method _initState

     */
    _initState: function() {
      this._oldState = {
        viewmode: null,
        search: null,
        charmID: null
      };
      this._viewState = Y.merge(this._oldState, {});
    },

    /**
      Determine if we should render the charm details based on the current
      state.

      @return {Boolean} true if should show.

    */
    _shouldShowCharm: function() {
      if (
          this._viewState.charmID &&
          (
           this._hasStateChanged('charmID') ||
           this._hasStateChanged('viewmode')
          )
      ) {
        return true;
      } else {
        return false;
      }
    },

    /**
      Determine if we should render the editorial content based on the current
      state.

      @return {Boolean} true if should show.

    */
    _shouldShowEditorial: function() {
      if (
          !this._viewState.search &&
          this._hasStateChanged('viewmode')
      ) {
        return true;
      } else {
        return false;
      }
    },

    /**
      Determine if we should render the search results based on the current
      state.

      @return {Boolean} true if should show.

    */
    _shouldShowSearch: function() {
      if (
          this._viewState.search &&
          (
           this._hasStateChanged('search') ||
           this._hasStateChanged('viewmode') ||
           this._hasStateChanged('querystring')
          )
      ) {
        return true;
      } else {
        return false;
      }
    },

    /**
      Verify that a particular part of the state has changed.

      @method _hasStateChanged
      @param {String} field the part of the state to check.

     */
    _hasStateChanged: function(field) {
      if (this._oldState[field] === this._viewState[field]) {
        return false;
      } else {
        return true;
      }
    },

    /**
      Update the oldState with the viewState now that we're done processing
      the request.

      @method _saveState

     */
    _saveState: function() {
      this._oldState = Y.merge(
          this._oldState,
          this._viewState);
    },

    /**
       Given the params in the route determine what the new state is going to
       be.

       @method _updateState
       @param {String} path the requested path.
       @param {Object} params the params from the request payload.

     */
    _updateState: function(path, params) {
      // Update the viewmode. Every request has a viewmode.
      this._viewState.viewmode = params.viewmode;

      // Check for a charm id in the request.
      if (params.id && params.id !== 'search') {
        this._viewState.charmID = params.id;
      } else {
        this._viewState.charmID = null;
      }

      // Check for search in the request.
      if (path.indexOf('search') !== -1) {
        this._viewState.search = true;
      } else {
        this._viewState.search = false;
      }
    },

    /**
     * The available Views run from this sub app.
     * @attribute views
     *
     */
    views: {
      fullscreen: {
        type: 'juju.browser.views.FullScreen',
        preserve: false
      },
      sidebar: {
        type: 'juju.browser.views.Sidebar',
        preserve: false
      }
    },

    /**
       Cleanup after ourselves on destroy.

       @method destructor

     */
    destructor: function() {
      this._cacheCharms.destroy();
      delete this._viewState;
    },

    /**
     * General app initializer
     *
     * @method initializer
     * @param {Object} cfg general init config object.
     *
     */
    initializer: function(cfg) {
      // Hold onto charm data so we can pass model instances to other views when
      // charms are selected.
      this._cacheCharms = new models.BrowserCharmList();
      this._initState();

      // Listen for navigate events from any views we're rendering.
      this.on('*:viewNavigate', function(ev) {
        var url;
        if (ev.url) {
          url = ev.url;
        } else if (ev.change) {
          url = this._getStateUrl(ev.change);
        }
        this.navigate(url);
      });
    },

    /**
     * Render the charm details view
     *
     * @method renderCharmDetails
     * @param {Request} req current request object.
     * @param {Response} res current response object.
     * @param {function} next callable for the next route in the chain.
     *
     */
    renderCharmDetails: function(req, res, next) {
      var charmID = req.params.id;
      var extraCfg = {
        charmID: charmID,
        container: Y.Node.create('<div class="charmview"/>'),
        deploy: this.get('deploy')
      };

      // The details view needs to know if we're using a fullscreen template
      // or the sidebar version.
      if (this._viewState.viewmode === 'fullscreen') {
        extraCfg.isFullscreen = true;
      }

      // Gotten from the sidebar creating the cache.
      var model = this._cacheCharms.getById(charmID);

      if (model) {
        extraCfg.charm = model;
      }

      this._details = new Y.juju.browser.views.BrowserCharmView(
          this._getViewCfg(extraCfg));
      this._details.render();
      this._details.addTarget(this);
    },

    /**
      Render editorial content into the parent view when required.

      The parent view is either fullscreen/sidebar which determines how the
      editorial content is to be rendered.

      @method renderEditorial
      @param {Request} req current request object.
      @param {Response} res current response object.
      @param {function} next callable for the next route in the chain.

     */
    renderEditorial: function(req, res, next) {
      // If loading the interesting content then it's not a search going on.
      var container = this.get('container'),
          extraCfg = {};

      if (this._viewState.viewmode === 'fullscreen') {
        // The fullscreen view requires that there be no editorial content if
        // we're looking at a specific charm. The div we dump our content into
        // is shared. So if the url is /fullscreen show editorial content, but
        // if it's not, there's something else handling displaying the
        // view-data.
        extraCfg.renderTo = container.one('.bws-view-data');
        extraCfg.isFullscreen = true;
      } else {
        // If this is the sidebar view, then the editorial content goes into a
        // different div since we can view both editorial content and
        // view-data (such as a charm details) side by side.
        extraCfg.renderTo = container.one('.bws-content');
      }

      this._editorial = new Y.juju.browser.views.EditorialView(
          this._getViewCfg(extraCfg));

      this._editorial.render();
      this._editorial.addTarget(this);

      // Add any sidebar charms to the running cache.
      this._cacheCharms.add(this._editorial._cacheCharms);
    },

    /**
     * Render search results
     *
     * @method renderSearchResults
     * @param {Request} req current request object.
     * @param {Response} res current response object.
     * @param {function} next callable for the next route in the chain.
     */
    renderSearchResults: function(req, res, next) {
      var container = this.get('container'),
          extraCfg = {},
          query;
      //XXX jcsackett: This should use req, not viewstate
      if (this._viewState.querystring) {
        query = Y.QueryString.parse(this._viewState.querystring);
      } else {
        // If there's no querystring, we need a default "empty" search.
        query = {text: ''};
      }

      if (req.params.viewmode === 'fullscreen') {
        extraCfg.renderTo = container.one('.bws-view-data');
        extraCfg.isFullscreen = true;
      } else {
        extraCfg.renderTo = container.one('.bws-content');
      }
      extraCfg.text = query.text;
      this._search = new Y.juju.browser.views.BrowserSearchView(
          this._getViewCfg(extraCfg));
      this._search.render();
      this._search.addTarget(this);
    },

    /**
     * Render the fullscreen view to the client.
     *
     * @method fullscreen
     * @param {Request} req current request object.
     * @param {Response} res current response object.
     * @param {function} next callable for the next route in the chain.
     *
     */
    fullscreen: function(req, res, next) {
      // If we've switched to viewmode fullscreen, we need to render it.
      // We know the viewmode is already fullscreen because we're in this
      // function.
      if (this._hasStateChanged('viewmode')) {
        Y.one('#subapp-browser').setStyle('display', 'block');
        this._fullscreen = this.showView('fullscreen', this._getViewCfg());
      }

      // If we've changed the charmID or the viewmode has changed and we have
      // a charmID, render charmDetails.
      if (this._shouldShowCharm()) {
        this._detailsVisible(true);
        this.renderCharmDetails(req, res, next);
      } else if (this._shouldShowSearch()) {
        // Render search results if search is in the url and the viewmode or
        // the search has been changed in the state.
        this.renderSearchResults(req, res, next);
      } else if (!this._viewState.charmID) {
        // Render the editorial in fullscreen only if we don't have a charmid
        this.renderEditorial(req, res, next);
      }

      // Sync that the state has changed.
      this._saveState();
      next();
    },

    /**
     * Handle the route for the sidebar view.
     *
     * @method sidebar
     * @param {Request} req current request object.
     * @param {Response} res current response object.
     * @param {function} next callable for the next route in the chain.
     *
     */
    sidebar: function(req, res, next) {
      // If we've switched to viewmode sidebar, we need to render it.
      if (this._hasStateChanged('viewmode')) {
        Y.one('#subapp-browser').setStyle('display', 'block');
        this._sidebar = this.showView('sidebar', this._getViewCfg());
      }

      // Render search results if search is in the url and the viewmode or the
      // search has been changed in the state.
      if (this._shouldShowSearch()) {
        this.renderSearchResults(req, res, next);
      }

      if (this._shouldShowEditorial()) {
        this.renderEditorial(req, res, next);
      }

      // If we've changed the charmID or the viewmode has changed and we have
      // a charmID, render charmDetails.
      if (this._shouldShowCharm()) {
        this._detailsVisible(true);
        this.renderCharmDetails(req, res, next);
      }

      // If the sidebar is the final part of the route, then hide the div for
      // viewing the charm details.
      if (!this._viewState.charmID) {
        this._detailsVisible(false);
        var detailsNode = Y.one('.bws-view-data');
        if (detailsNode) {
          detailsNode.hide();
        }
        // Clean up any details we've got.
        if (this._details) {
          this._details.destroy({remove: true});
        }
      }

      // Sync that the state has changed.
      this._saveState();
      next();
    },

    /**
        Dispatch to the correct viewmode based on the route that was hit.

       @method routeView
       @param {Request} req current request object.
       @param {Response} res current response object.
       @param {function} next callable for the next route in the chain.

     */
    routeView: function(req, res, next) {
      // Update the state for the rest of things to figure out what to do.
      this._updateState(req.path, req.params);
      this[req.params.viewmode](req, res, next);
    }

  }, {
    ATTRS: {
      /**
         @attribute container
         @default '#subapp-browser'
         @type {String}

       */
      container: {
        value: '#subapp-browser'
      },

      /**
         @attribute store
         @default Charmworld0
         @type {Charmworld0}

       */
      store: {
        /**
          We keep one instance of the store and will work on caching results
          at the app level so that routes can share api calls. However, in
          tests there's no config for talking to the api so we have to watch
          out in test runs and allow the store to be broken.

          method store.valueFn

        */
        valueFn: function() {
          var url = '';
          if (!window.juju_config || ! window.juju_config.charmworldURL) {
            console.error('No juju config to fetch charmworld store url');
          } else {
            url = window.juju_config.charmworldURL;
          }
          return new Y.juju.Charmworld0({
            'apiHost': url
          });
        }
      },

      /**
         @attribute routes
         @default Array of subapp routes.
         @type {Array}

       */
      routes: {
        value: [
          // Double routes are needed to catch /fullscreen and /fullscreen/
          { path: '/bws/:viewmode/', callbacks: 'routeView' },
          { path: '/bws/:viewmode/search/', callbacks: 'routeView' },
          { path: '/bws/:viewmode/search/*id/', callbacks: 'routeView' },
          { path: '/bws/:viewmode/*id/', callbacks: 'routeView' }
        ]
      },

      /**
         @attribute urlNamespace
         @default 'charmstore'
         @type {String}

       */
      urlNamespace: {
        value: 'charmstore'
      },

      /**
         The "deploy" function prompts the user for service configuration and
         deploys a service.

         @attribute deploy
         @default undefined
         @type {Function}

       */
      deploy: {}

    }
  });

}, '0.1.0', {
  requires: [
    'juju-charm-store',
    'juju-models',
    'querystring-parse',
    'sub-app',
    'subapp-browser-charmview',
    'subapp-browser-editorial',
    'subapp-browser-fullscreen',
    'subapp-browser-searchview',
    'subapp-browser-sidebar'
  ]
});
<commit_msg>Updates to viewstate management of querystring and documentation.<commit_after>'use strict';

/**
 * SubApp for the Browser
 *
 * @module juju
 * @submodule subapps
 *
 */
YUI.add('subapp-browser', function(Y) {
  var ns = Y.namespace('juju.subapps'),
      models = Y.namespace('juju.models');

  /**
   * Browser Sub App for the Juju Gui.
   *
   * @class Browser
   * @extends {juju.SubApp}
   *
   */
  ns.Browser = Y.Base.create('subapp-browser', Y.juju.SubApp, [], {
    /**
     *  Show or hide the details panel.
     *
     *  @method _detailsVisible
     *  @param {Boolean} visible set the panel to hide or show.
     *
     */
    _detailsVisible: function(visible) {
      var detailsNode = Y.one('.bws-view-data');
      if (detailsNode) {
        if (visible) {
          detailsNode.show();
        }
        else {
          detailsNode.hide();
        }
      }
    },

    /**
     *  Given the current subapp state, generate a url to pass up to the
     *  routing code to route to.
     *
     *  @method _getStateUrl
     *  @param {Object} change the values to change in the current state.
     *
     */
    _getStateUrl: function(change) {
      var urlParts = ['/bws'];
      this._oldState = this._viewState;
      this._viewState = Y.merge(this._viewState, change);

      urlParts.push(this._viewState.viewmode);
      if (this._viewState.search) {
        urlParts.push('search');
      }
      if (this._viewState.charmID) {
        urlParts.push(this._viewState.charmID);
      }
      var url = urlParts.join('/');
      if (this._viewState.querystring) {
        url = Y.Lang.sub('{ url }?{ qs }', {
          url: url,
          qs: this._viewState.querystring
        });
      }
      return url;
    },

    /**
     * Generate a standard shared set of cfg all Views can expect to see.
     *
     * @method _getViewCfg
     * @param {Object} cfg additional config to merge into the default view
     * config.
     *
     */
    _getViewCfg: function(cfg) {
      return Y.merge(cfg, {
        db: this.get('db'),
        store: this.get('store')
      });
    },

    /**
     * Create an initial subapp state for later url generation.
     *
     * @method _initState
     *
     */
    _initState: function() {
      this._oldState = {
        viewmode: null,
        search: null,
        charmID: null
      };
      this._viewState = Y.merge(this._oldState, {});
    },

    /**
     * Determine if we should render the charm details based on the current
     * state.
     *
     * @return {Boolean} true if should show.
     *
     */
    _shouldShowCharm: function() {
      if (
          this._viewState.charmID &&
          (
           this._hasStateChanged('charmID') ||
           this._hasStateChanged('viewmode')
          )
      ) {
        return true;
      } else {
        return false;
      }
    },

    /**
     * Determine if we should render the editorial content based on the current
     * state.
     *
     * @return {Boolean} true if should show.
     *
     */
    _shouldShowEditorial: function() {
      if (
          !this._viewState.search &&
          this._hasStateChanged('viewmode')
      ) {
        return true;
      } else {
        return false;
      }
    },

    /**
      Determine if we should render the search results based on the current
      state.

      @return {Boolean} true if should show.

    */
    _shouldShowSearch: function() {
      if (
          this._viewState.search &&
          (
           this._hasStateChanged('search') ||
           this._hasStateChanged('viewmode') ||
           this._hasStateChanged('querystring')
          )
      ) {
        return true;
      } else {
        return false;
      }
    },

    /**
      Verify that a particular part of the state has changed.

      @method _hasStateChanged
      @param {String} field the part of the state to check.

     */
    _hasStateChanged: function(field) {
      if (this._oldState[field] === this._viewState[field]) {
        return false;
      } else {
        return true;
      }
    },

    /**
      Update the oldState with the viewState now that we're done processing
      the request.

      @method _saveState

     */
    _saveState: function() {
      this._oldState = Y.merge(
          this._oldState,
          this._viewState);
    },

    /**
       Given the params in the route determine what the new state is going to
       be.

       @method _updateState
       @param {Object} req the request payload

     */
    _updateState: function(req) {
      // Update the viewmode. Every request has a viewmode.
      var path = req.path,
          params = req.params,
          query = req.query;

      this._viewState.viewmode = params.viewmode;

      // Check for a charm id in the request.
      if (params.id && params.id !== 'search') {
        this._viewState.charmID = params.id;
      } else {
        this._viewState.charmID = null;
      }

      // Check for search in the request.
      if (path.indexOf('search') !== -1) {
        this._viewState.search = true;
      } else {
        this._viewState.search = false;
      }

      // Check for a querystring
      if (req.query) {
        this._viewState.querystring = Y.QueryString.stringify(req.query);
      }

    },

    /**
     * The available Views run from this sub app.
     * @attribute views
     *
     */
    views: {
      fullscreen: {
        type: 'juju.browser.views.FullScreen',
        preserve: false
      },
      sidebar: {
        type: 'juju.browser.views.Sidebar',
        preserve: false
      }
    },

    /**
       Cleanup after ourselves on destroy.

       @method destructor

     */
    destructor: function() {
      this._cacheCharms.destroy();
      delete this._viewState;
    },

    /**
     * General app initializer
     *
     * @method initializer
     * @param {Object} cfg general init config object.
     *
     */
    initializer: function(cfg) {
      // Hold onto charm data so we can pass model instances to other views when
      // charms are selected.
      this._cacheCharms = new models.BrowserCharmList();
      this._initState();

      // Listen for navigate events from any views we're rendering.
      this.on('*:viewNavigate', function(ev) {
        var url;
        if (ev.url) {
          url = ev.url;
        } else if (ev.change) {
          url = this._getStateUrl(ev.change);
        }
        this.navigate(url);
      });
    },

    /**
     * Render the charm details view
     *
     * @method renderCharmDetails
     * @param {Request} req current request object.
     * @param {Response} res current response object.
     * @param {function} next callable for the next route in the chain.
     *
     */
    renderCharmDetails: function(req, res, next) {
      var charmID = req.params.id;
      var extraCfg = {
        charmID: charmID,
        container: Y.Node.create('<div class="charmview"/>'),
        deploy: this.get('deploy')
      };

      // The details view needs to know if we're using a fullscreen template
      // or the sidebar version.
      if (this._viewState.viewmode === 'fullscreen') {
        extraCfg.isFullscreen = true;
      }

      // Gotten from the sidebar creating the cache.
      var model = this._cacheCharms.getById(charmID);

      if (model) {
        extraCfg.charm = model;
      }

      this._details = new Y.juju.browser.views.BrowserCharmView(
          this._getViewCfg(extraCfg));
      this._details.render();
      this._details.addTarget(this);
    },

    /**
      Render editorial content into the parent view when required.

      The parent view is either fullscreen/sidebar which determines how the
      editorial content is to be rendered.

      @method renderEditorial
      @param {Request} req current request object.
      @param {Response} res current response object.
      @param {function} next callable for the next route in the chain.

     */
    renderEditorial: function(req, res, next) {
      // If loading the interesting content then it's not a search going on.
      var container = this.get('container'),
          extraCfg = {};

      if (this._viewState.viewmode === 'fullscreen') {
        // The fullscreen view requires that there be no editorial content if
        // we're looking at a specific charm. The div we dump our content into
        // is shared. So if the url is /fullscreen show editorial content, but
        // if it's not, there's something else handling displaying the
        // view-data.
        extraCfg.renderTo = container.one('.bws-view-data');
        extraCfg.isFullscreen = true;
      } else {
        // If this is the sidebar view, then the editorial content goes into a
        // different div since we can view both editorial content and
        // view-data (such as a charm details) side by side.
        extraCfg.renderTo = container.one('.bws-content');
      }

      this._editorial = new Y.juju.browser.views.EditorialView(
          this._getViewCfg(extraCfg));

      this._editorial.render();
      this._editorial.addTarget(this);

      // Add any sidebar charms to the running cache.
      this._cacheCharms.add(this._editorial._cacheCharms);
    },

    /**
     * Render search results
     *
     * @method renderSearchResults
     * @param {Request} req current request object.
     * @param {Response} res current response object.
     * @param {function} next callable for the next route in the chain.
     */
    renderSearchResults: function(req, res, next) {
      var container = this.get('container'),
          extraCfg = {},
          query;
      if (req.query) {
        query = req.query;
      } else {
        // If there's no querystring, we need a default "empty" search.
        query = {text: ''};
      }

      if (req.params.viewmode === 'fullscreen') {
        extraCfg.renderTo = container.one('.bws-view-data');
        extraCfg.isFullscreen = true;
      } else {
        extraCfg.renderTo = container.one('.bws-content');
      }
      extraCfg.text = query.text;
      this._search = new Y.juju.browser.views.BrowserSearchView(
          this._getViewCfg(extraCfg));
      this._search.render();
      this._search.addTarget(this);
    },

    /**
     * Render the fullscreen view to the client.
     *
     * @method fullscreen
     * @param {Request} req current request object.
     * @param {Response} res current response object.
     * @param {function} next callable for the next route in the chain.
     *
     */
    fullscreen: function(req, res, next) {
      // If we've switched to viewmode fullscreen, we need to render it.
      // We know the viewmode is already fullscreen because we're in this
      // function.
      if (this._hasStateChanged('viewmode')) {
        Y.one('#subapp-browser').setStyle('display', 'block');
        this._fullscreen = this.showView('fullscreen', this._getViewCfg());
      }

      // If we've changed the charmID or the viewmode has changed and we have
      // a charmID, render charmDetails.
      if (this._shouldShowCharm()) {
        this._detailsVisible(true);
        this.renderCharmDetails(req, res, next);
      } else if (!this._viewState.charmID) {
        // Render the editorial in fullscreen only if we don't have a charmid
        this.renderEditorial(req, res, next);
      } else if (this._shouldShowSearch()) {
        // Render search results if search is in the url and the viewmode or
        // the search has been changed in the state.
        this.renderSearchResults(req, res, next);
      }

      // Sync that the state has changed.
      this._saveState();
      next();
    },

    /**
     * Handle the route for the sidebar view.
     *
     * @method sidebar
     * @param {Request} req current request object.
     * @param {Response} res current response object.
     * @param {function} next callable for the next route in the chain.
     *
     */
    sidebar: function(req, res, next) {
      // If we've switched to viewmode sidebar, we need to render it.
      if (this._hasStateChanged('viewmode')) {
        Y.one('#subapp-browser').setStyle('display', 'block');
        this._sidebar = this.showView('sidebar', this._getViewCfg());
      }

      // Render search results if search is in the url and the viewmode or the
      // search has been changed in the state.
      if (this._shouldShowSearch()) {
        this.renderSearchResults(req, res, next);
      }

      if (this._shouldShowEditorial()) {
        this.renderEditorial(req, res, next);
      }

      // If we've changed the charmID or the viewmode has changed and we have
      // a charmID, render charmDetails.
      if (this._shouldShowCharm()) {
        this._detailsVisible(true);
        this.renderCharmDetails(req, res, next);
      }

      // If the sidebar is the final part of the route, then hide the div for
      // viewing the charm details.
      if (!this._viewState.charmID) {
        this._detailsVisible(false);
        var detailsNode = Y.one('.bws-view-data');
        if (detailsNode) {
          detailsNode.hide();
        }
        // Clean up any details we've got.
        if (this._details) {
          this._details.destroy({remove: true});
        }
      }

      // Sync that the state has changed.
      this._saveState();
      next();
    },

    /**
        Dispatch to the correct viewmode based on the route that was hit.

       @method routeView
       @param {Request} req current request object.
       @param {Response} res current response object.
       @param {function} next callable for the next route in the chain.

     */
    routeView: function(req, res, next) {
      // Update the state for the rest of things to figure out what to do.
      this._updateState(req);
      this[req.params.viewmode](req, res, next);
    }

  }, {
    ATTRS: {
      /**
         @attribute container
         @default '#subapp-browser'
         @type {String}

       */
      container: {
        value: '#subapp-browser'
      },

      /**
         @attribute store
         @default Charmworld0
         @type {Charmworld0}

       */
      store: {
        /**
          We keep one instance of the store and will work on caching results
          at the app level so that routes can share api calls. However, in
          tests there's no config for talking to the api so we have to watch
          out in test runs and allow the store to be broken.

          method store.valueFn

        */
        valueFn: function() {
          var url = '';
          if (!window.juju_config || ! window.juju_config.charmworldURL) {
            console.error('No juju config to fetch charmworld store url');
          } else {
            url = window.juju_config.charmworldURL;
          }
          return new Y.juju.Charmworld0({
            'apiHost': url
          });
        }
      },

      /**
         @attribute routes
         @default Array of subapp routes.
         @type {Array}

       */
      routes: {
        value: [
          // Double routes are needed to catch /fullscreen and /fullscreen/
          { path: '/bws/:viewmode/', callbacks: 'routeView' },
          { path: '/bws/:viewmode/search/', callbacks: 'routeView' },
          { path: '/bws/:viewmode/search/*id/', callbacks: 'routeView' },
          { path: '/bws/:viewmode/*id/', callbacks: 'routeView' }
        ]
      },

      /**
         @attribute urlNamespace
         @default 'charmstore'
         @type {String}

       */
      urlNamespace: {
        value: 'charmstore'
      },

      /**
         The "deploy" function prompts the user for service configuration and
         deploys a service.

         @attribute deploy
         @default undefined
         @type {Function}

       */
      deploy: {}

    }
  });

}, '0.1.0', {
  requires: [
    'juju-charm-store',
    'juju-models',
    'querystring',
    'sub-app',
    'subapp-browser-charmview',
    'subapp-browser-editorial',
    'subapp-browser-fullscreen',
    'subapp-browser-searchview',
    'subapp-browser-sidebar'
  ]
});
<|endoftext|>


<commit_before>import PaneTypeUserStorageService from 'glazier/services/pane_type_user_storage';
import assertResolved from 'helpers/promise_test_helpers';
import { inCard, TestService } from 'helpers/card_test_helpers';
import createServiceForTesting from 'helpers/service_test_helpers';
import mockAjax from 'helpers/ajax_test_helpers';

import Conductor from 'conductor';

var conductor, card;

if (!/phantom/i.test(navigator.userAgent)) {
  module("Glazier PaneTypeUserStorageService Integration", {
    setup: function() {
      conductor = new Conductor({
        testing: true,
        conductorURL: '/vendor/conductor.js.html'
      });
      Conductor.services['paneTypeUserStorage'] = PaneTypeUserStorageService;
      Conductor.services['test'] = TestService;

      var cardUrl = '/test/fixtures/app/services/pane_type_user_storage_card.js';
      card = conductor.load(cardUrl, 1, {
        capabilities: ['paneTypeUserStorage', 'test']
      });
      card.manifest = {
        name: 'card-type'
      };
      card.promise.then(null, function(e){ console.log(e); });
      card.appendTo('#qunit-fixture');

      mockAjax();
    },
    teardown: function(){
      Ember.$.ajax.restore();
    }
  });

  asyncTest("A card can persist a key value pair via the service", 2, function() {
    inCard(card, function(card, resolver){
      var service = card.consumers.paneTypeUserStorage;
      service.request('setItem', 'foo', 'bar').then(function(){
        resolver.resolve('setItemCalled');
      }, function(){
        resolver.reject('service request setItem failed');
      });
    }).then(function(){
      var ajaxRequest = mockAjax.requests[0];
      ok(ajaxRequest, 'made an ajax request');
      equal(ajaxRequest && ajaxRequest.type, 'PUT', 'made a PUT request');
      start();
    }, function(reason){
      ok(false, reason);
      start();
    });
  });
}

// var port, card, sandbox;
module("Glazier PaneTypeUserStorageService Unit", {
  setup: function() {
    this.service = createServiceForTesting(PaneTypeUserStorageService, 'card-id', { name: 'card-type' });
    mockAjax();
  },
  teardown: function() {
    Ember.$.ajax.restore();
  }
});

asyncTest("setItem PUTs to /api/pane_type_user_entries/:pane_type_name.json", 4, function() {
  this.service.simulateRequest('setItem', "name", "stef").then(function() {
    var ajaxRequest = mockAjax.requests[0];
    ok(ajaxRequest, 'made an ajax request');
    equal(ajaxRequest.type, 'PUT', 'made a PUT request');
    equal(ajaxRequest.url, '/api/pane_type_user_entries/card-type.json', 'made a request to the correct endpoint');
    deepEqual(ajaxRequest.data, { data: {name: 'stef'}, access: 'private' }, 'has expected payload ' + JSON.stringify(ajaxRequest.data) );
    start();
  }, function(e) {
    ok(false, "failed");
    start();
  });
});

asyncTest("removeItem DELETEs to /api/pane_type_user_entries/:pane_type_name.json", 3, function() {
  this.service.simulateRequest('removeItem', "name").then(function() {
    var ajaxRequest = mockAjax.requests[0];
    equal(ajaxRequest.type, 'DELETE', 'made a DELETE request');
    equal(ajaxRequest.url, '/api/pane_type_user_entries/card-type.json', 'made a request to the correct endpoint');
    deepEqual(ajaxRequest.data, { key: 'name', access: 'private' }, 'has expected payload ' + JSON.stringify(ajaxRequest.data) );
    start();
  }, function() {
    ok(false, "failed");
    start();
  });
});
<commit_msg>inCard function no longer accepts a resolver.<commit_after>import PaneTypeUserStorageService from 'glazier/services/pane_type_user_storage';
import assertResolved from 'helpers/promise_test_helpers';
import { inCard, TestService } from 'helpers/card_test_helpers';
import createServiceForTesting from 'helpers/service_test_helpers';
import mockAjax from 'helpers/ajax_test_helpers';

import Conductor from 'conductor';

var conductor, card;

if (!/phantom/i.test(navigator.userAgent)) {
  module("Glazier PaneTypeUserStorageService Integration", {
    setup: function() {
      conductor = new Conductor({
        testing: true,
        conductorURL: '/vendor/conductor.js.html'
      });
      Conductor.services['paneTypeUserStorage'] = PaneTypeUserStorageService;
      Conductor.services['test'] = TestService;

      var cardUrl = '/test/fixtures/app/services/pane_type_user_storage_card.js';
      card = conductor.load(cardUrl, 1, {
        capabilities: ['paneTypeUserStorage', 'test']
      });
      card.manifest = {
        name: 'card-type'
      };
      card.promise.then(null, function(e){ console.log(e); });
      card.appendTo('#qunit-fixture');

      mockAjax();
    },
    teardown: function(){
      Ember.$.ajax.restore();
    }
  });

  asyncTest("A card can persist a key value pair via the service", 3, function() {
    inCard(card, function(card){
      var service = card.consumers.paneTypeUserStorage;
      return service.request('setItem', 'foo', 'bar').then(function(){
        ok(true, 'setItem called');
      }, function(){
        ok(!true, 'service request setItem failed');
      });
    }).then(function(){
      var ajaxRequest = mockAjax.requests[0];
      ok(ajaxRequest, 'made an ajax request');
      equal(ajaxRequest && ajaxRequest.type, 'PUT', 'made a PUT request');
      start();
    }, function(reason){
      ok(false, reason);
      start();
    });
  });
}

// var port, card, sandbox;
module("Glazier PaneTypeUserStorageService Unit", {
  setup: function() {
    this.service = createServiceForTesting(PaneTypeUserStorageService, 'card-id', { name: 'card-type' });
    mockAjax();
  },
  teardown: function() {
    Ember.$.ajax.restore();
  }
});

asyncTest("setItem PUTs to /api/pane_type_user_entries/:pane_type_name.json", 4, function() {
  this.service.simulateRequest('setItem', "name", "stef").then(function() {
    var ajaxRequest = mockAjax.requests[0];
    ok(ajaxRequest, 'made an ajax request');
    equal(ajaxRequest.type, 'PUT', 'made a PUT request');
    equal(ajaxRequest.url, '/api/pane_type_user_entries/card-type.json', 'made a request to the correct endpoint');
    deepEqual(ajaxRequest.data, { data: {name: 'stef'}, access: 'private' }, 'has expected payload ' + JSON.stringify(ajaxRequest.data) );
    start();
  }, function(e) {
    ok(false, "failed");
    start();
  });
});

asyncTest("removeItem DELETEs to /api/pane_type_user_entries/:pane_type_name.json", 3, function() {
  this.service.simulateRequest('removeItem', "name").then(function() {
    var ajaxRequest = mockAjax.requests[0];
    equal(ajaxRequest.type, 'DELETE', 'made a DELETE request');
    equal(ajaxRequest.url, '/api/pane_type_user_entries/card-type.json', 'made a request to the correct endpoint');
    deepEqual(ajaxRequest.data, { key: 'name', access: 'private' }, 'has expected payload ' + JSON.stringify(ajaxRequest.data) );
    start();
  }, function() {
    ok(false, "failed");
    start();
  });
});
<|endoftext|>


<commit_before>function showColorInfo(e) {
	var color = e.row.model.color;
	var collection = Alloy.createCollection('color');

	// fetch color info from the color collection based on the
	// given color from the user model
	collection.fetch({
		query: 'SELECT * FROM colors WHERE color = "' + color + '"'
	});
	var colorModel = collection.at(0);

	// open the color info window
	if (colorModel) {
		var win = Alloy.createController('color', colorModel).getView();
		OS_IOS ? Alloy.Globals.navgroup.open(win) : win.open();
	} else {
		alert('No color info found!');
	}
}

Alloy.Collections.user.fetch();<commit_msg>Show how to use a prepared statement with sql adapter in models/sql_queries test app<commit_after>function showColorInfo(e) {
	var color = e.row.model.color;
	var collection = Alloy.createCollection('color');

	// fetch() color info from the color collection based on the
	// given color from the user model. We can use a plain query string
	// like the commented property, or a prepared statement like the
	// one in use below.
	collection.fetch({
		// query: 'SELECT * FROM colors WHERE color = "' + color + '"'
		query: {
			statement: 'SELECT * FROM colors WHERE color = ?',
			params: [color]
		}
	});
	var colorModel = collection.at(0);

	// open the color info window
	if (colorModel) {
		var win = Alloy.createController('color', colorModel).getView();
		OS_IOS ? Alloy.Globals.navgroup.open(win) : win.open();
	} else {
		alert('No color info found!');
	}
}

Alloy.Collections.user.fetch();<|endoftext|>


<commit_before>'use strict';

var users = require('./../controllers/user.controller');

module.exports = function (router) {

  router.route('/signup')
    .post(users.signup);
};
<commit_msg>Define route for fetching users.<commit_after>'use strict';

var users = require('./../controllers/user.controller');

function ensureAuthorized (request, response, next) {
  var bearerToken;
  var bearerHeader = request.headers['authorization'];
  if (typeof bearerHeader !== 'undefined') {
    bearerToken = bearerHeader;
    request.token = bearerToken;
    next();
  } else {
    response
      .status(403)
      .json({
        type: false,
        data: 'Unauthorized.'
      });
  }
}

module.exports = function (router) {

  router.route('/signup')
    .post(users.signup);

  router.route('/login')
    .post(users.login);

  router
    .use('/api/v1/users', ensureAuthorized);

  router.route('/api/v1/users')
    .get(users.getAllUsers);
};
<|endoftext|>


<commit_before>'use strict';

var expect = require('chai').expect;

var FilterJSCompiler = require('../../../lib/compiler/filters/filter-js-compiler.js');
var JuttleMoment = require('../../../lib/moment').JuttleMoment;

// Needed to evaluate compiled Juttle code.
var juttle = require('../../../lib/runtime/runtime');   // eslint-disable-line

var POINTS_MISC = [
    { v: null                                         },
    { v: true                                         },
    { v: false                                        },
    { v: 5                                            },
    { v: Infinity                                     },
    { v: -Infinity                                    },
    { v: NaN                                          },
    { v: 'abcd'                                       },
    { v: new JuttleMoment('2015-01-01T00:00:00.000Z') },
    { v: JuttleMoment.duration('00:00:05.000')        }
];

var POINTS_NUMBERS = [
    { v: 1 },
    { v: 2 },
    { v: 3 }
];

var POINTS_STRINGS = [
    { v: 'abcd' },
    { v: 'efgh' },
    { v: 'ijkl' }
];

describe('FilterJSCompiler', function() {
    var fieldM = {
        type: 'UnaryExpression',
        operator: '*',
        expression: { type: 'StringLiteral', value: 'm' },
    };
    var fieldV = {
        type: 'UnaryExpression',
        operator: '*',
        expression: { type: 'StringLiteral', value: 'v' },
    };
    var literalNull = { type: 'NullLiteral' };
    var literalTrue = { type: 'BooleanLiteral', value: true };
    var literal1 = { type: 'NumericLiteral', value: 1 };
    var literal2 = { type: 'NumericLiteral', value: 2 };
    var literal3 = { type: 'NumericLiteral', value: 3 };
    var literal5 = { type: 'NumericLiteral', value: 5 };
    var literalInfinity = { type: 'InfinityLiteral', negative: false };
    var literalMinusInfinity = { type: 'InfinityLiteral', negative: true };
    var literalNaN = { type: 'NaNLiteral' };
    var literalAbcd = { type: 'StringLiteral', value: 'abcd' };
    var literalGlob = { type: 'StringLiteral', value: 'e*h' };
    var literalMoment = { type: 'MomentLiteral', value: '2015-01-01T00:00:00.000Z' };
    var literalDuration = { type: 'DurationLiteral', value: '00:00:05.000' };
    var literal13 = { type: 'ArrayLiteral', elements: [literal1, literal3] };
    var regExp = { type: 'RegularExpressionLiteral', value: 'e.*h', flags: '' };

    function testFilter(ast, points, expected) {
        /* jshint evil:true */

        var compiler = new FilterJSCompiler();
        var fn = eval(compiler.compile(ast));

        expect(points.filter(fn)).to.deep.equal(expected);
    }

    describe('literals', function() {
        it('handles null', function() {
            testFilter(
                {
                    type: 'ExpressionFilterTerm',
                    expression: {
                        type: 'BinaryExpression',
                        operator: '==',
                        left: fieldV,
                        right: literalNull
                    }
                },
                POINTS_MISC,
                [{ v: null }]
            );
        });

        it('handles booleans', function() {
            testFilter(
                {
                    type: 'ExpressionFilterTerm',
                    expression: {
                        type: 'BinaryExpression',
                        operator: '==',
                        left: fieldV,
                        right: literalTrue
                    }
                },
                POINTS_MISC,
                [{ v: true }]
            );
        });

        it('handles numbers', function() {
            testFilter(
                {
                    type: 'ExpressionFilterTerm',
                    expression: {
                        type: 'BinaryExpression',
                        operator: '==',
                        left: fieldV,
                        right: literal5
                    }
                },
                POINTS_MISC,
                [{ v: 5 }]
            );

            testFilter(
                {
                    type: 'ExpressionFilterTerm',
                    expression: {
                        type: 'BinaryExpression',
                        operator: '==',
                        left: fieldV,
                        right: literalInfinity
                    }
                },
                POINTS_MISC,
                [{ v: Infinity }]
            );

            testFilter(
                {
                    type: 'ExpressionFilterTerm',
                    expression: {
                        type: 'BinaryExpression',
                        operator: '==',
                        left: fieldV,
                        right: literalMinusInfinity
                    }
                },
                POINTS_MISC,
                [{ v: -Infinity }]
            );

            testFilter(
                {
                    type: 'ExpressionFilterTerm',
                    expression: {
                        type: 'BinaryExpression',
                        operator: '==',
                        left: fieldV,
                        right: literalNaN
                    }
                },
                POINTS_MISC,
                []   // no value equals NaN, not even NaN
            );
        });

        it('handles strings', function() {
            testFilter(
                {
                    type: 'ExpressionFilterTerm',
                    expression: {
                        type: 'BinaryExpression',
                        operator: '==',
                        left: fieldV,
                        right: literalAbcd
                    }
                },
                POINTS_MISC,
                [{ v: 'abcd' }]
            );
        });

        it('handles dates', function() {
            testFilter(
                {
                    type: 'ExpressionFilterTerm',
                    expression: {
                        type: 'BinaryExpression',
                        operator: '==',
                        left: fieldV,
                        right: literalMoment
                    }
                },
                POINTS_MISC,
                [{ v: new JuttleMoment('2015-01-01T00:00:00.000Z') }]
            );
        });

        it('handles durations', function() {
            testFilter(
                {
                    type: 'ExpressionFilterTerm',
                    expression: {
                        type: 'BinaryExpression',
                        operator: '==',
                        left: fieldV,
                        right: literalDuration
                    }
                },
                POINTS_MISC,
                [{ v: JuttleMoment.duration('00:00:05.000') }]
            );
        });
    });

    describe('filter expressions', function() {
        describe('NOT', function() {
            it('finds correct points', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'UnaryExpression',
                            operator: 'NOT',
                            expression: {
                                type: 'BinaryExpression',
                                operator: '==',
                                left: fieldV,
                                right: literal2
                            }
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 1 }, { v: 3 }]
                );
            });
        });

        describe('AND', function() {
            it('finds correct points', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: 'AND',
                            left: {
                                type: 'BinaryExpression',
                                operator: '<=',
                                left: fieldV,
                                right: literal2
                            },
                            right: {
                                type: 'BinaryExpression',
                                operator: '>=',
                                left: fieldV,
                                right: literal2
                            }
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 2 }]
                );
            });
        });

        describe('OR', function() {
            it('finds correct points', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: 'OR',
                            left: {
                                type: 'BinaryExpression',
                                operator: '<=',
                                left: fieldV,
                                right: literal2
                            },
                            right: {
                                type: 'BinaryExpression',
                                operator: '>=',
                                left: fieldV,
                                right: literal2
                            }
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 1 }, { v: 2 }, { v: 3 }]
                );
            });
        });
    });

    describe('simple terms', function() {
        describe('with filter literals', function() {
            it('finds correct points', function() {
                testFilter(
                    {
                        type: 'SimpleFilterTerm',
                        expression: {
                            type: 'FilterLiteral',
                            ast: {
                                type: 'ExpressionFilterTerm',
                                expression: {
                                    type: 'BinaryExpression',
                                    operator: '==',
                                    left: fieldV,
                                    right: literal2
                                }
                            },
                            text: 'v == 2'
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 2 }]
                );
            });
        });
    });

    describe('expression terms', function() {
        describe('missing fields', function() {
            it('treats them as null', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '==',
                            left: fieldM,
                            right: literalNull
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 1 }, { v: 2 }, { v: 3 }]
                );
            });
        });

        describe('==', function() {
            it('finds correct points (field == expression)', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '==',
                            left: fieldV,
                            right: literal2
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 2 }]
                );
            });

            it('finds correct points (expression == field)', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '==',
                            left: literal2,
                            right: fieldV
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 2 }]
                );
            });
        });

        describe('!=', function() {
            it('finds correct points (field != expression)', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '!=',
                            left: fieldV,
                            right: literal2
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 1 }, { v: 3 }]
                );
            });

            it('finds correct points (expression != field)', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '!=',
                            left: literal2,
                            right: fieldV
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 1 }, { v: 3 }]
                );
            });
        });

        describe('=~', function() {
            it('finds correct points (field =~ expression)', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '=~',
                            left: fieldV,
                            right: literalGlob
                        }
                    },
                    POINTS_STRINGS,
                    [{ v: 'efgh' }]
                );

                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '=~',
                            left: fieldV,
                            right: regExp
                        }
                    },
                    POINTS_STRINGS,
                    [{ v: 'efgh' }]
                );
            });
        });

        describe('!~', function() {
            it('finds correct points (field !~ expression)', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '!~',
                            left: fieldV,
                            right: literalGlob
                        }
                    },
                    POINTS_STRINGS,
                    [{ v: 'abcd' }, { v: 'ijkl' }]
                );

                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '!~',
                            left: fieldV,
                            right: regExp
                        }
                    },
                    POINTS_STRINGS,
                    [{ v: 'abcd' }, { v: 'ijkl' }]
                );
            });
        });

        describe('<', function() {
            it('finds correct points (field < expression)', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '<',
                            left: fieldV,
                            right: literal2
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 1 }]
                );
            });

            it('finds correct points (expression < field)', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '<',
                            left: literal2,
                            right: fieldV
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 3 }]
                );
            });
        });

        describe('>', function() {
            it('finds correct points (field > expression)', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '>',
                            left: fieldV,
                            right: literal2
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 3 }]
                );
            });

            it('finds correct points (expression > field)', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '>',
                            left: literal2,
                            right: fieldV
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 1 }]
                );
            });
        });

        describe('<=', function() {
            it('finds correct points (field <= expression)', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '<=',
                            left: fieldV,
                            right: literal2
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 1 }, { v: 2 }]
                );
            });

            it('finds correct points (expression <= field)', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '<=',
                            left: literal2,
                            right: fieldV
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 2 }, { v: 3 }]
                );
            });
        });

        describe('>=', function() {
            it('finds correct points (field >= expression)', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '>=',
                            left: fieldV,
                            right: literal2
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 2 }, { v: 3 }]
                );
            });

            it('finds correct points (expression >= field)', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: '>=',
                            left: literal2,
                            right: fieldV
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 1 }, { v: 2 }]
                );
            });
        });

        describe('in', function() {
            it('finds correct points (field in expression)', function() {
                testFilter(
                    {
                        type: 'ExpressionFilterTerm',
                        expression: {
                            type: 'BinaryExpression',
                            operator: 'in',
                            left: fieldV,
                            right: literal13
                        }
                    },
                    POINTS_NUMBERS,
                    [{ v: 1 }, { v: 3 }]
                );
            });
        });
    });
});
<commit_msg>filter-js-compiler.spec.js: Do not use ASTs<commit_after>'use strict';

var expect = require('chai').expect;

var FilterJSCompiler = require('../../../lib/compiler/filters/filter-js-compiler.js');
var JuttleMoment = require('../../../lib/moment').JuttleMoment;
var parser = require('../../../lib/parser');
var SemanticPass = require('../../../lib/compiler/semantic');

// Needed to evaluate compiled Juttle code.
var juttle = require('../../../lib/runtime/runtime');   // eslint-disable-line

var POINTS_MISC = [
    { v: null                                         },
    { v: true                                         },
    { v: false                                        },
    { v: 5                                            },
    { v: Infinity                                     },
    { v: NaN                                          },
    { v: 'abcd'                                       },
    { v: new JuttleMoment('2015-01-01T00:00:00.000Z') },
    { v: JuttleMoment.duration('00:00:05.000')        }
];

var POINTS_NUMBERS = [
    { v: 1 },
    { v: 2 },
    { v: 3 }
];

var POINTS_STRINGS = [
    { v: 'abcd' },
    { v: 'efgh' },
    { v: 'ijkl' }
];

describe('FilterJSCompiler', function() {
    function testFilter(filter, points, expected) {
        /* jshint evil:true */

        var ast = parser.parseFilter(filter).ast;

        // We need to run the semantic pass to convert Variable nodes to field
        // references.
        var semantic = new SemanticPass();
        ast = semantic.sa_expr(ast);

        var compiler = new FilterJSCompiler();
        var fn = eval(compiler.compile(ast));

        expect(points.filter(fn)).to.deep.equal(expected);
    }

    describe('literals', function() {
        it('handles null', function() {
            testFilter('v == null', POINTS_MISC, [{ v: null }]);
        });

        it('handles booleans', function() {
            testFilter('v == true', POINTS_MISC, [{ v: true }]);
        });

        it('handles numbers', function() {
            testFilter('v == 5', POINTS_MISC, [{ v: 5 }]);
            testFilter('v == Infinity', POINTS_MISC, [{ v: Infinity }]);
            // No value equals NaN, not even NaN.
            testFilter('v == NaN', POINTS_MISC, []);
        });

        it('handles strings', function() {
            testFilter( 'v == "abcd"', POINTS_MISC, [{ v: 'abcd' }]);
        });

        it('handles dates', function() {
            testFilter(
                'v == :2015-01-01T00:00:00.000Z:',
                POINTS_MISC,
                [{ v: new JuttleMoment('2015-01-01T00:00:00.000Z') }]
            );
        });

        it('handles durations', function() {
            testFilter(
                'v == :00:00:05.000:',
                POINTS_MISC,
                [{ v: JuttleMoment.duration('00:00:05.000') }]
            );
        });
    });

    describe('filter expressions', function() {
        describe('NOT', function() {
            it('finds correct points', function() {
                testFilter('NOT v == 2', POINTS_NUMBERS, [{ v: 1 }, { v: 3 }]);
            });
        });

        describe('AND', function() {
            it('finds correct points', function() {
                testFilter('v <= 2 AND v >= 2', POINTS_NUMBERS, [{ v: 2 }]);
            });
        });

        describe('OR', function() {
            it('finds correct points', function() {
                testFilter('v <= 2 OR v >= 2', POINTS_NUMBERS, [{ v: 1 }, { v: 2 }, { v: 3 }]);
            });
        });
    });

    describe('expression terms', function() {
        describe('missing fields', function() {
            it('treats them as null', function() {
                testFilter('m == null', POINTS_NUMBERS, [{ v: 1 }, { v: 2 }, { v: 3 }]);
            });
        });

        describe('==', function() {
            it('finds correct points (field == expression)', function() {
                testFilter('v == 2', POINTS_NUMBERS, [{ v: 2 }]);
            });

            it('finds correct points (expression == field)', function() {
                testFilter('v == 2', POINTS_NUMBERS, [{ v: 2 }]);
            });
        });

        describe('!=', function() {
            it('finds correct points (field != expression)', function() {
                testFilter('v != 2', POINTS_NUMBERS, [{ v: 1 }, { v: 3 }]);
            });

            it('finds correct points (expression != field)', function() {
                testFilter('2 != v', POINTS_NUMBERS, [{ v: 1 }, { v: 3 }]);
            });
        });

        describe('=~', function() {
            it('finds correct points (field =~ expression)', function() {
                testFilter('v =~ "e*h"', POINTS_STRINGS, [{ v: 'efgh' }]);
                testFilter('v =~ /e.*h/', POINTS_STRINGS, [{ v: 'efgh' }]);
            });
        });

        describe('!~', function() {
            it('finds correct points (field !~ expression)', function() {
                testFilter('v !~ "e*h"', POINTS_STRINGS, [{ v: 'abcd' }, { v: 'ijkl' }]);
                testFilter('v !~ /e.*h/', POINTS_STRINGS, [{ v: 'abcd' }, { v: 'ijkl' }]);
            });
        });

        describe('<', function() {
            it('finds correct points (field < expression)', function() {
                testFilter('v < 2', POINTS_NUMBERS, [{ v: 1 }]);
            });

            it('finds correct points (expression < field)', function() {
                testFilter('2 < v', POINTS_NUMBERS, [{ v: 3 }]);
            });
        });

        describe('>', function() {
            it('finds correct points (field > expression)', function() {
                testFilter('v > 2', POINTS_NUMBERS, [{ v: 3 }]);
            });

            it('finds correct points (expression > field)', function() {
                testFilter('2 > v', POINTS_NUMBERS, [{ v: 1 }]);
            });
        });

        describe('<=', function() {
            it('finds correct points (field <= expression)', function() {
                testFilter('v <= 2', POINTS_NUMBERS, [{ v: 1 }, { v: 2 }]);
            });

            it('finds correct points (expression <= field)', function() {
                testFilter('2 <= v', POINTS_NUMBERS, [{ v: 2 }, { v: 3 }]);
            });
        });

        describe('>=', function() {
            it('finds correct points (field >= expression)', function() {
                testFilter('v >= 2', POINTS_NUMBERS, [{ v: 2 }, { v: 3 }]
                );
            });

            it('finds correct points (expression >= field)', function() {
                testFilter('2 >= v', POINTS_NUMBERS, [{ v: 1 }, { v: 2 }]);
            });
        });

        describe('in', function() {
            it('finds correct points (field in expression)', function() {
                testFilter('v in [1, 3]', POINTS_NUMBERS, [{ v: 1 }, { v: 3 }]);
            });
        });
    });
});
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import Helmet from 'react-helmet'
import _ from 'lodash'

import { compose, bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { connectRequest } from 'redux-query'

import api from '../../../services'
import { Loading } from '../../../components'
import { manifestsByProposal, sortManifestsByProposal } from '../../../selectors'

import { Link } from 'react-router'
import { Table, Checkbox, Badge, message } from 'antd'

//  Status indicator mapping for badge components
const indicators = {
  'Submitted': 'default',
  'Funded': 'success',
  'Partially Funded': 'warning',
  'In Review': 'warning',
  'Awaiting Decision': 'warning',
  'Denied': 'error',
  'Draft': 'error',
  'Withdrawn': 'error'
}

/*
DOCKET PAGE:  .../docket/:year
Allows you to add budgets to /voting,
so the committee can review all relevant proposals
*/
import styles from './Docket.css'
@compose(
  connect(
    (state, props) => {
      const year = parseInt(props.params.year)
      return {
        manifests: manifestsByProposal(state)
          .filter(m => m.proposal && m.proposal.year === year),
        screen: state.screen
      }
    },
    dispatch => ({ api: bindActionCreators(api, dispatch) })
),
  connectRequest(props => api.get('manifests', {
    select: ['type', 'proposal', 'docket', 'decision'],
    populate: [
      { path: 'proposal', select: ['title', 'year', 'number', 'status'] },
      { path: 'decision', select: ['approved'] }
    ]
  }))
)
class Docket extends React.Component {
  static propTypes = {
    api: PropTypes.object,
    manifests: PropTypes.array,
    screen: PropTypes.object
  }
  constructor (props) {
    super(props)
    this.columns = [{
      title: 'ID',
      dataIndex: 'proposal.number',
      key: 'proposal.number',
      sorter: sortManifestsByProposal,
      render: (text, record) => {
        return text
          ? {`${record.proposal.year}-${record.proposal.number}`}
          : N/A
      },
      width: 90
    },
    {
      title: 'Title',
      dataIndex: 'proposal.title',
      key: 'proposal.title',
      // render: (text, record) => <Link to={`/proposals/${record.proposal.year}/${record.proposal.number}`}>{text}</Link>,
      render: (text = 'Untitled Proposal', record) => {
        return record.proposal
          ?
            <Link to={`/proposals/${record.proposal.year}/${record.proposal.number}`}>{text}</Link>

            <Badge status={indicators[record.proposal.status] || 'default'} text={record.proposal.status.toString()} />

          :
            {text}

            <Badge status='error' text='Unknown' />

      },
      filters: [
        { text: 'In Review', value: 'In Review' },
        { text: 'Funded', value: 'Funded' },
        { text: 'Partially Funded', value: 'Partially Funded' },
        { text: 'Denied', value: 'Denied' }
      ],
      onFilter: (value, record) => {
        const { proposal } = record
        return proposal && proposal.status.includes(value)
      }
    },
    {
      title: 'Type',
      dataIndex: 'type',
      key: 'type',
      // render: (text, record) => <Link to={`/proposals/${record.proposal.year}/${record.proposal.number}`}>{text}</Link>,
      render: (text, record) => {_.capitalize(text)},
      filters: [
        { text: 'Original Budget', value: 'original' },
        { text: 'Supplemental Request', value: 'supplemental' },
        { text: 'Partial / Alternates', value: 'partial' }
      ],
      onFilter: (value, record) => {
        const { type } = record
        return type.includes(value)
      },
      width: 120
    },
    {
      title: 'Docket',
      dataIndex: 'docket',
      key: 'docket',
      render: (text, record, index) => (
        //  Only original proposals have metrics taken (otherwise it's redundant)

          <Checkbox
            disabled={record.type !== 'original'}
            checked={text.metrics}
            onChange={e => this.handleToggle({ metrics: e.target.checked }, record, index)}
          >Metrics</Checkbox>
          <Checkbox
            checked={text.voting}
            onChange={e => this.handleToggle({ voting: e.target.checked }, record, index)}
          >Voting</Checkbox>
          <Checkbox
            checked={text.decisions}
            onChange={e => this.handleToggle({ decisions: e.target.checked }, record, index)}
          >Decisions</Checkbox>

      ),
      filters: [
        { text: 'Metrics', value: 'metrics' },
        { text: 'Voting', value: 'voting' },
        { text: 'Decision', value: 'decisions' }
      ],
      onFilter: (value, record) => record.docket[value],
      width: 120
    }]
  }
  handleToggle = (change, record) => {
    //  Change is an object that can be easily assigned, like { docket: true } or { voting: false }
    //  We do this so we can accept a key/bool in one arg and use it in a one-liner, so we can share this toggle func.
    const { api } = this.props
    let { _id: id, docket } = record
    let body = { docket: Object.assign(docket, change) }
    //  Update the manifest at the correct index.
    const params = {
      id,
      // select: ['type', 'proposal', 'docket', 'decision'],
      // populate: [
      //   { path: 'proposal', select: ['title', 'year', 'number', 'status'] },
      //   { path: 'decision', select: ['approved'] }
      // ],
      update: { manifests: (prev, next) => {
        let newData = prev.slice()
        let index = newData.findIndex(m => m._id === record._id)
        if (index >= 0) Object.assign(newData[index].docket, change)
        return newData
      }}
    }
    api.patch('manifest', body, params)
    .then(message.success('Docket updated!', 10))
    .catch(err => {
      message.warning(`Failed to update - client error`)
      console.warn(err)
    })
  }
  render (
    { columns } = this,
    { params, manifests, screen } = this.props
  ) {
    const { year } = params
    return (

        <Helmet title='Docket' />

Committee Docket

Internal use only.

          This page allows admins to control the availability of committee actions on proposals through the website. To make proposals available for metric submission or a committee vote, or to issue a final decision, use the switches below to update proposal status.

        <Loading render={Array.isArray(manifests) && manifests.length > 0}
          title='Docket'
          tip={`Loading ${year} Docket...`}
        >

 col.title !== 'Title') : columns}
            rowKey={record => record._id}
            pagination={false}
          />
        </Loading>

    )
  }
}

export default Docket
<commit_msg>Updated docket - includes percentage and subtitle<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import Helmet from 'react-helmet'
import _ from 'lodash'

import { compose, bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { connectRequest } from 'redux-query'

import api from '../../../services'
import { Loading } from '../../../components'
import { manifestsByProposal, sortManifestsByProposal } from '../../../selectors'

import { Link } from 'react-router'
import { Table, Checkbox, Badge, message } from 'antd'

//  At this scale, cents are triffling
const currency = number => `$${Number.parseInt(number).toLocaleString('en-US')}`

//  Status indicator mapping for badge components
const indicators = {
  'Submitted': 'default',
  'Funded': 'success',
  'Partially Funded': 'warning',
  'In Review': 'warning',
  'Awaiting Decision': 'warning',
  'Denied': 'error',
  'Draft': 'error',
  'Withdrawn': 'error'
}

/*
DOCKET PAGE:  .../docket/:year
Allows you to add budgets to /voting,
so the committee can review all relevant proposals
*/
import styles from './Docket.css'
@compose(
  connect(
    (state, props) => {
      const year = parseInt(props.params.year)
      return {
        manifests: manifestsByProposal(state)
          .filter(m => m.proposal && m.proposal.year === year),
        screen: state.screen
      }
    },
    dispatch => ({ api: bindActionCreators(api, dispatch) })
),
  connectRequest(props => api.get('manifests', {
    select: ['type', 'title', 'proposal', 'docket', 'decision', 'total'],
    populate: [
      { path: 'proposal', select: ['title', 'year', 'number', 'status', 'asked'] },
      { path: 'decision', select: ['approved'] }
    ]
  }))
)
class Docket extends React.Component {
  static propTypes = {
    api: PropTypes.object,
    manifests: PropTypes.array,
    screen: PropTypes.object
  }
  constructor (props) {
    super(props)
    this.columns = [{
      title: 'ID',
      dataIndex: 'proposal.number',
      key: 'proposal.number',
      sorter: sortManifestsByProposal,
      filters: [
        { text: 'In Review', value: 'In Review' },
        { text: 'Funded', value: 'Funded' },
        { text: 'Partially Funded', value: 'Partially Funded' },
        { text: 'Denied', value: 'Denied' }
      ],
      onFilter: (value, record) => {
        const { proposal } = record
        return proposal && proposal.status.includes(value)
      },
      render: (text, record) => (

          {text ? `${record.proposal.year}-${record.proposal.number}` : 'N/A'}
          <Badge status={indicators[record.proposal.status] || 'default'} text={record.proposal.status.toString()} />

      ),
      width: 120
    }, {
      title: 'Type',
      dataIndex: 'type',
      key: 'type',
      filters: [
        { text: 'Original Budget', value: 'original' },
        { text: 'Supplemental Request', value: 'supplemental' },
        { text: 'Partial / Alternates', value: 'partial' }
      ],
      onFilter: (value, record) => {
        const { type } = record
        return type.includes(value)
      },
      render: text => {_.capitalize(text)},
      width: 80
    }, {
      title: 'Title',
      dataIndex: 'proposal.title',
      key: 'proposal.title',
      render: (text = 'Untitled Proposal', record) => {
        return (

            <Link to={`/proposals/${record.proposal.year}/${record.proposal.number}`}>{text}</Link>
            {record.title &&

              {record.title}
            }

        )
      }
    }, {
      title: 'Asked',
      dataIndex: 'total',
      key: 'total',
      sorter: (a, b) => a.total - b.total,
      render: (text, record) => {
        let percentage = Number.parseInt((text / record.proposal.asked) * 100)
        if (Number.isNaN(percentage)) percentage = 0
        // console.log(text, record.proposal.asked, percentage)
        return (

            {currency(text || 0)}

            {percentage !== 100 && `${percentage}% of Original`}

        )
      },
      width: 120
    }, {
      title: 'Docket',
      dataIndex: 'docket',
      key: 'docket',
      render: (text, record, index) => (
        //  Only original proposals have metrics taken (otherwise it's redundant)

          <Checkbox
            disabled={record.type !== 'original'}
            checked={text.metrics}
            onChange={e => this.handleToggle({ metrics: e.target.checked }, record, index)}
          >Metrics</Checkbox>
          <Checkbox
            checked={text.voting}
            onChange={e => this.handleToggle({ voting: e.target.checked }, record, index)}
          >Voting</Checkbox>
          <Checkbox
            checked={text.decisions}
            onChange={e => this.handleToggle({ decisions: e.target.checked }, record, index)}
          >Decisions</Checkbox>

      ),
      filters: [
        { text: 'Metrics', value: 'metrics' },
        { text: 'Voting', value: 'voting' },
        { text: 'Decision', value: 'decisions' }
      ],
      onFilter: (value, record) => record.docket[value],
      width: 120
    }]
  }
  handleToggle = (change, record) => {
    //  Change is an object that can be easily assigned, like { docket: true } or { voting: false }
    //  We do this so we can accept a key/bool in one arg and use it in a one-liner, so we can share this toggle func.
    const { api } = this.props
    let { _id: id, docket } = record
    let body = { docket: Object.assign(docket, change) }
    //  Update the manifest at the correct index.
    const params = {
      id,
      // select: ['type', 'proposal', 'docket', 'decision'],
      // populate: [
      //   { path: 'proposal', select: ['title', 'year', 'number', 'status'] },
      //   { path: 'decision', select: ['approved'] }
      // ],
      update: { manifests: (prev, next) => {
        let newData = prev.slice()
        let index = newData.findIndex(m => m._id === record._id)
        if (index >= 0) Object.assign(newData[index].docket, change)
        return newData
      }}
    }
    api.patch('manifest', body, params)
    .then(message.success('Docket updated!', 10))
    .catch(err => {
      message.warning(`Failed to update - client error`)
      console.warn(err)
    })
  }
  render (
    { columns } = this,
    { params, manifests, screen } = this.props
  ) {
    const { year } = params
    return (

        <Helmet title='Docket' />

Committee Docket

Internal use only.

          This page allows admins to control the availability of committee actions on proposals through the website. To make proposals available for metric submission or a committee vote, or to issue a final decision, use the switches below to update proposal status.

        <Loading render={Array.isArray(manifests) && manifests.length > 0}
          title='Docket'
          tip={`Loading ${year} Docket...`}
        >

 col.title !== 'Title') : columns}
            rowKey={record => record._id}
            pagination={false}
          />
        </Loading>

    )
  }
}

export default Docket
<|endoftext|>