<commit_before>'use strict';

var BuilderMockV0 = require('./BuilderMockV0');;
var bitcore = require('bitcore');
var util = bitcore.util;
var Transaction = bitcore.Transaction;
var BuilderMockV0 = require('./BuilderMockV0');;
var TxProposal = require('./TxProposal');;
var Script = bitcore.Script;
var Key = bitcore.Key;
var buffertools = bitcore.buffertools;
var preconditions = require('preconditions').instance();

function TxProposals(opts) {
  opts = opts || {};
  this.walletId = opts.walletId;
  this.network = opts.networkName === 'livenet' ?
    bitcore.networks.livenet : bitcore.networks.testnet;
  this.txps = {};
}

// fromObj => from a trusted source
TxProposals.fromObj = function(o, forceOpts) {
  var ret = new TxProposals({
    networkName: o.networkName,
    walletId: o.walletId,
  });

  o.txps.forEach(function(o2) {
    var t = TxProposal.fromObj(o2, forceOpts);
    if (t.builder) {
      var id = t.getId();
      ret.txps[id] = t;
    }
  });
  return ret;
};

TxProposals.prototype.length = function() {
  return Object.keys(this.txps).length;
};

TxProposals.prototype.getNtxidsSince = function(sinceTs) {
  preconditions.checkArgument(sinceTs);
  var ret = [];

  for(var ii in this.txps){
    var txp = this.txps[ii];
    if (txp.createdTs >=  sinceTs)
      ret.push(ii);
  }
  return ret;
};

TxProposals.prototype.getNtxids = function() {
  return Object.keys(this.txps);
};

TxProposals.prototype.deleteOne = function(ntxid) {
  preconditions.checkState(this.txps[ntxid], 'Unknown TXP: ' + ntxid);
  delete this.txps[ntxid];
};

TxProposals.prototype.deleteAll = function() {
  this.txps = {};
};

TxProposals.prototype.deletePending = function(maxRejectCount) {
  for (var ntxid in this.txps) {
    if (this.txps[ntxid].isPending(maxRejectCount))
      delete this.txps[ntxid];
  };
};

TxProposals.prototype.toObj = function() {
  var ret = [];
  for (var id in this.txps) {
    var t = this.txps[id];
    if (!t.sent)
      ret.push(t.toObj());
  }
  return {
    txps: ret,
    walletId: this.walletId,
    networkName: this.network.name,
  };
};

TxProposals.prototype.merge = function(inObj, builderOpts) {
  var incomingTx = TxProposal.fromUntrustedObj(inObj, builderOpts);
  incomingTx._sync();

  var myTxps = this.txps;
  var ntxid = incomingTx.getId();
  var ret = {
    ntxid: ntxid
  };

  if (myTxps[ntxid]) {

    // Merge an existing txProposal
    ret.hasChanged = myTxps[ntxid].merge(incomingTx);

  } else {
    // Create a new one
    ret.new = ret.hasChanged =  1;
    this.txps[ntxid] = incomingTx;
  }

  ret.txp = this.txps[ntxid];
  return ret;
};

// Add a LOCALLY CREATED (trusted) tx proposal
TxProposals.prototype.add = function(txp) {
  txp._sync();
  var ntxid = txp.getId();
  this.txps[ntxid] = txp;
  return ntxid;
};

TxProposals.prototype.get = function(ntxid) {
  var ret = this.txps[ntxid];
  if (!ret)
    throw new Error('Unknown TXP: '+ntxid);

  return ret;
};

TxProposals.prototype.getTxProposal = function(ntxid, copayers) {
  var txp = this.get(ntxid);

  var i = JSON.parse(JSON.stringify(txp));
  i.builder = txp.builder;
  i.ntxid = ntxid;
  i.peerActions = {};

  if (copayers) {
    for (var j = 0; j < copayers.length; j++) {
      var p = copayers[j];
      i.peerActions[p] = {};
    }
  }

  for (var p in txp.seenBy) {
    i.peerActions[p] = {
      seen: txp.seenBy[p]
    };
  }
  for (var p in txp.signedBy) {
    i.peerActions[p] = i.peerActions[p] || {};
    i.peerActions[p].sign = txp.signedBy[p];
  }
  var r = 0;
  for (var p in txp.rejectedBy) {
    i.peerActions[p] = i.peerActions[p] || {};
    i.peerActions[p].rejected = txp.rejectedBy[p];
    r++;
  }
  i.rejectCount = r;

  var c = txp.creator;
  i.peerActions[c] = i.peerActions[c] || {};
  i.peerActions[c].create = txp.createdTs;
  return i;
};

TxProposals.prototype.reject = function(ntxid, copayerId) {
  var txp = this.get(ntxid);
  txp.setRejected(copayerId);
};

TxProposals.prototype.seen = function(ntxid, copayerId) {
  var txp = this.get(ntxid);
  txp.setSeen(copayerId);
};

//returns the unspent txid-vout used in PENDING Txs
TxProposals.prototype.getUsedUnspent = function(maxRejectCount) {
  var ret = {};
  for (var i in this.txps) {
    if (!this.txps[i].isPending(maxRejectCount))
      continue;

    var u = this.txps[i].builder.getSelectedUnspent();
    var p = this.getTxProposal(i);

    for (var j in u) {
      ret[u[j].txid + ',' + u[j].vout] = 1;
    }
  }
  return ret;
};

module.exports = TxProposals;
<commit_msg>ignore corrupted TX proposals when imported a backup<commit_after>'use strict';

var BuilderMockV0 = require('./BuilderMockV0');;
var bitcore = require('bitcore');
var util = bitcore.util;
var Transaction = bitcore.Transaction;
var BuilderMockV0 = require('./BuilderMockV0');;
var TxProposal = require('./TxProposal');;
var Script = bitcore.Script;
var Key = bitcore.Key;
var buffertools = bitcore.buffertools;
var preconditions = require('preconditions').instance();
var log = require('../log');

function TxProposals(opts) {
  opts = opts || {};
  this.walletId = opts.walletId;
  this.network = opts.networkName === 'livenet' ?
    bitcore.networks.livenet : bitcore.networks.testnet;
  this.txps = {};
}

// fromObj => from a trusted source
TxProposals.fromObj = function(o, forceOpts) {
  var ret = new TxProposals({
    networkName: o.networkName,
    walletId: o.walletId,
  });

  o.txps.forEach(function(o2) {
    try {
      var t = TxProposal.fromObj(o2, forceOpts);
    } catch (e) {
      log.info('Ignoring corrupted TxProposal:', o2, e);
    }
    if (t && t.builder) {
      var id = t.getId();
      ret.txps[id] = t;
    }

  });
  return ret;
};

TxProposals.prototype.length = function() {
  return Object.keys(this.txps).length;
};

TxProposals.prototype.getNtxidsSince = function(sinceTs) {
  preconditions.checkArgument(sinceTs);
  var ret = [];

  for(var ii in this.txps){
    var txp = this.txps[ii];
    if (txp.createdTs >=  sinceTs)
      ret.push(ii);
  }
  return ret;
};

TxProposals.prototype.getNtxids = function() {
  return Object.keys(this.txps);
};

TxProposals.prototype.deleteOne = function(ntxid) {
  preconditions.checkState(this.txps[ntxid], 'Unknown TXP: ' + ntxid);
  delete this.txps[ntxid];
};

TxProposals.prototype.deleteAll = function() {
  this.txps = {};
};

TxProposals.prototype.deletePending = function(maxRejectCount) {
  for (var ntxid in this.txps) {
    if (this.txps[ntxid].isPending(maxRejectCount))
      delete this.txps[ntxid];
  };
};

TxProposals.prototype.toObj = function() {
  var ret = [];
  for (var id in this.txps) {
    var t = this.txps[id];
    if (!t.sent)
      ret.push(t.toObj());
  }
  return {
    txps: ret,
    walletId: this.walletId,
    networkName: this.network.name,
  };
};

TxProposals.prototype.merge = function(inObj, builderOpts) {
  var incomingTx = TxProposal.fromUntrustedObj(inObj, builderOpts);
  incomingTx._sync();

  var myTxps = this.txps;
  var ntxid = incomingTx.getId();
  var ret = {
    ntxid: ntxid
  };

  if (myTxps[ntxid]) {

    // Merge an existing txProposal
    ret.hasChanged = myTxps[ntxid].merge(incomingTx);

  } else {
    // Create a new one
    ret.new = ret.hasChanged =  1;
    this.txps[ntxid] = incomingTx;
  }

  ret.txp = this.txps[ntxid];
  return ret;
};

// Add a LOCALLY CREATED (trusted) tx proposal
TxProposals.prototype.add = function(txp) {
  txp._sync();
  var ntxid = txp.getId();
  this.txps[ntxid] = txp;
  return ntxid;
};

TxProposals.prototype.get = function(ntxid) {
  var ret = this.txps[ntxid];
  if (!ret)
    throw new Error('Unknown TXP: '+ntxid);

  return ret;
};

TxProposals.prototype.getTxProposal = function(ntxid, copayers) {
  var txp = this.get(ntxid);

  var i = JSON.parse(JSON.stringify(txp));
  i.builder = txp.builder;
  i.ntxid = ntxid;
  i.peerActions = {};

  if (copayers) {
    for (var j = 0; j < copayers.length; j++) {
      var p = copayers[j];
      i.peerActions[p] = {};
    }
  }

  for (var p in txp.seenBy) {
    i.peerActions[p] = {
      seen: txp.seenBy[p]
    };
  }
  for (var p in txp.signedBy) {
    i.peerActions[p] = i.peerActions[p] || {};
    i.peerActions[p].sign = txp.signedBy[p];
  }
  var r = 0;
  for (var p in txp.rejectedBy) {
    i.peerActions[p] = i.peerActions[p] || {};
    i.peerActions[p].rejected = txp.rejectedBy[p];
    r++;
  }
  i.rejectCount = r;

  var c = txp.creator;
  i.peerActions[c] = i.peerActions[c] || {};
  i.peerActions[c].create = txp.createdTs;
  return i;
};

TxProposals.prototype.reject = function(ntxid, copayerId) {
  var txp = this.get(ntxid);
  txp.setRejected(copayerId);
};

TxProposals.prototype.seen = function(ntxid, copayerId) {
  var txp = this.get(ntxid);
  txp.setSeen(copayerId);
};

//returns the unspent txid-vout used in PENDING Txs
TxProposals.prototype.getUsedUnspent = function(maxRejectCount) {
  var ret = {};
  for (var i in this.txps) {
    if (!this.txps[i].isPending(maxRejectCount))
      continue;

    var u = this.txps[i].builder.getSelectedUnspent();
    var p = this.getTxProposal(i);

    for (var j in u) {
      ret[u[j].txid + ',' + u[j].vout] = 1;
    }
  }
  return ret;
};

module.exports = TxProposals;
<|endoftext|>


<commit_before>
/*global define */
define(["jquery", "underscore", "backbone", "../collections/edge-collection", "../collections/node-collection", "../models/node-model", "../models/edge-model"], function($, _, Backbone, BaseEdgeCollection, BaseNodeCollection){
  var pvt = {};

  pvt.alwaysTrue = function(){return true;};

  pvt.setEdgeId = function (edge) {
    var src,
        tar,
        needsServerId;

    if (edge.set) {
      // edge is a backbone model
      src = edge.get("source");
      tar = edge.get("target");
      edge.set("id", String(src.id) + String(tar.id));
      edge.set("needsServerId",!src.hasServerId || !tar.hasServerId);
    } else {
      src = edge.source;
      tar = edge.target;
      edge.id = String(src.id) + String(tar.id);
      edge.needsServerId = !src.hasServerId || !tar.hasServerId;
    }
  };

  return Backbone.Model.extend({
    defaults: function(){
      return {
        leafs: [], // TODO perhaps this should be stored in the nodes
        edges: new BaseEdgeCollection(),
        nodes: new BaseNodeCollection()
      };
    },

    initialize: function(){
      var thisModel = this;
      thisModel.edgeModel = thisModel.edgeModel || thisModel.get("edges").model;
      thisModel.nodeModel = thisModel.nodeModel || thisModel.get("nodes").model;
      thisModel.get("nodes").on("setFocusNode", function(id){
        thisModel.trigger("setFocusNode", id);
      });
      thisModel.get("nodes").on("toggleNodeScope", function(id){
        thisModel.trigger("toggleNodeScope", id);
      });

      thisModel.postinitialize();
    },

    // override in subclass
    postinitialize: function(){},

    /**
     * Export this graph to a simple json representation
     *
     * @return {json object}: simple json object representation of this graph
     *   that can be directly converted into a string
     */
    toJSON: function() {
      // returning nodes AND edges is redundant, since nodes contain dep info
      return {
        id: this.get("id"),
        title: this.get("title"),
        concepts: this.get("nodes").toJSON()
      };
    },

    /**
     * Make/extend this graph from a json object, but terminating at leaf node with id leafId
     */
    addJsonSubGraphToGraph: function (jsonNodeArr, leafId) {
      // create an id to node obj for DFS:
      var thisGraph = this,
          idObj = {},
          i;

      i = jsonNodeArr.length;
      while( i -- ){
        idObj[jsonNodeArr[i].id] = jsonNodeArr[i];
      }

      if (!idObj.hasOwnProperty(leafId)) {
        throw new Error("input node array does not have node with id: " + leafId);
      }

      var subgraph = [],
          visitedNodeIds = {},
          curDepSrc;

      // recursively add the nodes to the graph
      dfsVisitNodes(idObj[leafId]);

      function dfsVisitNodes (node) {
        var depLen= node.dependencies.length;
        subgraph.push(node);
        visitedNodeIds[node.id] = 1;
        while( depLen -- ){
          curDepSrc = node.dependencies[depLen].source;
          if (!visitedNodeIds.hasOwnProperty(curDepSrc)) {
            dfsVisitNodes(idObj[curDepSrc]);
          }
        }
      }

      // add the subgraph to the graph
      thisGraph.addJsonNodesToGraph(subgraph);
    },

    /**
     * Make/extend this graph from a json obj
     *
     * @param {json object} jsonObj: json string with attribute:
     *   nodes: array of objects: each contains at least node title and id (optional coordinates) and dependencies
     * @return {backbone model} this model altered by the jsonObj (allows chaining)
     */
    addJsonNodesToGraph: function(jsonNodeArr) {
      var thisGraph = this,
          tmpEdges = [];

      jsonNodeArr.forEach(function(node) {
        node.dependencies.forEach(function(dep) {
          var tEdge = {source: dep.source, target: node.id, isContracted: dep.isContracted};
          if (dep.reason) tEdge.reason = dep.reason;
          if (dep.middlePts) tEdge.middlePts = dep.middlePts;
          if (dep.id) tEdge.id = dep.id;
          if (dep.id) tEdge.id = dep.id;

          tmpEdges.push(tEdge);
        });
        node.dependencies = undefined;
        thisGraph.addNode(node);
      });

      // add edges
      tmpEdges.forEach(function(edge){
        thisGraph.addEdge.call(thisGraph, edge);
      });

      return this;
    },

    /**
     * @return <boolean> true if the graph is populated
     */
    isPopulated: function(){
      return this.getEdges().length > 0 || this.getNodes().length > 0;
    },

    /**
     * @param stNode: the start node
     * @param endNode: the end node
     * @param checkFun: an optional function that dermines whether an edge
     * is valid (e.g. an "is visible" function --defaults to a tautological function)
     * @return <boolean> - true if there is a directed path from stNode to endNode (follows outlinks from stNode)
     */
    isPathBetweenNodes: function (stNode, endNode, checkFun) {
      var thisGraph = this,
          outlinks = stNode.get("outlinks");
      checkFun = checkFun || pvt.alwaysTrue;
      // DFS recursive search
      return outlinks.length > 0
        && outlinks.any(function(ol){
          var olTar = ol.get("target");
          return checkFun(ol) && (olTar.id === endNode.id || thisGraph.isPathBetweenNodes(olTar, endNode, checkFun));
        });
    },

    /**
     * Checks if an edge is transitive by performing a DFS on it's source
     *
     * @param edge: the edge to be checked
     * @param checkFun: a function that returns a boolean value indiciating
     * whether a given edge should be counted in the traversal
     * (e.g. an "is visible" function --defaults to a tautological function)
     * @return <boolean> true if the edge is transitive
     */
    checkIfTransitive: function(edge, checkFun){
      var thisGraph = this,
          edgeSource = edge.get("source"),
          edgeTarget = edge.get("target");
      checkFun = checkFun || pvt.alwaysTrue;
      return edgeSource.get("outlinks").any(
        function(ol){
          return edge.id !== ol.id && thisGraph.isPathBetweenNodes(ol.get("target"), edgeTarget, checkFun);
        });
    },

    /**
     * Get a nodes from the graph
     *
     * @return {node collection} the node collection of the model
     */
    getNodes: function() {
      return this.get("nodes");
    },

    /**
     * Get a edges from the graph
     *
     * @return {edge collection} the edge collection of the model
     */
    getEdges: function() {
      return this.get("edges");
    },

    /**
     * Get a node from the graph with the given id
     *
     * @param {node id} nodeId: the node id of the desired node
     * @return {node} the desired node object or undefined if not present
     */
    getNode: function(nodeId) {
      return this.get("nodes").get(nodeId);
    },

    /**
     * Get an edge from the graph with the given id
     *
     * @param {edge id} edgeId: the edge id of the desired edge
     * @return {edge} the desired edge object or undefined if not present
     */
    getEdge: function(edgeId) {
      return this.get("edges").get(edgeId);
    },

    /**
     * Add an edge to the graph: adds the edge to the edge collection and
     * outlinks/dependencies properties in the appropriate nodes
     *
     * @param {edge object} edge: the edge to be added to the model
     */
    addEdge: function(edge) {
      var thisGraph = this,
          // check if source/target are ids and switch to nodes if necessary
          source =  typeof edge.source === "string"  ? this.getNode(edge.source) : edge.source,
          target = typeof edge.target === "string" ? this.getNode(edge.target) :  edge.target;

      if (!source  || !target) {
        throw new Error("source or target was not given correctly for input or does not exist in graph: " + edge.source + " -> " + edge.target );
      }

      edge.source = source;
      edge.target = target;

      if (edge.source.id === edge.target.id){
        console.log("warning: loop edge detected and not added to graph, node: " + edge.source.id);
        return;
      }

      if (edge.id === undefined) {
        pvt.setEdgeId(edge);
      }

      // check if this edge is transitive
      // (will be transitive if there is already a path from the edge source to the edge target)
      edge.isTransitive = thisGraph.isPathBetweenNodes(edge.source, edge.target);

      //
      var edges = thisGraph.getEdges();
      edges.add(edge);
      var mEdge = edges.get(edge.id);
      edge.source.get("outlinks").add(mEdge);
      edge.target.get("dependencies").add(mEdge);

      // check if the new edge changes transitivity of other edges
      if (!edge.isTransitive) {
        thisGraph.getEdges().filter(function(e){return !e.get("isTransitive");}).forEach(function(notTransEdge){
          var isTrans = thisGraph.checkIfTransitive(notTransEdge);
          if (isTrans) {
            notTransEdge.set("isTransitive", true);
          }
        });
      }
    },

    /**
     * Add a node to the graph
     * Note:
     * new nodes that should be synced with a server
     * should not have an id specified and should have a defined "url"
     * in the node backbone model or they should have the property
     * "syncWithServer" set to a non-falsey value
     *
     * @param {node object} node: the node to be added to the model
     */
    addNode: function(node) {
      var thisGraph = this,
          isNewNode = false;
      if (!(node.set )){
        var nodeId = node.id;
        if (nodeId === undefined || nodeId === null || nodeId === "") {
          isNewNode = true;
          node.id = "-new-" + Math.random().toString(36).substring(3);
        }
        node = new thisGraph.nodeModel(node, {parse: true});
      }
      thisGraph.getNodes().add(node);

//!!!!      PROBLEM: nodeModel is defined in init (perhaps check if its defined here)

      if (node.url && (isNewNode || node.get("syncWithServer"))) {
        node.hasServerId = false;

        node.save(["id", "tag"], {
          parse  : false, // NB must check for parse == false in model's parse method
          success: function (model, xhr) {
            node.set("id", xhr.id);
            node.set("tag", xhr.tag);
            node.hasServerId = true;

            // set edge ids that were waiting for the server
            thisGraph.getEdges().filter(function (edge) {
              return edge.get("needsServerId");
            }).forEach(pvt.setEdgeId);
          },
          error: function (model, xhr) {
            console.log("error response text:" + xhr.responseText);
          }
        });
      } else {
        node.hasServerId = true;
      }
    },

    /**
     * Removes an edge from the graph: removes the edge from the edge collection
     * and appropriate outlinks/dependencies properties in the appropriate nodes
     *
     * @param {edge-id or edge object} edge: the edge id or edge object
     */
    removeEdge: function(edge) {
      var thisGraph = this,
          edges = thisGraph.get("edges");

      edge = edge instanceof thisGraph.edgeModel ? edge : edges.get(edge);
      edge.get("source").get("outlinks").remove(edge);
      edge.get("target").get("dependencies").remove(edge);
      edges.remove(edge);

      // possibly change transitivity relationships of transitive edges
      edges.filter(function(e){return e.get("isTransitive");}).forEach(function(transEdge){
        var isTrans = thisGraph.checkIfTransitive(transEdge);
        if (!isTrans){
          transEdge.set("isTransitive", false);
        }
      });
    },

    /**
     * Removes the node from the graph and all edges that were in its dependencies/outlinks attributes
     *
     * @param {node id or node object} node: the node id or node object to be removed
     */
    removeNode: function(node){
      var thisGraph = this,
          nodes = this.get("nodes");
      node =  node instanceof thisGraph.nodeModel ? node : nodes.get(node);
      node.get("dependencies").pluck("id").forEach(function(edgeId){ thisGraph.removeEdge(edgeId);});
      node.get("outlinks").pluck("id").forEach(function(edgeId){ thisGraph.removeEdge(edgeId);});
      nodes.remove(node);
    },

    /**
     * Compute the learning view ordering (topological sort)
     * TODO write tests for getTopoSort FIXME
     */
    getTopoSort: function(){
      var thisGraph = this;
      if (!thisGraph.topoSort || !thisGraph.topoSort.length){
        thisGraph.doTopoSort();
      }
      return thisGraph.topoSort;
    },

    /**
     * Perform a depth first topological sort of the graph
     *
     * @return {list} - sorted ids of the nodes in the graph
     */
    doTopoSort: function () {
      // TODO cache the sort
      var thisGraph = this,
          nodes = thisGraph.getNodes(),
          traversedNodes = {}, // keep track of traversed nodes
          startLeafNodes;

      // init: obtain node tags with 0 outlinks (root nodes)
      startLeafNodes = _.map(nodes.filter(function(mdl){
        return mdl.get("outlinks").length == 0;
      }), function(itm){
        return itm.get("id");
      });

      thisGraph.topoSort = dfsTopSort(startLeafNodes);

      // recursive dfs topological sort
      // TODO this should be defined in pvt?
      function dfsTopSort (leafNodeTags){
        var curLeafNodeTagDepth,
            returnArr = [],
            curLeafNodeTag,
            directDeps,
            curNode;

        // recurse on the input leaf node tags
        // -- use edge weight to do the ordering if available
        for(curLeafNodeTagDepth = 0; curLeafNodeTagDepth < leafNodeTags.length; curLeafNodeTagDepth++){
          curLeafNodeTag = leafNodeTags[curLeafNodeTagDepth];
          curNode = nodes.get(curLeafNodeTag);

          if (!traversedNodes.hasOwnProperty(curLeafNodeTag)) {
            directDeps = curNode.getDirectDeps();
            traversedNodes[curLeafNodeTag] = 1;
            if (directDeps.length > 0){
              returnArr = returnArr.concat(dfsTopSort(directDeps.map(function (dep) {
                return dep.get("source").id;
              })));
            }
            returnArr.push(curLeafNodeTag);

          }
        }
        return returnArr;
      };
    },

    // /**
    //  * Contract all nodes that are not part of the dep graph for the input node id
    //  */
    // showSubgraphFromNodeId: function (nodeId) {
    //   var thisGraph = this;
    //   thisGraph.expandGraph();
    //   var visNodes = {};
    //   var traceDepGraph = function traceDepGraph(node) {
    //     visNodes[node.id] = 1;
    //     node.get("dependencies").each(function (dep) {
    //       var nextNode = dep.get("source");
    //       if (!visNodes.hasOwnProperty(nextNode.id)) {
    //         traceDepGraph(nextNode);
    //       }
    //     });
    //   };

    //   traceDepGraph(thisGraph.getNode(nodeId));

    //   thisGraph.getNodes().each(function (node) {
    //     if (!visNodes.hasOwnProperty(node.id)) {
    //       node.set("isContracted", true);
    //     }
    //   });
    // },

    expandGraph: function () {
      var thisGraph = this;
      thisGraph.getEdges().each(function (edge) {
        edge.set("isContracted", false);
      });

      thisGraph.getNodes().each(function (node) {
        node.set("isContracted", false);
      });
    },

    /**
     * Check if the input edge is present in the topological sort
     * (sheel function - not currently working)
     */
    isEdgeInTopoSort: function(edge){
      var thisGraph = this;
      // make sure we've done a topological sort
      if (!thisGraph.topoSort){
        thisGraph.doTopoSort();
      }
      return edge.isTopoEdge;
    }
  });
});
<commit_msg>small comment fix<commit_after>
/*global define */
define(["jquery", "underscore", "backbone", "../collections/edge-collection", "../collections/node-collection", "../models/node-model", "../models/edge-model"], function($, _, Backbone, BaseEdgeCollection, BaseNodeCollection){
  var pvt = {};

  pvt.alwaysTrue = function(){return true;};

  pvt.setEdgeId = function (edge) {
    var src,
        tar,
        needsServerId;

    if (edge.set) {
      // edge is a backbone model
      src = edge.get("source");
      tar = edge.get("target");
      edge.set("id", String(src.id) + String(tar.id));
      edge.set("needsServerId",!src.hasServerId || !tar.hasServerId);
    } else {
      src = edge.source;
      tar = edge.target;
      edge.id = String(src.id) + String(tar.id);
      edge.needsServerId = !src.hasServerId || !tar.hasServerId;
    }
  };

  return Backbone.Model.extend({
    defaults: function(){
      return {
        leafs: [], // TODO perhaps this should be stored in the nodes
        edges: new BaseEdgeCollection(),
        nodes: new BaseNodeCollection()
      };
    },

    initialize: function(){
      var thisModel = this;
      thisModel.edgeModel = thisModel.edgeModel || thisModel.get("edges").model;
      thisModel.nodeModel = thisModel.nodeModel || thisModel.get("nodes").model;
      thisModel.get("nodes").on("setFocusNode", function(id){
        thisModel.trigger("setFocusNode", id);
      });
      thisModel.get("nodes").on("toggleNodeScope", function(id){
        thisModel.trigger("toggleNodeScope", id);
      });

      thisModel.postinitialize();
    },

    // override in subclass
    postinitialize: function(){},

    /**
     * Export this graph to a simple json representation
     *
     * @return {json object}: simple json object representation of this graph
     *   that can be directly converted into a string
     */
    toJSON: function() {
      // returning nodes AND edges is redundant, since nodes contain dep info
      return {
        id: this.get("id"),
        title: this.get("title"),
        concepts: this.get("nodes").toJSON()
      };
    },

    /**
     * Make/extend this graph from a json object, but terminating at leaf node with id leafId
     */
    addJsonSubGraphToGraph: function (jsonNodeArr, leafId) {
      // create an id to node obj for DFS:
      var thisGraph = this,
          idObj = {},
          i;

      i = jsonNodeArr.length;
      while( i -- ){
        idObj[jsonNodeArr[i].id] = jsonNodeArr[i];
      }

      if (!idObj.hasOwnProperty(leafId)) {
        throw new Error("input node array does not have node with id: " + leafId);
      }

      var subgraph = [],
          visitedNodeIds = {},
          curDepSrc;

      // recursively add the nodes to the graph
      dfsVisitNodes(idObj[leafId]);

      function dfsVisitNodes (node) {
        var depLen= node.dependencies.length;
        subgraph.push(node);
        visitedNodeIds[node.id] = 1;
        while( depLen -- ){
          curDepSrc = node.dependencies[depLen].source;
          if (!visitedNodeIds.hasOwnProperty(curDepSrc)) {
            dfsVisitNodes(idObj[curDepSrc]);
          }
        }
      }

      // add the subgraph to the graph
      thisGraph.addJsonNodesToGraph(subgraph);
    },

    /**
     * Make/extend this graph from a json obj
     *
     * @param {json object} jsonObj: json string with attribute:
     *   nodes: array of objects: each contains at least node title and id (optional coordinates) and dependencies
     * @return {backbone model} this model altered by the jsonObj (allows chaining)
     */
    addJsonNodesToGraph: function(jsonNodeArr) {
      var thisGraph = this,
          tmpEdges = [];

      jsonNodeArr.forEach(function(node) {
        node.dependencies.forEach(function(dep) {
          var tEdge = {source: dep.source, target: node.id, isContracted: dep.isContracted};
          if (dep.reason) tEdge.reason = dep.reason;
          if (dep.middlePts) tEdge.middlePts = dep.middlePts;
          if (dep.id) tEdge.id = dep.id;
          if (dep.id) tEdge.id = dep.id;

          tmpEdges.push(tEdge);
        });
        node.dependencies = undefined;
        thisGraph.addNode(node);
      });

      // add edges
      tmpEdges.forEach(function(edge){
        thisGraph.addEdge.call(thisGraph, edge);
      });

      return this;
    },

    /**
     * @return <boolean> true if the graph is populated
     */
    isPopulated: function(){
      return this.getEdges().length > 0 || this.getNodes().length > 0;
    },

    /**
     * @param stNode: the start node
     * @param endNode: the end node
     * @param checkFun: an optional function that dermines whether an edge
     * is valid (e.g. an "is visible" function --defaults to a tautological function)
     * @return <boolean> - true if there is a directed path from stNode to endNode (follows outlinks from stNode)
     */
    isPathBetweenNodes: function (stNode, endNode, checkFun) {
      var thisGraph = this,
          outlinks = stNode.get("outlinks");
      checkFun = checkFun || pvt.alwaysTrue;
      // DFS recursive search
      return outlinks.length > 0
        && outlinks.any(function(ol){
          var olTar = ol.get("target");
          return checkFun(ol) && (olTar.id === endNode.id || thisGraph.isPathBetweenNodes(olTar, endNode, checkFun));
        });
    },

    /**
     * Checks if an edge is transitive by performing a DFS on it's source
     *
     * @param edge: the edge to be checked
     * @param checkFun: a function that returns a boolean value indiciating
     * whether a given edge should be counted in the traversal
     * (e.g. an "is visible" function --defaults to a tautological function)
     * @return <boolean> true if the edge is transitive
     */
    checkIfTransitive: function(edge, checkFun){
      var thisGraph = this,
          edgeSource = edge.get("source"),
          edgeTarget = edge.get("target");
      checkFun = checkFun || pvt.alwaysTrue;
      return edgeSource.get("outlinks").any(
        function(ol){
          return edge.id !== ol.id && thisGraph.isPathBetweenNodes(ol.get("target"), edgeTarget, checkFun);
        });
    },

    /**
     * Get a nodes from the graph
     *
     * @return {node collection} the node collection of the model
     */
    getNodes: function() {
      return this.get("nodes");
    },

    /**
     * Get a edges from the graph
     *
     * @return {edge collection} the edge collection of the model
     */
    getEdges: function() {
      return this.get("edges");
    },

    /**
     * Get a node from the graph with the given id
     *
     * @param {node id} nodeId: the node id of the desired node
     * @return {node} the desired node object or undefined if not present
     */
    getNode: function(nodeId) {
      return this.get("nodes").get(nodeId);
    },

    /**
     * Get an edge from the graph with the given id
     *
     * @param {edge id} edgeId: the edge id of the desired edge
     * @return {edge} the desired edge object or undefined if not present
     */
    getEdge: function(edgeId) {
      return this.get("edges").get(edgeId);
    },

    /**
     * Add an edge to the graph: adds the edge to the edge collection and
     * outlinks/dependencies properties in the appropriate nodes
     *
     * @param {edge object} edge: the edge to be added to the model
     */
    addEdge: function(edge) {
      var thisGraph = this,
          // check if source/target are ids and switch to nodes if necessary
          source =  typeof edge.source === "string"  ? this.getNode(edge.source) : edge.source,
          target = typeof edge.target === "string" ? this.getNode(edge.target) :  edge.target;

      if (!source  || !target) {
        throw new Error("source or target was not given correctly for input or does not exist in graph: " + edge.source + " -> " + edge.target );
      }

      edge.source = source;
      edge.target = target;

      if (edge.source.id === edge.target.id){
        console.log("warning: loop edge detected and not added to graph, node: " + edge.source.id);
        return;
      }

      if (edge.id === undefined) {
        pvt.setEdgeId(edge);
      }

      // check if this edge is transitive
      // (will be transitive if there is already a path from the edge source to the edge target)
      edge.isTransitive = thisGraph.isPathBetweenNodes(edge.source, edge.target);

      //
      var edges = thisGraph.getEdges();
      edges.add(edge);
      var mEdge = edges.get(edge.id);
      edge.source.get("outlinks").add(mEdge);
      edge.target.get("dependencies").add(mEdge);

      // check if the new edge changes transitivity of other edges
      if (!edge.isTransitive) {
        thisGraph.getEdges().filter(function(e){return !e.get("isTransitive");}).forEach(function(notTransEdge){
          var isTrans = thisGraph.checkIfTransitive(notTransEdge);
          if (isTrans) {
            notTransEdge.set("isTransitive", true);
          }
        });
      }
    },

    /**
     * Add a node to the graph
     * Note:
     * new nodes that should be synced with a server
     * should not have an id specified and should have a defined "url"
     * in the node backbone model or they should have the property
     * "syncWithServer" set to a non-falsey value
     *
     * @param {node object} node: the node to be added to the model
     */
    addNode: function(node) {
      var thisGraph = this,
          isNewNode = false;
      if (!(node.set )){
        var nodeId = node.id;
        if (nodeId === undefined || nodeId === null || nodeId === "") {
          isNewNode = true;
          node.id = "-new-" + Math.random().toString(36).substring(3);
        }
        node = new thisGraph.nodeModel(node, {parse: true});
      }
      thisGraph.getNodes().add(node);

      if (node.url && (isNewNode || node.get("syncWithServer"))) {
        node.hasServerId = false;

        node.save(["id", "tag"], {
          parse  : false, // NB must check for parse == false in model's parse method
          success: function (model, xhr) {
            node.set("id", xhr.id);
            node.set("tag", xhr.tag);
            node.hasServerId = true;

            // set edge ids that were waiting for the server
            thisGraph.getEdges().filter(function (edge) {
              return edge.get("needsServerId");
            }).forEach(pvt.setEdgeId);
          },
          error: function (model, xhr) {
            console.log("error response text:" + xhr.responseText);
          }
        });
      } else {
        node.hasServerId = true;
      }
    },

    /**
     * Removes an edge from the graph: removes the edge from the edge collection
     * and appropriate outlinks/dependencies properties in the appropriate nodes
     *
     * @param {edge-id or edge object} edge: the edge id or edge object
     */
    removeEdge: function(edge) {
      var thisGraph = this,
          edges = thisGraph.get("edges");

      edge = edge instanceof thisGraph.edgeModel ? edge : edges.get(edge);
      edge.get("source").get("outlinks").remove(edge);
      edge.get("target").get("dependencies").remove(edge);
      edges.remove(edge);

      // possibly change transitivity relationships of transitive edges
      edges.filter(function(e){return e.get("isTransitive");}).forEach(function(transEdge){
        var isTrans = thisGraph.checkIfTransitive(transEdge);
        if (!isTrans){
          transEdge.set("isTransitive", false);
        }
      });
    },

    /**
     * Removes the node from the graph and all edges that were in its dependencies/outlinks attributes
     *
     * @param {node id or node object} node: the node id or node object to be removed
     */
    removeNode: function(node){
      var thisGraph = this,
          nodes = this.get("nodes");
      node =  node instanceof thisGraph.nodeModel ? node : nodes.get(node);
      node.get("dependencies").pluck("id").forEach(function(edgeId){ thisGraph.removeEdge(edgeId);});
      node.get("outlinks").pluck("id").forEach(function(edgeId){ thisGraph.removeEdge(edgeId);});
      nodes.remove(node);
    },

    /**
     * Compute the learning view ordering (topological sort)
     * TODO write tests for getTopoSort FIXME
     */
    getTopoSort: function(){
      var thisGraph = this;
      if (!thisGraph.topoSort || !thisGraph.topoSort.length){
        thisGraph.doTopoSort();
      }
      return thisGraph.topoSort;
    },

    /**
     * Perform a depth first topological sort of the graph
     *
     * @return {list} - sorted ids of the nodes in the graph
     */
    doTopoSort: function () {
      // TODO cache the sort
      var thisGraph = this,
          nodes = thisGraph.getNodes(),
          traversedNodes = {}, // keep track of traversed nodes
          startLeafNodes;

      // init: obtain node tags with 0 outlinks (root nodes)
      startLeafNodes = _.map(nodes.filter(function(mdl){
        return mdl.get("outlinks").length == 0;
      }), function(itm){
        return itm.get("id");
      });

      thisGraph.topoSort = dfsTopSort(startLeafNodes);

      // recursive dfs topological sort
      // TODO this should be defined in pvt?
      function dfsTopSort (leafNodeTags){
        var curLeafNodeTagDepth,
            returnArr = [],
            curLeafNodeTag,
            directDeps,
            curNode;

        // recurse on the input leaf node tags
        // -- use edge weight to do the ordering if available
        for(curLeafNodeTagDepth = 0; curLeafNodeTagDepth < leafNodeTags.length; curLeafNodeTagDepth++){
          curLeafNodeTag = leafNodeTags[curLeafNodeTagDepth];
          curNode = nodes.get(curLeafNodeTag);

          if (!traversedNodes.hasOwnProperty(curLeafNodeTag)) {
            directDeps = curNode.getDirectDeps();
            traversedNodes[curLeafNodeTag] = 1;
            if (directDeps.length > 0){
              returnArr = returnArr.concat(dfsTopSort(directDeps.map(function (dep) {
                return dep.get("source").id;
              })));
            }
            returnArr.push(curLeafNodeTag);

          }
        }
        return returnArr;
      };
    },

    // /**
    //  * Contract all nodes that are not part of the dep graph for the input node id
    //  */
    // showSubgraphFromNodeId: function (nodeId) {
    //   var thisGraph = this;
    //   thisGraph.expandGraph();
    //   var visNodes = {};
    //   var traceDepGraph = function traceDepGraph(node) {
    //     visNodes[node.id] = 1;
    //     node.get("dependencies").each(function (dep) {
    //       var nextNode = dep.get("source");
    //       if (!visNodes.hasOwnProperty(nextNode.id)) {
    //         traceDepGraph(nextNode);
    //       }
    //     });
    //   };

    //   traceDepGraph(thisGraph.getNode(nodeId));

    //   thisGraph.getNodes().each(function (node) {
    //     if (!visNodes.hasOwnProperty(node.id)) {
    //       node.set("isContracted", true);
    //     }
    //   });
    // },

    expandGraph: function () {
      var thisGraph = this;
      thisGraph.getEdges().each(function (edge) {
        edge.set("isContracted", false);
      });

      thisGraph.getNodes().each(function (node) {
        node.set("isContracted", false);
      });
    },

    /**
     * Check if the input edge is present in the topological sort
     * (sheel function - not currently working)
     */
    isEdgeInTopoSort: function(edge){
      var thisGraph = this;
      // make sure we've done a topological sort
      if (!thisGraph.topoSort){
        thisGraph.doTopoSort();
      }
      return edge.isTopoEdge;
    }
  });
});
<|endoftext|>


<commit_before>var final_transcript = '';
var recognizing = false;
var recognition;

// reply processing
var two_line = /\n\n/g;
var one_line = /\n/g;
function linebreak(s) {
  return s.replace(two_line, '

').replace(one_line, '
');
}

var first_char = /\S/;
function capitalize(s) {
  return s.replace(first_char, function(m) { return m.toUpperCase(); });
}

function startConverting() {
  if (recognizing) {
    recognition.stop();
    return;
  }
  final_transcript = '';
  recognition.lang = 'en-US';
  recognition.start();
}

function show_result(){
     if(final_transcript.toLowerCase() == 'hello'){
                $('#hello_response').text("Hi! I'm Bhuvanesh")
                $('#hello_response').css('background-color', '#18bc9c');
            }
            else if(final_transcript.toLowerCase() == 'show me your picture'){
                $('#pic_response').append('');
            }
            else if(final_transcript.toLowerCase() == 'show expense report'){
                $("#report_img").css("display", "block");
                $('#report_img').animate({bottom: '-100px'});
            }
  final_transcript = "";
}

$( document ).ready(function() {
    if('webkitSpeechRecognition' in window){
        recognition = new webkitSpeechRecognition();
        recognition.continuous = false;
        recognition.interimResults = false;
        recognition.maxAlternatives = 1;

        recognition.onstart = function() {
            recognizing = true;
        };
        recognition.onend = function() {
            recognizing = false;
            recognition.start();

        };
        recognition.onspeechstart = function() {

        };

        recognition.onresult = function(event) {
            var interim_transcript = '';
            if (typeof(event.results) == 'undefined') {
            recognition.onend = null;
            recognition.stop();
            return;
            }
            for (var i = event.resultIndex; i < event.results.length; ++i) {
            if (event.results[i].isFinal) {
                final_transcript += event.results[i][0].transcript;
            } else {
                interim_transcript += event.results[i][0].transcript;
            }
            }
            final_transcript = capitalize(final_transcript);
            show_result();
        };

    }
    startConverting();
});
<commit_msg>Update speech_recognition.js<commit_after>var final_transcript = '';
var recognizing = false;
var recognition;

// reply processing
var two_line = /\n\n/g;
var one_line = /\n/g;
function linebreak(s) {
  return s.replace(two_line, '

').replace(one_line, '
');
}

var first_char = /\S/;
function capitalize(s) {
  return s.replace(first_char, function(m) { return m.toUpperCase(); });
}

function startConverting() {
  if (recognizing) {
    recognition.stop();
    return;
  }
  final_transcript = '';
  recognition.lang = 'en-US';
  recognition.start();
}

function show_result(){
     if(final_transcript.toLowerCase() == 'hello'){
                $('#hello_response').text("Hi! I'm Bhuvanesh")
                $('#hello_response').css('background-color', '#18bc9c');
            }
            else if(final_transcript.toLowerCase() == 'show me your picture'){
                $('#pic_response').append('');
            }
            else if(final_transcript.toLowerCase() == 'show expense report'){
//                 $("#report_img").css("display", "block");
                $('#report_img').animate({bottom: '-100px'});
            }
  final_transcript = "";
}

$( document ).ready(function() {
    if('webkitSpeechRecognition' in window){
        recognition = new webkitSpeechRecognition();
        recognition.continuous = false;
        recognition.interimResults = false;
        recognition.maxAlternatives = 1;

        recognition.onstart = function() {
            recognizing = true;
        };
        recognition.onend = function() {
            recognizing = false;
            recognition.start();

        };
        recognition.onspeechstart = function() {

        };

        recognition.onresult = function(event) {
            var interim_transcript = '';
            if (typeof(event.results) == 'undefined') {
            recognition.onend = null;
            recognition.stop();
            return;
            }
            for (var i = event.resultIndex; i < event.results.length; ++i) {
            if (event.results[i].isFinal) {
                final_transcript += event.results[i][0].transcript;
            } else {
                interim_transcript += event.results[i][0].transcript;
            }
            }
            final_transcript = capitalize(final_transcript);
            show_result();
        };

    }
    startConverting();
});
<|endoftext|>


<commit_before>// This is the boilerplate file
// it should be used as a base for every module
define([
  'jquery',
  'underscore',
  'backbone',
  'models/relay',
  'models/graph',
  'text!templates/details/router.html',
  'text!templates/details/bridge.html',
  'text!templates/details/error.html',
  'tooltip',
  'd3js',
  'collapse',
  'helpers'
], function($, _, Backbone, relayModel, graphModel,
        routerDetailsTemplate, bridgeDetailsTemplate, errorDetailsTemplate){
    var mainDetailsView = Backbone.View.extend({
        el: $("#content"),
        initialize: function() {
           this.model = new relayModel;
           this.graph = new graphModel;
           //console.log(this.graph);
           $("#loading").show();
        },
        plot: function(g, data, labels, legendPos, colors, tickFormat,
                       tooltipFormat) {

            /* Initialize graph. */
            var margin = {top: 30, right: 10, bottom: 20, left: 60},
                width = 550 - margin.left - margin.right,
                height = 342 - margin.top - margin.bottom;
            var svg = d3.select("#" + g).append("svg:svg")
                .attr("version", 1.1)
                .attr("xmlns", "http://www.w3.org/2000/svg")
                .attr("width", width + margin.left + margin.right)
                .attr("height", height + margin.top + margin.bottom)
                .append("g")
                .attr("transform",
                    "translate(" + margin.left + "," + margin.top + ")");

            /* Define scales to convert domain values to pixels. */
            var xExtents = d3.extent(d3.merge(data), function(d) {
                return d[0]; });
            var yExtents = d3.extent(d3.merge(data), function(d) {
                return d[1]; });
            var xScale = d3.time.scale()
                .domain(xExtents)
                .range([0, width]);
            var yScale = d3.scale.linear()
                .domain([0, yExtents[1]])
                .range([height, 0]);

            /* Add the x axis. */
            var xAxis = d3.svg.axis()
                .scale(xScale)
                .ticks(4)
                .orient("bottom");
            var xAxisContainer = svg.append("g")
                .attr("class", "x axis")
                .attr("transform", "translate(0, " + height + ")")
                .call(xAxis);
            xAxisContainer.selectAll("line")
                .style("stroke", "lightgrey");
            xAxisContainer.selectAll("path")
                .style("display", "none");
            xAxisContainer.selectAll("minor")
                .style("stroke-opacity", .5);
            xAxisContainer.selectAll("text")
                .style("font-family", "Helvetica")
                .style("font-size", "12px");

            /* Add the y axis. */
            var yAxis = d3.svg.axis()
                .scale(yScale)
                .orient("left")
                .ticks(4)
                .tickSize(-width - 4)
                .tickFormat(d3.format(tickFormat));
            var yAxisContainer = svg.append("g")
                .attr("class", "y axis")
                .call(yAxis);
            yAxisContainer.selectAll("line")
                .style("stroke", "lightgrey");
            yAxisContainer.selectAll("path")
                .style("display", "none");
            yAxisContainer.selectAll("minor")
                .style("stroke-opacity", .5);
            yAxisContainer.selectAll("text")
                .style("font-family", "Helvetica")
                .style("font-size", "12px");

            /* Add one group per data line. */
            var lineContainers = svg.selectAll("g.line")
                .data(data)
                .enter().append("svg:g")
                .attr("class", "line")
                .style("stroke-width", 2)
                .style("stroke", function(d) {
                    return colors[data.indexOf(d)]; });

            /* Add path between all line values. */
            var line = d3.svg.line()
                .defined(function(d) { return d[1] != null; })
                .x(function (d) { return xScale(d[0]); })
                .y(function (d) { return yScale(d[1]); });
            lineContainers.append("svg:path")
                .attr("d", line)
                .style("fill", "none");

            var datetimeFormat = function(seconds) {
              var date = new Date(seconds);
              return d3.time.format("%Y-%m-%d %H:%M")(date);
            }

            var tooltip = d3.select("body").append("div")
                          .attr("class", "tooltip")
                          .style("opacity", 0);

            /* Add dots for all line values, and add tooltips. */
            var tooltipFormatter = d3.format(tooltipFormat);
            lineContainers.selectAll("circle")
                .data(function(d) { return d; })
                .enter()
                .append("svg:circle")
                .attr("class", "dot")
                .attr("cx", function(d) { return xScale(d[0]); })
                .attr("cy", function(d) { return yScale(d[1]); })
                .attr("r", 3)
                .style("fill", "white");

            $("svg circle").tooltip({
              title: function() {
                var d = this.__data__;
                return datetimeFormat(d[0]) + ": " + tooltipFormatter(d[1]);
              }
            });

            /* Add a legend. */
            var legend = svg.append("g")
                .attr("class", "legend");
            legend.selectAll("g").data(data)
                .enter().append("g")
                .each(function(d, i) {
                    var g = d3.select(this);
                    g.append("svg:circle")
                        .attr("cx", legendPos[i][0] - margin.left + 5)
                        .attr("cy", legendPos[i][1] - margin.top + 10)
                        .attr("r", 3)
                        .style("stroke-width", 2)
                        .style("stroke", colors[i])
                        .style("fill", "white");
                    g.append("svg:text")
                        .attr("x", legendPos[i][0] - margin.left + 15)
                        .attr("y", legendPos[i][1] - margin.top + 14)
                        .style("fill", colors[i])
                        .style("font-family", "Helvetica")
                        .style("font-size", "12px")
                        .text(labels[i]);
                });

            /* Remove placeholder image. */
            d3.select("#" + g).selectAll("img").remove();

            /* Encode SVG image for download link. */
            html = d3.select("#" + g)
                .node()
                .innerHTML;
            d3.select("#save_" + g)
                .attr("href", "data:image/svg+xml;base64," + btoa(html));
        },
        render: function() {
            var data = {relay: this.model};
            //console.log(data);
            var compiledTemplate;
            if (!this.model)
                compiledTemplate = _.template(errorDetailsTemplate, data);
            else if (this.model.get('is_bridge'))
                compiledTemplate = _.template(bridgeDetailsTemplate, data);
            else
                compiledTemplate = _.template(routerDetailsTemplate, data);
            document.title = "Atlas: " + this.model.get('nickname');
            this.el.html(compiledTemplate);
            var graph = this.graph;
            var plot = this.plot;
            this.graph.lookup_bw(this.model.fingerprint, {
                success: function() {
                    graph.parse_bw_data(graph.data);
                    graphs = ['bw_month',
                            'bw_months', 'bw_year', 'bw_years'];
                    _.each(graphs, function(g) {
                        var data = [graph.get(g).write, graph.get(g).read];
                        var labels = ["written bytes per second", "read bytes per second"];
                        var legendPos = [[140, 0], [310, 0]];
                        var colors = ["#edc240", "#afd8f8"];
                        plot(g, data, labels, legendPos, colors, "s", ".4s");
                    });
                }
            });

            if (!this.model.get('is_bridge')) {
                this.graph.lookup_weights(this.model.fingerprint, {
                    success: function() {
                        graph.parse_weights_data(graph.data);
                        graphs = ['weights_week', 'weights_month',
                                'weights_months', 'weights_year', 'weights_years'];
                        _.each(graphs, function(g) {
                            var data = [graph.get(g).cw, graph.get(g).middle,
                                        graph.get(g).guard, graph.get(g).exit];
                            var labels = ["consensus weight fraction",
                                          "middle probability",
                                          "guard probability",
                                          "exit probability"];
                            var legendPos = [[28, 0], [309, 0], [194, 0], [429, 0]];
                            var colors = ["#afd8f8", "#edc240",
                                          "#cb4b4b", "#4da74d"];
                            plot(g, data, labels, legendPos, colors, ".4%", ".6%");
                        });
                    }
                });
            } else {
                this.graph.lookup_clients(this.model.fingerprint, {
                    success: function() {
                        graph.parse_clients_data(graph.data);
                        graphs = ['clients_week', 'clients_month',
                                'clients_months', 'clients_year', 'clients_years'];
                        _.each(graphs, function(g) {
                            var data = [graph.get(g).average];
                            var labels = ["average number of connected clients"];
                            var legendPos = [[3, 0]];
                            var colors = ["#edc240"];
                            plot(g, data, labels, legendPos, colors, "g", ".2g");
                        });
                    }
                });
            }

            $("#loading").hide();
            $(".tip").tooltip({ placement: 'right' });
        },
        error: function() {
            var compiledTemplate = _.template(errorDetailsTemplate, {relay: null});
            $("#loading").hide();
            this.el.html(compiledTemplate);
        }
    });
    return new mainDetailsView;
});

<commit_msg>Do not plot empty graphs<commit_after>// This is the boilerplate file
// it should be used as a base for every module
define([
  'jquery',
  'underscore',
  'backbone',
  'models/relay',
  'models/graph',
  'text!templates/details/router.html',
  'text!templates/details/bridge.html',
  'text!templates/details/error.html',
  'tooltip',
  'd3js',
  'collapse',
  'helpers'
], function($, _, Backbone, relayModel, graphModel,
        routerDetailsTemplate, bridgeDetailsTemplate, errorDetailsTemplate){
    var mainDetailsView = Backbone.View.extend({
        el: $("#content"),
        initialize: function() {
           this.model = new relayModel;
           this.graph = new graphModel;
           //console.log(this.graph);
           $("#loading").show();
        },
        plot: function(g, data, labels, legendPos, colors, tickFormat,
                       tooltipFormat) {

            /* do not plot graph if there is no data */
            if (data[0].length==0) return;

            /* Initialize graph. */
            var margin = {top: 30, right: 10, bottom: 20, left: 60},
                width = 550 - margin.left - margin.right,
                height = 342 - margin.top - margin.bottom;
            var svg = d3.select("#" + g).append("svg:svg")
                .attr("version", 1.1)
                .attr("xmlns", "http://www.w3.org/2000/svg")
                .attr("width", width + margin.left + margin.right)
                .attr("height", height + margin.top + margin.bottom)
                .append("g")
                .attr("transform",
                    "translate(" + margin.left + "," + margin.top + ")");

            /* Define scales to convert domain values to pixels. */
            var xExtents = d3.extent(d3.merge(data), function(d) {
                return d[0]; });
            var yExtents = d3.extent(d3.merge(data), function(d) {
                return d[1]; });
            var xScale = d3.time.scale()
                .domain(xExtents)
                .range([0, width]);
            var yScale = d3.scale.linear()
                .domain([0, yExtents[1]])
                .range([height, 0]);

            /* Add the x axis. */
            var xAxis = d3.svg.axis()
                .scale(xScale)
                .ticks(4)
                .orient("bottom");
            var xAxisContainer = svg.append("g")
                .attr("class", "x axis")
                .attr("transform", "translate(0, " + height + ")")
                .call(xAxis);
            xAxisContainer.selectAll("line")
                .style("stroke", "lightgrey");
            xAxisContainer.selectAll("path")
                .style("display", "none");
            xAxisContainer.selectAll("minor")
                .style("stroke-opacity", .5);
            xAxisContainer.selectAll("text")
                .style("font-family", "Helvetica")
                .style("font-size", "12px");

            /* Add the y axis. */
            var yAxis = d3.svg.axis()
                .scale(yScale)
                .orient("left")
                .ticks(4)
                .tickSize(-width - 4)
                .tickFormat(d3.format(tickFormat));
            var yAxisContainer = svg.append("g")
                .attr("class", "y axis")
                .call(yAxis);
            yAxisContainer.selectAll("line")
                .style("stroke", "lightgrey");
            yAxisContainer.selectAll("path")
                .style("display", "none");
            yAxisContainer.selectAll("minor")
                .style("stroke-opacity", .5);
            yAxisContainer.selectAll("text")
                .style("font-family", "Helvetica")
                .style("font-size", "12px");

            /* Add one group per data line. */
            var lineContainers = svg.selectAll("g.line")
                .data(data)
                .enter().append("svg:g")
                .attr("class", "line")
                .style("stroke-width", 2)
                .style("stroke", function(d) {
                    return colors[data.indexOf(d)]; });

            /* Add path between all line values. */
            var line = d3.svg.line()
                .defined(function(d) { return d[1] != null; })
                .x(function (d) { return xScale(d[0]); })
                .y(function (d) { return yScale(d[1]); });
            lineContainers.append("svg:path")
                .attr("d", line)
                .style("fill", "none");

            var datetimeFormat = function(seconds) {
              var date = new Date(seconds);
              return d3.time.format("%Y-%m-%d %H:%M")(date);
            }

            var tooltip = d3.select("body").append("div")
                          .attr("class", "tooltip")
                          .style("opacity", 0);

            /* Add dots for all line values, and add tooltips. */
            var tooltipFormatter = d3.format(tooltipFormat);
            lineContainers.selectAll("circle")
                .data(function(d) { return d; })
                .enter()
                .append("svg:circle")
                .attr("class", "dot")
                .attr("cx", function(d) { return xScale(d[0]); })
                .attr("cy", function(d) { return yScale(d[1]); })
                .attr("r", 3)
                .style("fill", "white");

            $("svg circle").tooltip({
              title: function() {
                var d = this.__data__;
                return datetimeFormat(d[0]) + ": " + tooltipFormatter(d[1]);
              }
            });

            /* Add a legend. */
            var legend = svg.append("g")
                .attr("class", "legend");
            legend.selectAll("g").data(data)
                .enter().append("g")
                .each(function(d, i) {
                    var g = d3.select(this);
                    g.append("svg:circle")
                        .attr("cx", legendPos[i][0] - margin.left + 5)
                        .attr("cy", legendPos[i][1] - margin.top + 10)
                        .attr("r", 3)
                        .style("stroke-width", 2)
                        .style("stroke", colors[i])
                        .style("fill", "white");
                    g.append("svg:text")
                        .attr("x", legendPos[i][0] - margin.left + 15)
                        .attr("y", legendPos[i][1] - margin.top + 14)
                        .style("fill", colors[i])
                        .style("font-family", "Helvetica")
                        .style("font-size", "12px")
                        .text(labels[i]);
                });

            /* Remove placeholder image. */
            d3.select("#" + g).selectAll("img").remove();

            /* Encode SVG image for download link. */
            html = d3.select("#" + g)
                .node()
                .innerHTML;
            d3.select("#save_" + g)
                .attr("href", "data:image/svg+xml;base64," + btoa(html));
        },
        render: function() {
            var data = {relay: this.model};
            //console.log(data);
            var compiledTemplate;
            if (!this.model)
                compiledTemplate = _.template(errorDetailsTemplate, data);
            else if (this.model.get('is_bridge'))
                compiledTemplate = _.template(bridgeDetailsTemplate, data);
            else
                compiledTemplate = _.template(routerDetailsTemplate, data);
            document.title = "Atlas: " + this.model.get('nickname');
            this.el.html(compiledTemplate);
            var graph = this.graph;
            var plot = this.plot;
            this.graph.lookup_bw(this.model.fingerprint, {
                success: function() {
                    graph.parse_bw_data(graph.data);
                    graphs = ['bw_month',
                            'bw_months', 'bw_year', 'bw_years'];
                    _.each(graphs, function(g) {
                        var data = [graph.get(g).write, graph.get(g).read];
                        var labels = ["written bytes per second", "read bytes per second"];
                        var legendPos = [[140, 0], [310, 0]];
                        var colors = ["#edc240", "#afd8f8"];
                        plot(g, data, labels, legendPos, colors, "s", ".4s");
                    });
                }
            });

            if (!this.model.get('is_bridge')) {
                this.graph.lookup_weights(this.model.fingerprint, {
                    success: function() {
                        graph.parse_weights_data(graph.data);
                        graphs = ['weights_week', 'weights_month',
                                'weights_months', 'weights_year', 'weights_years'];
                        _.each(graphs, function(g) {
                            var data = [graph.get(g).cw, graph.get(g).middle,
                                        graph.get(g).guard, graph.get(g).exit];
                            var labels = ["consensus weight fraction",
                                          "middle probability",
                                          "guard probability",
                                          "exit probability"];
                            var legendPos = [[28, 0], [309, 0], [194, 0], [429, 0]];
                            var colors = ["#afd8f8", "#edc240",
                                          "#cb4b4b", "#4da74d"];
                            plot(g, data, labels, legendPos, colors, ".4%", ".6%");
                        });
                    }
                });
            } else {
                this.graph.lookup_clients(this.model.fingerprint, {
                    success: function() {
                        graph.parse_clients_data(graph.data);
                        graphs = ['clients_week', 'clients_month',
                                'clients_months', 'clients_year', 'clients_years'];
                        _.each(graphs, function(g) {
                            var data = [graph.get(g).average];
                            var labels = ["average number of connected clients"];
                            var legendPos = [[3, 0]];
                            var colors = ["#edc240"];
                            plot(g, data, labels, legendPos, colors, "g", ".2g");
                        });
                    }
                });
            }

            $("#loading").hide();
            $(".tip").tooltip({ placement: 'right' });
        },
        error: function() {
            var compiledTemplate = _.template(errorDetailsTemplate, {relay: null});
            $("#loading").hide();
            this.el.html(compiledTemplate);
        }
    });
    return new mainDetailsView;
});

<|endoftext|>


<commit_before>/* global Marionette, Handlebars */

/**
 * @author Christoph Wurst <christoph@winzerhof-wurst.at>
 *
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

(function(OCA, Marionette, Handlebars) {
	'use strict';

	OCA.SpreedMe = OCA.SpreedMe || {};
	OCA.SpreedMe.Views = OCA.SpreedMe.Views || {};

	var uiChannel = Backbone.Radio.channel('ui');

	var ITEM_TEMPLATE = '

 {{displayName}}'+
						'
'+
							'
'+
								'
<button></button>
'+
							'
'+
						'
'+
						'
'+
							'
'+
								'
'+
									'<button class="add-person-button">'+
										''+
										''+t('spreedme', 'Add person')+''+
									'</button>'+
								'
'+
							'
'+
							'
'+
								'
'+
									'<button class="share-group-button">'+
										''+
										''+t('spreedme', 'Share group')+''+
									'</button>'+
								'
'+
							'
'+
							'
'+
								'
'+
									'<button class="leave-group-button">'+
										''+
										''+t('spreedme', 'Leave group')+''+
									'</button>'+
								'
'+
							'
'+
						'
';

	var RoomItenView = Marionette.View.extend({
		tagName: 'li',
		modelEvents: {
			change: 'render'
		},
		initialize: function() {
			// Add class to every room list item to detect it on click.
			this.$el.addClass('room-list-item');

			this.listenTo(uiChannel, 'document:click', function(event) {
				var target = $(event.target);

				if (!this.$el.is(target.closest('.room-list-item'))) {
					// Click was not triggered by this element -> close menu
					this.menuShown = false;
					this.toggleMenuClass();
				}
			});
		},
		onRender: function() {
			if (this.model.get('active')) {
				this.$el.addClass('active');
			} else {
				this.$el.removeClass('active');
			}

			if (this.model.get('type') === 1) { // 1on1
				_.each(this.$el.find('.avatar'), function(a) {
					$(a).avatar($(a).data('user'), 32);
				});
			} else if (this.model.get('type') === 2) { // group
				_.each(this.$el.find('.avatar'), function(a) {
					$(a).addClass('icon-contacts-dark');
				});
			}

			this.toggleMenuClass();
		},
		events: {
			'click .app-navigation-entry-utils-menu-button button': 'toggleMenu',
			'click .app-navigation-entry-menu .add-person-button': 'addPerson',
			'click .app-navigation-entry-menu .share-group-button': 'shareGroup',
			'click .app-navigation-entry-menu .leave-group-button': 'leaveGroup',
		},
		ui: {
			'menu': 'div.app-navigation-entry-menu',
		},
		template: Handlebars.compile(ITEM_TEMPLATE),
		menuShown: false,
		toggleMenu: function(e) {
			e.preventDefault();
			this.menuShown = !this.menuShown;
			this.toggleMenuClass();
		},
		toggleMenuClass: function() {
			this.ui.menu.toggleClass('open', this.menuShown);
		},
		addPerson: function() {
			console.log("add person", this.model.get('id'));
		},
		shareGroup: function() {
			console.log("share group", this.model.get('id'));
		},
		leaveGroup: function() {
			//If user is in that room, it should leave that room first.
			if (this.model.get('active')) {
				OCA.SpreedMe.webrtc.leaveRoom();
				window.location.replace(window.location.href.slice(0, -window.location.hash.length));
			}

			this.$el.slideUp();

			$.ajax({
				url: OC.generateUrl('/apps/spreed/api/room/') + this.model.get('id'),
				type: 'DELETE'
			});
		}
	});

	var RoomListView = Marionette.CollectionView.extend({
		tagName: 'ul',
		childView: RoomItenView
	});

	OCA.SpreedMe.Views.RoomListView = RoomListView;

})(OCA, Marionette, Handlebars);
<commit_msg>Only re-render rooms when 'active' or 'displayName' attributes change.<commit_after>/* global Marionette, Handlebars */

/**
 * @author Christoph Wurst <christoph@winzerhof-wurst.at>
 *
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

(function(OCA, Marionette, Handlebars) {
	'use strict';

	OCA.SpreedMe = OCA.SpreedMe || {};
	OCA.SpreedMe.Views = OCA.SpreedMe.Views || {};

	var uiChannel = Backbone.Radio.channel('ui');

	var ITEM_TEMPLATE = '

 {{displayName}}'+
						'
'+
							'
'+
								'
<button></button>
'+
							'
'+
						'
'+
						'
'+
							'
'+
								'
'+
									'<button class="add-person-button">'+
										''+
										''+t('spreedme', 'Add person')+''+
									'</button>'+
								'
'+
							'
'+
							'
'+
								'
'+
									'<button class="share-group-button">'+
										''+
										''+t('spreedme', 'Share group')+''+
									'</button>'+
								'
'+
							'
'+
							'
'+
								'
'+
									'<button class="leave-group-button">'+
										''+
										''+t('spreedme', 'Leave group')+''+
									'</button>'+
								'
'+
							'
'+
						'
';

	var RoomItenView = Marionette.View.extend({
		tagName: 'li',
		modelEvents: {
			'change:active': function() {
			  this.render();
			},
			'change:displayName': function() {
			  this.render();
			}
		},
		initialize: function() {
			// Add class to every room list item to detect it on click.
			this.$el.addClass('room-list-item');

			this.listenTo(uiChannel, 'document:click', function(event) {
				var target = $(event.target);

				if (!this.$el.is(target.closest('.room-list-item'))) {
					// Click was not triggered by this element -> close menu
					this.menuShown = false;
					this.toggleMenuClass();
				}
			});
		},
		onRender: function() {
			if (this.model.get('active')) {
				this.$el.addClass('active');
			} else {
				this.$el.removeClass('active');
			}

			if (this.model.get('type') === 1) { // 1on1
				_.each(this.$el.find('.avatar'), function(a) {
					$(a).avatar($(a).data('user'), 32);
				});
			} else if (this.model.get('type') === 2) { // group
				_.each(this.$el.find('.avatar'), function(a) {
					$(a).addClass('icon-contacts-dark');
				});
			}

			this.toggleMenuClass();
		},
		events: {
			'click .app-navigation-entry-utils-menu-button button': 'toggleMenu',
			'click .app-navigation-entry-menu .add-person-button': 'addPerson',
			'click .app-navigation-entry-menu .share-group-button': 'shareGroup',
			'click .app-navigation-entry-menu .leave-group-button': 'leaveGroup',
		},
		ui: {
			'menu': 'div.app-navigation-entry-menu',
		},
		template: Handlebars.compile(ITEM_TEMPLATE),
		menuShown: false,
		toggleMenu: function(e) {
			e.preventDefault();
			this.menuShown = !this.menuShown;
			this.toggleMenuClass();
		},
		toggleMenuClass: function() {
			this.ui.menu.toggleClass('open', this.menuShown);
		},
		addPerson: function() {
			console.log("add person", this.model.get('id'));
		},
		shareGroup: function() {
			console.log("share group", this.model.get('id'));
		},
		leaveGroup: function() {
			//If user is in that room, it should leave that room first.
			if (this.model.get('active')) {
				OCA.SpreedMe.webrtc.leaveRoom();
				window.location.replace(window.location.href.slice(0, -window.location.hash.length));
			}

			this.$el.slideUp();

			$.ajax({
				url: OC.generateUrl('/apps/spreed/api/room/') + this.model.get('id'),
				type: 'DELETE'
			});
		}
	});

	var RoomListView = Marionette.CollectionView.extend({
		tagName: 'ul',
		childView: RoomItenView
	});

	OCA.SpreedMe.Views.RoomListView = RoomListView;

})(OCA, Marionette, Handlebars);
<|endoftext|>


<commit_before>var jsdocPackage = require('../index');
var Dgeni = require('dgeni');
var mockLog = require('dgeni/lib/mocks/log');

describe('jsdoc package', function() {
  it("should be instance of Package", function() {
      expect(jsdocPackage instanceof Dgeni.Package).toBeTruthy();
  });

  describe("computePathsProcessor", function() {

    function runDgeni(docs) {
      var testPackage = new Dgeni.Package('testPackage', [jsdocPackage])
        .factory('templateEngine', function() {})
        .factory('log', function() { return mockLog(false); })
        .processor('provideTestDocs', function() {
          return {
            $runBefore: ['computePathsProcessor'],
            $process: function() {
              return docs;
            }
          };
        })

        .config(function(readFilesProcessor, writeFilesProcessor, renderDocsProcessor, unescapeCommentsProcessor) {
          readFilesProcessor.$enabled = false;
          writeFilesProcessor.$enabled = false;
          renderDocsProcessor.$enabled = false;
          unescapeCommentsProcessor.$enabled = false;
        });

      return new Dgeni([testPackage]).generate();
    }

    it("should compute the path of the document from its name or codeName", function(done) {
      var doc1 = { docType: 'js', fileInfo: { relativePath: 'a/b/c/foo.ngdoc', baseName: 'foo' }, name: 'fooName', codeName: 'fooCodeName' };
      var doc2 = { docType: 'js', fileInfo: { relativePath: 'x/y/z/index.html', baseName: 'index' }, codeName: 'xyz' };

        runDgeni([doc1,doc2]).then(function(docs) {
          expect(doc1.path).toEqual('fooName');
          expect(doc1.outputPath).toEqual('fooName.html');
          expect(doc2.path).toEqual('xyz');
          expect(doc2.outputPath).toEqual('xyz.html');
          done();
        });
    });

    it("should compute the path of the document from its file name", function(done) {
      var doc1 = { docType: 'js', fileInfo: { relativePath: 'a/b/c/foo.ngdoc', baseName: 'foo' } };
      var doc2 = { docType: 'js', fileInfo: { relativePath: 'x/y/z/index.html', baseName: 'index' } };

        runDgeni([doc1,doc2]).then(function(docs) {
          expect(doc1.path).toEqual('a/b/c/foo');
          expect(doc1.outputPath).toEqual('a/b/c/foo.html');
          expect(doc2.path).toEqual('x/y/z');
          expect(doc2.outputPath).toEqual('x/y/z.html');
          done();
        });
    });
  });
});<commit_msg>test(jsdoc): disable computeIdsProcessor for test<commit_after>var jsdocPackage = require('../index');
var Dgeni = require('dgeni');
var mockLog = require('dgeni/lib/mocks/log');

describe('jsdoc package', function() {
  it("should be instance of Package", function() {
      expect(jsdocPackage instanceof Dgeni.Package).toBeTruthy();
  });

  describe("computePathsProcessor", function() {

    function runDgeni(docs) {
      var testPackage = new Dgeni.Package('testPackage', [jsdocPackage])
        .factory('templateEngine', function() {})
        .factory('log', function() { return mockLog(false); })
        .processor('provideTestDocs', function() {
          return {
            $runBefore: ['computePathsProcessor'],
            $process: function() {
              return docs;
            }
          };
        })

        .config(function(readFilesProcessor, writeFilesProcessor, computeIdsProcessor, renderDocsProcessor, unescapeCommentsProcessor) {
          readFilesProcessor.$enabled = false;
          writeFilesProcessor.$enabled = false;
          computeIdsProcessor.$enabled = false;
          renderDocsProcessor.$enabled = false;
          unescapeCommentsProcessor.$enabled = false;
        });

      return new Dgeni([testPackage]).generate();
    }

    it("should compute the path of the document from its name or codeName", function(done) {
      var doc1 = { docType: 'js', fileInfo: { relativePath: 'a/b/c/foo.ngdoc', baseName: 'foo' }, name: 'fooName', codeName: 'fooCodeName' };
      var doc2 = { docType: 'js', fileInfo: { relativePath: 'x/y/z/index.html', baseName: 'index' }, codeName: 'xyz' };

        runDgeni([doc1,doc2]).then(function(docs) {
          expect(doc1.path).toEqual('fooName');
          expect(doc1.outputPath).toEqual('fooName.html');
          expect(doc2.path).toEqual('xyz');
          expect(doc2.outputPath).toEqual('xyz.html');
          done();
        });
    });

    it("should compute the path of the document from its file name", function(done) {
      var doc1 = { docType: 'js', fileInfo: { relativePath: 'a/b/c/foo.ngdoc', baseName: 'foo' } };
      var doc2 = { docType: 'js', fileInfo: { relativePath: 'x/y/z/index.html', baseName: 'index' } };

        runDgeni([doc1,doc2]).then(function(docs) {
          expect(doc1.path).toEqual('a/b/c/foo');
          expect(doc1.outputPath).toEqual('a/b/c/foo.html');
          expect(doc2.path).toEqual('x/y/z');
          expect(doc2.outputPath).toEqual('x/y/z.html');
          done();
        });
    });
  });
});<|endoftext|>


<commit_before>/**
 * Validates blocks for AMP compatibility.
 *
 * This uses the REST API response from saving a page to find validation errors.
 * If one exists for a block, it display it inline with a Notice component.
 */

/* exported ampBlockValidation */
/* global wp, _ */
var ampBlockValidation = ( function() {
	'use strict';

	var module = {
		/**
		 * Holds data.
		 */
		data: {},

		/**
		 * The blocks with validation errors.
		 */
		blocksWithErrors: {},

		/**
		 * Boot module.
		 *
		 * @param {Object} data - Module data.
		 * @return {void}
		 */
		boot: function boot( data ) {
			module.data = data;
			wp.data.subscribe( function() {
				module.blocksWithErrors = module.getBlocksWithErrors();
			} );
			wp.hooks.addFilter(
				'blocks.BlockEdit',
				'amp/add-notice',
				module.conditionallyAddNotice
			);
		},

		/**
		 * Wraps the edit() method of a block, and conditionally adds a Notice.
		 *
		 * @param {Function} OriginalBlockEdit - The original edit() method of the block.
		 * @returns {Function} The edit() method, conditionally wrapped in a notice for AMP validation error(s).
		 */
		conditionallyAddNotice: function conditionallyAddNotice( OriginalBlockEdit ) {
			return function( props ) {
				var errorPanel,
					errors = module.getBlockValidationErrors( props ),
					result = [ wp.element.createElement( OriginalBlockEdit, _.extend( props, { key: 'amp-original-edit' } ) ) ];

				if ( errors.length > 0 ) {
					errorPanel = wp.element.createElement(
						wp.components.PanelBody,
						{
							title: module.data.i18n.notice.replace( '%s', props.name ),
							children:  module.getErrorSummary( errors ),
							initialOpen: false
						}
					);
					result.unshift(
						wp.element.createElement(
							wp.components.Notice,
							{
								status: 'warning',
								content: errorPanel, //
								isDismissible: false,
								key: 'amp-validation-notice'
							}
						)
					);
				}
				return result;
			};
		},

		/**
		 * Gets the block types with errors.
		 *
		 * Iterates through the 'amp_validation_errors' from the REST API response.
		 * This returns an object, with block types as the keys, and error arrays as the values.
		 * The block's overridden edit() method can then get the errors for its block type.
		 *
		 * @returns {Object} The blocks with errors.
		 */
		getBlocksWithErrors: function getBlocksWithErrors() {
			var currentPost      = wp.data.select( 'core/editor' ).getCurrentPost(),
				blocksWithErrors = {};
			if ( ! currentPost.hasOwnProperty( module.data.errorKey ) || ! Array.isArray( currentPost[ module.data.errorKey ] ) ) {
				return blocksWithErrors;
			}

			currentPost[ module.data.errorKey ].forEach( function( validationError ) {
				if ( validationError.hasOwnProperty( 'sources' ) ) {
					validationError.sources.forEach( function( source ) {
						if ( source.hasOwnProperty( 'block_name' ) ) {
							if ( source.hasOwnProperty( 'block_attrs' ) ) {
								validationError.blockAttrs = source.block_attrs; // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
							}
							if ( blocksWithErrors.hasOwnProperty( source.block_name ) ) {
								blocksWithErrors[ source.block_name ].push( validationError );
							} else {
								blocksWithErrors[ source.block_name ] = [ validationError ];
							} // jscs:enable requireCamelCaseOrUpperCaseIdentifiers
						}
					} );
				}
			} );
			return blocksWithErrors;
		},

		/**
		 * Gets the validation errors for a specific block if they exist.
		 *
		 * In module.blocksWithErrors, the errors are stored by block name.
		 * This finds the validation errors for a specific block, based on its attributes or content.
		 *
		 * @todo: keep refining how this finds if the errors match.
		 * @param {Object} props - Properties for the block.
		 * @return {Array} The validation error(s) for the block, or an empty array.
		 */
		getBlockValidationErrors: function getBlockValidationErrors( props ) {
			var rawErrors,
				validationErrors = [];

			if ( ! module.blocksWithErrors.hasOwnProperty( props.name ) ) {
				return validationErrors;
			}
			rawErrors = module.blocksWithErrors[ props.name ];

			rawErrors.forEach( function( validationError ) {

				// Uses _.isMatch because the props attributes can also have default attributes that blockAttrs doesn't have.
				if ( validationError.hasOwnProperty( 'blockAttrs' ) && _.isMatch( props.attributes, validationError.blockAttrs ) ) {
					validationErrors.push( validationError );
				} else if ( module.doNameAndAttributesMatch( validationError, props.attributes ) ) {
					validationErrors.push( validationError );
				}
			} );

			return validationErrors;
		},

		/**
		 * Whether the node_name and node_attributes in the validation error are present in the block.
		 *
		 * @param {Object} validationError - The validation errors to check.
		 * @param {Object} propAttributes  - The block attributes, originally passed in the props object.
		 * @returns {Boolean} Whether node_name and the node_attributes are in the block.
		 */
		doNameAndAttributesMatch: function doNameAndAttributesMatch( validationError, propAttributes ) {
			var attribute, attributes,
				attributesKey = module.getAttributesKey( validationError );
			if ( ! attributesKey || ! propAttributes.hasOwnProperty( 'content' ) || ! propAttributes.content.includes( validationError.node_name ) ) { // jscs:ignore requireCamelCaseOrUpperCaseIdentifiers
				return false;
			}

			// Ensure the content has all attributes and properties in the validationError.
			attributes = validationError[ attributesKey ];
			for ( attribute in attributes ) {
				if ( ! attributes.hasOwnProperty( attribute ) || ! propAttributes.content.includes( attribute ) || ! propAttributes.content.includes( attributes[ attribute ] ) ) {
					return false;
				}
			}
			return true;
		},

		/**
		 * Gets the key for the attributes in validationError.
		 *
		 * @param {Object} validationError - The validation errors to check.
		 * @returns {String|null} attributeKey The key used to get the attributes, or null.
		 */
		getAttributesKey: function getAttributesKey( validationError ) {
			if ( validationError.hasOwnProperty( 'node_attributes' ) ) {
				return 'node_attributes';
			} else if ( validationError.hasOwnProperty( 'element_attributes' ) ) {
				return 'element_attributes';
			} else {
				return null;
			}
		},

		/**
		 * Gets the unique error codes from the block errors.
		 *
		 * @param {Array} errors - The validation errors for a block.
		 * @returns {String} errorCodes A comma-separated string of validation error codes.
		 */
		getErrorSummary: function getErrorSummary( errors ) {
			var allErrors = [];

			errors.forEach( function( validationError ) {
				if ( ! allErrors.includes( validationError.code ) ) {
					allErrors.push( validationError.code );
				}
			} );
			return module.data.i18n.summary + ': ' + allErrors.join( ', ' ) + '
%s
'.replace( '%s', JSON.stringify( errors ) );
		}

	};

	return module;
} )();
<commit_msg>Fix: Each child in an array or iterator should have a unique "key" prop<commit_after>/**
 * Validates blocks for AMP compatibility.
 *
 * This uses the REST API response from saving a page to find validation errors.
 * If one exists for a block, it display it inline with a Notice component.
 */

/* exported ampBlockValidation */
/* global wp, _ */
var ampBlockValidation = ( function() {
	'use strict';

	var module = {
		/**
		 * Holds data.
		 */
		data: {},

		/**
		 * The blocks with validation errors.
		 */
		blocksWithErrors: {},

		/**
		 * Boot module.
		 *
		 * @param {Object} data - Module data.
		 * @return {void}
		 */
		boot: function boot( data ) {
			module.data = data;
			wp.data.subscribe( function() {
				module.blocksWithErrors = module.getBlocksWithErrors();
			} );
			wp.hooks.addFilter(
				'blocks.BlockEdit',
				'amp/add-notice',
				module.conditionallyAddNotice
			);
		},

		/**
		 * Wraps the edit() method of a block, and conditionally adds a Notice.
		 *
		 * @param {Function} BlockEdit - The original edit() method of the block.
		 * @returns {Function} The edit() method, conditionally wrapped in a notice for AMP validation error(s).
		 */
		conditionallyAddNotice: function conditionallyAddNotice( BlockEdit ) {
			return function( props ) {
				var errorPanel,
					errors = module.getBlockValidationErrors( props ),
					result = [ wp.element.createElement( BlockEdit, _.extend( {}, props, { key: 'amp-original-edit' } ) ) ];

				if ( errors.length > 0 ) {
					errorPanel = wp.element.createElement(
						wp.components.PanelBody,
						{
							title: module.data.i18n.notice.replace( '%s', props.name ),
							children:  module.getErrorSummary( errors ),
							initialOpen: false
						}
					);
					result.unshift(
						wp.element.createElement(
							wp.components.Notice,
							{
								status: 'warning',
								content: errorPanel, //
								isDismissible: false,
								key: 'amp-validation-notice'
							}
						)
					);
				}
				return result;
			};
		},

		/**
		 * Gets the block types with errors.
		 *
		 * Iterates through the 'amp_validation_errors' from the REST API response.
		 * This returns an object, with block types as the keys, and error arrays as the values.
		 * The block's overridden edit() method can then get the errors for its block type.
		 *
		 * @returns {Object} The blocks with errors.
		 */
		getBlocksWithErrors: function getBlocksWithErrors() {
			var currentPost      = wp.data.select( 'core/editor' ).getCurrentPost(),
				blocksWithErrors = {};
			if ( ! currentPost.hasOwnProperty( module.data.errorKey ) || ! Array.isArray( currentPost[ module.data.errorKey ] ) ) {
				return blocksWithErrors;
			}

			currentPost[ module.data.errorKey ].forEach( function( validationError ) {
				if ( validationError.hasOwnProperty( 'sources' ) ) {
					validationError.sources.forEach( function( source ) {
						if ( source.hasOwnProperty( 'block_name' ) ) {
							if ( source.hasOwnProperty( 'block_attrs' ) ) {
								validationError.blockAttrs = source.block_attrs; // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
							}
							if ( blocksWithErrors.hasOwnProperty( source.block_name ) ) {
								blocksWithErrors[ source.block_name ].push( validationError );
							} else {
								blocksWithErrors[ source.block_name ] = [ validationError ];
							} // jscs:enable requireCamelCaseOrUpperCaseIdentifiers
						}
					} );
				}
			} );
			return blocksWithErrors;
		},

		/**
		 * Gets the validation errors for a specific block if they exist.
		 *
		 * In module.blocksWithErrors, the errors are stored by block name.
		 * This finds the validation errors for a specific block, based on its attributes or content.
		 *
		 * @todo: keep refining how this finds if the errors match.
		 * @param {Object} props - Properties for the block.
		 * @return {Array} The validation error(s) for the block, or an empty array.
		 */
		getBlockValidationErrors: function getBlockValidationErrors( props ) {
			var rawErrors,
				validationErrors = [];

			if ( ! module.blocksWithErrors.hasOwnProperty( props.name ) ) {
				return validationErrors;
			}
			rawErrors = module.blocksWithErrors[ props.name ];

			rawErrors.forEach( function( validationError ) {

				// Uses _.isMatch because the props attributes can also have default attributes that blockAttrs doesn't have.
				if ( validationError.hasOwnProperty( 'blockAttrs' ) && _.isMatch( props.attributes, validationError.blockAttrs ) ) {
					validationErrors.push( validationError );
				} else if ( module.doNameAndAttributesMatch( validationError, props.attributes ) ) {
					validationErrors.push( validationError );
				}
			} );

			return validationErrors;
		},

		/**
		 * Whether the node_name and node_attributes in the validation error are present in the block.
		 *
		 * @param {Object} validationError - The validation errors to check.
		 * @param {Object} propAttributes  - The block attributes, originally passed in the props object.
		 * @returns {Boolean} Whether node_name and the node_attributes are in the block.
		 */
		doNameAndAttributesMatch: function doNameAndAttributesMatch( validationError, propAttributes ) {
			var attribute, attributes,
				attributesKey = module.getAttributesKey( validationError );
			if ( ! attributesKey || ! propAttributes.hasOwnProperty( 'content' ) || ! propAttributes.content.includes( validationError.node_name ) ) { // jscs:ignore requireCamelCaseOrUpperCaseIdentifiers
				return false;
			}

			// Ensure the content has all attributes and properties in the validationError.
			attributes = validationError[ attributesKey ];
			for ( attribute in attributes ) {
				if ( ! attributes.hasOwnProperty( attribute ) || ! propAttributes.content.includes( attribute ) || ! propAttributes.content.includes( attributes[ attribute ] ) ) {
					return false;
				}
			}
			return true;
		},

		/**
		 * Gets the key for the attributes in validationError.
		 *
		 * @param {Object} validationError - The validation errors to check.
		 * @returns {String|null} attributeKey The key used to get the attributes, or null.
		 */
		getAttributesKey: function getAttributesKey( validationError ) {
			if ( validationError.hasOwnProperty( 'node_attributes' ) ) {
				return 'node_attributes';
			} else if ( validationError.hasOwnProperty( 'element_attributes' ) ) {
				return 'element_attributes';
			} else {
				return null;
			}
		},

		/**
		 * Gets the unique error codes from the block errors.
		 *
		 * @param {Array} errors - The validation errors for a block.
		 * @returns {String} errorCodes A comma-separated string of validation error codes.
		 */
		getErrorSummary: function getErrorSummary( errors ) {
			var allErrors = [];

			errors.forEach( function( validationError ) {
				if ( ! allErrors.includes( validationError.code ) ) {
					allErrors.push( validationError.code );
				}
			} );
			return module.data.i18n.summary + ': ' + allErrors.join( ', ' ) + '
%s
'.replace( '%s', JSON.stringify( errors ) );
		}

	};

	return module;
} )();
<|endoftext|>