<commit_before>var $document = $(document),
    $source = $('#source');

var editorModes = {
  html: 'htmlmixed',
  javascript: 'javascript',
  css: 'css'
};

var Panel = function (name, settings) {
  var panel = this,
      showPanelButton = true,
      $panel = null,
      splitterSettings = {},
      cmSettings = {};

  panel.settings = settings = settings || {};
  panel.id = panel.name = name;
  $panel = $('.panel.' + name);
  $panel.data('name', name);
  panel.$el = $panel.detach();
  panel.$el.appendTo($source);
  panel.$el.wrapAll('
');
  panel.$el = panel.$el.parent().hide();
  panel.el = document.getElementById(name);
  panel.order = ++Panel.order;

  panel.$el.data('panel', panel);

  // keyboard shortcut (set in keyboardcontrol.js)
  panelShortcuts[panelShortcuts.start + panel.order] = panel.id;

  if (panel.order === 1) {
    settings.nosplitter = true;
  }

  if (settings.editor) {
    cmSettings = {
      parserfile: [],
      tabMode: 'shift',
      readOnly: jsbin.state.embed ? 'nocursor' : false,
      dragDrop: false, // we handle it ourselves
      mode: editorModes[name],
      onChange: function (event) {
        $document.trigger('codeChange', [{ panelId: panel.id, revert: true }]);
        return true;
      },
      lineWrapping: true,
      theme: 'jsbin'
    };

    $.extend(cmSettings, jsbin.settings.codemirror || {});

    panel.editor = CodeMirror.fromTextArea(panel.el, cmSettings);

    panel.processor = settings.processor || function (str) { return str; };

    panel._setupEditor(panel.editor, name);
  }

  if (!settings.nosplitter) {
    // console.log(panel.id, wrapper);
    panel.splitter = panel.$el.splitter(splitterSettings).data('splitter');
    panel.splitter.hide();
  } else {
    // create a fake splitter to let the rest of the code work
    panel.splitter = $();
  }

  if (settings.beforeRender) {
    $document.bind('render', $.proxy(settings.beforeRender, panel));
  }

  if (!settings.editor) {
    panel.ready = true;
  }

  // append panel to controls
  if (jsbin.state.embed) {
    // showPanelButton = window.location.search.indexOf(panel.id) !== -1;
  }

  if (showPanelButton) {
    this.controlButton = $('' + (settings.label || name) + '');
    this.controlButton.click(function () {
      panel.toggle();
      return false;
    });
    this.controlButton.appendTo('#panels');
  }

  $panel.focus(function () {
    panel.focus();
  });
  $panel.add(this.$el.find('.label')).click(function () {
    panel.focus();
  });
}

Panel.order = 0;

Panel.prototype = {
  virgin: true,
  visible: false,
  show: function (x) {
    // check to see if there's a panel to the left.
    // if there is, take it's size/2 and make this our
    // width
    var panel = this,
        panelCount = panel.$el.find('.panel').length;

    analytics.showPanel(panel.id);

    // panel.$el.show();
    if (panel.splitter.length) {
      if (panelCount == 0 || panelCount > 1) {
        var $panel = $('.panel.' + panel.id).show();
        // $panel.next().show(); // should be the splitter...
        $panel.closest('.panelwrapper').show();
      } else {
        panel.$el.show();
      }
      panel.splitter.show();
    } else {
      panel.$el.show();
    }

    $body.addClass('panelsVisible');

    panel.visible = true;
    if (panel.settings.show) {
      panel.settings.show.call(panel, true);
    }
    panel.controlButton.addClass('active');

    // update the splitter - but do it on the next tick
    // required to allow the splitter to see it's visible first
    setTimeout(function () {
      if (x !== undefined) {
        panel.splitter.trigger('init', x);
      } else {
        panel.distribute();
      }
      // update all splitter positions
      $document.trigger('sizeeditors');
      if (panel.editor) {
        // populate the panel for the first time
        if (panel.virgin) {
          $(panel.editor.win).find('.CodeMirror-scroll > div').css('padding-top', panel.$el.find('.label').outerHeight());

          populateEditor(panel, panel.name);
        }
        panel.editor.focus();
        panel.focus();
      }
      panel.virgin = false;
  }, 0);

    // TODO save which panels are visible in their profile - but check whether it's their code
  },
  hide: function () {
    var panel = this;
    // panel.$el.hide();
    panel.visible = false;
    analytics.hidePanel(panel.id);

    // update all splitter positions
    // LOGIC: when you go to hide, you need to check if there's
    // other panels inside the panel wrapper - if there are
    // hide the nested panel and any previous visible splitter
    // if there's only one - then hide the whole thing.
    // if (panel.splitter.length) {
    var panelCount = panel.$el.find('.panel').length;
    if (panelCount == 0 || panelCount > 1) {
      var $panel = $('.panel.' + panel.id).hide();
      $panel.prev().hide(); // hide the splitter if there is one

      // TODO trigger a distribute horizontally
      if ($panel.closest('.panelwrapper').find('.panel:visible').length === 0) {
        console.log('hiding related panel');
        $panel.closest('.panelwrapper').hide();
        // panel.splitter.hide();
        // TODO FIXME
      }
    } else {
      panel.$el.hide();
      panel.splitter.hide();
    }
    // } else {
    //   panel.$el.hide();
    // }
    panel.controlButton.removeClass('active');
    panel.distribute();

    if (panel.settings.hide) {
      panel.settings.hide.call(panel, true);
    }

    // this.controlButton.show();
    setTimeout(function () {
      $document.trigger('sizeeditors');
    }, 110);
  },
  toggle: function () {
    (this)[this.visible ? 'hide' : 'show']();
  },
  getCode: function () {
    if (this.editor) {
      return this.editor.getCode();
    }
  },
  setCode: function (content) {
    if (this.editor) {
      this.codeSet = true;
      this.editor.setCode(content);
    }
  },
  codeSet: false,
  focus: function () {
    jsbin.panels.focus(this);
  },
  render: function () {
    var panel = this;
    if (panel.editor) {
      return panel.processor(panel.getCode());
    } else if (this.visible && this.settings.render) {
      this.settings.render.call(this);
    }
  },
  init: function () {
    this.settings.init && this.settings.init.call(this);
  },
  _setupEditor: function () {
    var focusedPanel = sessionStorage.getItem('panel'),
        panel = this,
        editor = panel.editor;

    // overhang from CodeMirror1
    editor.setCode = function (str) {
      //Cannot call method 'chunkSize' of undefined
      try {
        editor.setValue(str);
      } catch(err) {
        // console.error(panel.id, err + '');
      }
    };

    editor.getCode = function () {
      return editor.getValue();
    };

    editor.currentLine = function () {
      var pos = editor.getCursor();
      return pos.line;
    };

    // editor.setOption('onKeyEvent', keycontrol);
    // editor.setOption('onFocus', function () {
      // panel.$el.trigger('focus');
    // });

    editor.id = panel.name;

    editor.win = editor.getWrapperElement();
    editor.scroller = $(editor.getScrollerElement());

    $(editor.win).click(function () {
      editor.focus();
      panel.focus();
    });

    var $label = panel.$el.find('.label');
    if (document.body.className.indexOf('ie6') === -1 && $label.length) {
      editor.scroller.scroll(function (event) {
        if (this.scrollTop > 10) {
          $label.stop().animate({ opacity: 0 }, 20, function () {
            $(this).hide();
          });
        } else {
          $label.show().stop().animate({ opacity: 1 }, 150);
        }
      });
    }

    var $error = null;
    $document.bind('sizeeditors', function () {
      if (panel.visible) {
        var height = panel.editor.scroller.closest('.panel').outerHeight(),
            offset = 0;
            // offset = panel.$el.find('> .label').outerHeight();

        // special case for the javascript panel
        if (panel.name === 'javascript') {
          if ($error === null) { // it wasn't there right away, so we populate
            $error = panel.$el.find('details');
          }
          console.log('offset now ', offset)
          offset += ($error.filter(':visible').height() || 0);
          console.log('offset after ', offset, $error.filter(':visible').height())
        }

        editor.scroller.height(height - offset);
        try { editor.refresh(); } catch (e) {}
      }
    });

    // required because the populate looks at the height, and at
    // this point in the code, the editor isn't visible, the browser
    // needs one more tick and it'll be there.
    setTimeout(function () {
      // if the panel isn't visible this only has the effect of putting
      // the code in the textarea (though probably costs us a lot more)
      // it has to be re-populated upon show for the first time because
      // it appears that CM2 uses the visible height to work out what
      // should be shown.
      panel.ready = true;
      populateEditor(panel, panel.name);

      if (focusedPanel == panel.name || focusedPanel == null) {
        if (panel.visible) {
          // another fracking timeout to avoid conflict with other panels firing up
          setTimeout(function () {
            editor.focus();
            panel.focus();
            editor.setCursor({ line: (sessionStorage.getItem('line') || 0) * 1, ch: (sessionStorage.getItem('character') || 0) * 1 });
          }, 0);
        }
      }
    }, 0);
  },
  populateEditor: function () {
    populateEditor(this, this.name);
  }
};

function populateEditor(editor, panel) {
  if (!editor.codeSet) {
    // populate - should eventually use: session, saved data, local storage
    var cached = sessionStorage.getItem('jsbin.content.' + panel), // session code
        saved = localStorage.getItem('saved-' + panel), // user template
        sessionURL = sessionStorage.getItem('url'),
        changed = false;

    if (sessionURL !== template.url) {
      // nuke the live saving checksum
      sessionStorage.removeItem('checksum');
      saveChecksum = false;
    }

    if (template && cached == template[panel]) { // restored from original saved
      editor.setCode(cached);
    } else if (cached && sessionURL == template.url) { // try to restore the session first - only if it matches this url
      editor.setCode(cached);
      // tell the document that it's currently being edited, but check that it doesn't match the saved template
      // because sessionStorage gets set on a reload
      changed = cached != saved && cached != template[panel];
    } else if (saved !== null && !/edit/.test(window.location) && !window.location.search) { // then their saved preference
      editor.setCode(saved);
    } else { // otherwise fall back on the JS Bin default
      editor.setCode(template[panel]);
    }
  } else {
    // this means it was set via the url
    changed = true;
  }

  if (changed) {
    $document.trigger('codeChange', [ { revert: false, onload: true } ]);
  }
}<commit_msg>Removed logging<commit_after>var $document = $(document),
    $source = $('#source');

var editorModes = {
  html: 'htmlmixed',
  javascript: 'javascript',
  css: 'css'
};

var Panel = function (name, settings) {
  var panel = this,
      showPanelButton = true,
      $panel = null,
      splitterSettings = {},
      cmSettings = {};

  panel.settings = settings = settings || {};
  panel.id = panel.name = name;
  $panel = $('.panel.' + name);
  $panel.data('name', name);
  panel.$el = $panel.detach();
  panel.$el.appendTo($source);
  panel.$el.wrapAll('
');
  panel.$el = panel.$el.parent().hide();
  panel.el = document.getElementById(name);
  panel.order = ++Panel.order;

  panel.$el.data('panel', panel);

  // keyboard shortcut (set in keyboardcontrol.js)
  panelShortcuts[panelShortcuts.start + panel.order] = panel.id;

  if (panel.order === 1) {
    settings.nosplitter = true;
  }

  if (settings.editor) {
    cmSettings = {
      parserfile: [],
      tabMode: 'shift',
      readOnly: jsbin.state.embed ? 'nocursor' : false,
      dragDrop: false, // we handle it ourselves
      mode: editorModes[name],
      onChange: function (event) {
        $document.trigger('codeChange', [{ panelId: panel.id, revert: true }]);
        return true;
      },
      lineWrapping: true,
      theme: 'jsbin'
    };

    $.extend(cmSettings, jsbin.settings.codemirror || {});

    panel.editor = CodeMirror.fromTextArea(panel.el, cmSettings);

    panel.processor = settings.processor || function (str) { return str; };

    panel._setupEditor(panel.editor, name);
  }

  if (!settings.nosplitter) {
    // console.log(panel.id, wrapper);
    panel.splitter = panel.$el.splitter(splitterSettings).data('splitter');
    panel.splitter.hide();
  } else {
    // create a fake splitter to let the rest of the code work
    panel.splitter = $();
  }

  if (settings.beforeRender) {
    $document.bind('render', $.proxy(settings.beforeRender, panel));
  }

  if (!settings.editor) {
    panel.ready = true;
  }

  // append panel to controls
  if (jsbin.state.embed) {
    // showPanelButton = window.location.search.indexOf(panel.id) !== -1;
  }

  if (showPanelButton) {
    this.controlButton = $('' + (settings.label || name) + '');
    this.controlButton.click(function () {
      panel.toggle();
      return false;
    });
    this.controlButton.appendTo('#panels');
  }

  $panel.focus(function () {
    panel.focus();
  });
  $panel.add(this.$el.find('.label')).click(function () {
    panel.focus();
  });
}

Panel.order = 0;

Panel.prototype = {
  virgin: true,
  visible: false,
  show: function (x) {
    // check to see if there's a panel to the left.
    // if there is, take it's size/2 and make this our
    // width
    var panel = this,
        panelCount = panel.$el.find('.panel').length;

    analytics.showPanel(panel.id);

    // panel.$el.show();
    if (panel.splitter.length) {
      if (panelCount == 0 || panelCount > 1) {
        var $panel = $('.panel.' + panel.id).show();
        // $panel.next().show(); // should be the splitter...
        $panel.closest('.panelwrapper').show();
      } else {
        panel.$el.show();
      }
      panel.splitter.show();
    } else {
      panel.$el.show();
    }

    $body.addClass('panelsVisible');

    panel.visible = true;
    if (panel.settings.show) {
      panel.settings.show.call(panel, true);
    }
    panel.controlButton.addClass('active');

    // update the splitter - but do it on the next tick
    // required to allow the splitter to see it's visible first
    setTimeout(function () {
      if (x !== undefined) {
        panel.splitter.trigger('init', x);
      } else {
        panel.distribute();
      }
      // update all splitter positions
      $document.trigger('sizeeditors');
      if (panel.editor) {
        // populate the panel for the first time
        if (panel.virgin) {
          $(panel.editor.win).find('.CodeMirror-scroll > div').css('padding-top', panel.$el.find('.label').outerHeight());

          populateEditor(panel, panel.name);
        }
        panel.editor.focus();
        panel.focus();
      }
      panel.virgin = false;
  }, 0);

    // TODO save which panels are visible in their profile - but check whether it's their code
  },
  hide: function () {
    var panel = this;
    // panel.$el.hide();
    panel.visible = false;
    analytics.hidePanel(panel.id);

    // update all splitter positions
    // LOGIC: when you go to hide, you need to check if there's
    // other panels inside the panel wrapper - if there are
    // hide the nested panel and any previous visible splitter
    // if there's only one - then hide the whole thing.
    // if (panel.splitter.length) {
    var panelCount = panel.$el.find('.panel').length;
    if (panelCount == 0 || panelCount > 1) {
      var $panel = $('.panel.' + panel.id).hide();
      $panel.prev().hide(); // hide the splitter if there is one

      // TODO trigger a distribute horizontally
      if ($panel.closest('.panelwrapper').find('.panel:visible').length === 0) {
        $panel.closest('.panelwrapper').hide();
        // panel.splitter.hide();
        // TODO FIXME
      }
    } else {
      panel.$el.hide();
      panel.splitter.hide();
    }
    // } else {
    //   panel.$el.hide();
    // }
    panel.controlButton.removeClass('active');
    panel.distribute();

    if (panel.settings.hide) {
      panel.settings.hide.call(panel, true);
    }

    // this.controlButton.show();
    setTimeout(function () {
      $document.trigger('sizeeditors');
    }, 110);
  },
  toggle: function () {
    (this)[this.visible ? 'hide' : 'show']();
  },
  getCode: function () {
    if (this.editor) {
      return this.editor.getCode();
    }
  },
  setCode: function (content) {
    if (this.editor) {
      this.codeSet = true;
      this.editor.setCode(content);
    }
  },
  codeSet: false,
  focus: function () {
    jsbin.panels.focus(this);
  },
  render: function () {
    var panel = this;
    if (panel.editor) {
      return panel.processor(panel.getCode());
    } else if (this.visible && this.settings.render) {
      this.settings.render.call(this);
    }
  },
  init: function () {
    this.settings.init && this.settings.init.call(this);
  },
  _setupEditor: function () {
    var focusedPanel = sessionStorage.getItem('panel'),
        panel = this,
        editor = panel.editor;

    // overhang from CodeMirror1
    editor.setCode = function (str) {
      //Cannot call method 'chunkSize' of undefined
      try {
        editor.setValue(str);
      } catch(err) {
        // console.error(panel.id, err + '');
      }
    };

    editor.getCode = function () {
      return editor.getValue();
    };

    editor.currentLine = function () {
      var pos = editor.getCursor();
      return pos.line;
    };

    // editor.setOption('onKeyEvent', keycontrol);
    // editor.setOption('onFocus', function () {
      // panel.$el.trigger('focus');
    // });

    editor.id = panel.name;

    editor.win = editor.getWrapperElement();
    editor.scroller = $(editor.getScrollerElement());

    $(editor.win).click(function () {
      editor.focus();
      panel.focus();
    });

    var $label = panel.$el.find('.label');
    if (document.body.className.indexOf('ie6') === -1 && $label.length) {
      editor.scroller.scroll(function (event) {
        if (this.scrollTop > 10) {
          $label.stop().animate({ opacity: 0 }, 20, function () {
            $(this).hide();
          });
        } else {
          $label.show().stop().animate({ opacity: 1 }, 150);
        }
      });
    }

    var $error = null;
    $document.bind('sizeeditors', function () {
      if (panel.visible) {
        var height = panel.editor.scroller.closest('.panel').outerHeight(),
            offset = 0;
            // offset = panel.$el.find('> .label').outerHeight();

        // special case for the javascript panel
        if (panel.name === 'javascript') {
          if ($error === null) { // it wasn't there right away, so we populate
            $error = panel.$el.find('details');
          }
          offset += ($error.filter(':visible').height() || 0);
        }

        editor.scroller.height(height - offset);
        try { editor.refresh(); } catch (e) {}
      }
    });

    // required because the populate looks at the height, and at
    // this point in the code, the editor isn't visible, the browser
    // needs one more tick and it'll be there.
    setTimeout(function () {
      // if the panel isn't visible this only has the effect of putting
      // the code in the textarea (though probably costs us a lot more)
      // it has to be re-populated upon show for the first time because
      // it appears that CM2 uses the visible height to work out what
      // should be shown.
      panel.ready = true;
      populateEditor(panel, panel.name);

      if (focusedPanel == panel.name || focusedPanel == null) {
        if (panel.visible) {
          // another fracking timeout to avoid conflict with other panels firing up
          setTimeout(function () {
            editor.focus();
            panel.focus();
            editor.setCursor({ line: (sessionStorage.getItem('line') || 0) * 1, ch: (sessionStorage.getItem('character') || 0) * 1 });
          }, 0);
        }
      }
    }, 0);
  },
  populateEditor: function () {
    populateEditor(this, this.name);
  }
};

function populateEditor(editor, panel) {
  if (!editor.codeSet) {
    // populate - should eventually use: session, saved data, local storage
    var cached = sessionStorage.getItem('jsbin.content.' + panel), // session code
        saved = localStorage.getItem('saved-' + panel), // user template
        sessionURL = sessionStorage.getItem('url'),
        changed = false;

    if (sessionURL !== template.url) {
      // nuke the live saving checksum
      sessionStorage.removeItem('checksum');
      saveChecksum = false;
    }

    if (template && cached == template[panel]) { // restored from original saved
      editor.setCode(cached);
    } else if (cached && sessionURL == template.url) { // try to restore the session first - only if it matches this url
      editor.setCode(cached);
      // tell the document that it's currently being edited, but check that it doesn't match the saved template
      // because sessionStorage gets set on a reload
      changed = cached != saved && cached != template[panel];
    } else if (saved !== null && !/edit/.test(window.location) && !window.location.search) { // then their saved preference
      editor.setCode(saved);
    } else { // otherwise fall back on the JS Bin default
      editor.setCode(template[panel]);
    }
  } else {
    // this means it was set via the url
    changed = true;
  }

  if (changed) {
    $document.trigger('codeChange', [ { revert: false, onload: true } ]);
  }
}<|endoftext|>


<commit_before>define([], function() {

  'use strict';

  window.diana = window.diana || {};
  window.diana.helpers = window.diana.helpers || {};
  var diana = window.diana;

  diana.helpers.Graph = {

    formatRegex: {
      minute: /^\d{2}\/\d{2}\/\d{4} \d{2}:\d{2}:00$/,
      hour: /^\d{2}\/\d{2}\/\d{4} \d{2}:00$/,
      day: /^\d{2}\/\d{2}\/\d{4}$/,
      month: /^\d{4}-\d{2}$/,
      year: /^\d{4}$/
    },

    unitFormat: {
      minute: 'MM/DD/YYYY HH:mm:ss',
      hour: 'MM/DD/YYYY HH:mm',
      day: 'MM/DD/YYYY',
      month: 'YYYY-MM',
      year: 'YYYY'
    },

    /**
     * Adjust graph axis options based on a data set and default options.
     *
     * Ex. if there is only one data point, then ensure the axis scope is zoomed
     * in on that point.
     *
     * @param data {Array} Array of [x,y] arrays.
     * @param axes {Object} Default options for each axis.
     * - x {Object}
     * - y {Object}
     * @return {Object} Modified 'axes'.
     */
    adjustAxes: function(data, axes) {
      axes = _.clone(axes);
      axes.xaxis = axes.xaxis || {};
      axes.yaxis = axes.yaxis || {};

      var xunit = diana.helpers.Graph.detectDateUnit(data[0][0]);

      if (data.length == 1) {
        // Zoom in, one unit padding on both sides.
        axes.xaxis.min = diana.helpers.Graph.subtractDateUnit(data[0][0], 1);
        axes.xaxis.max = diana.helpers.Graph.addDateUnit(data[0][0], 1);
        axes.xaxis.tickInterval = '1 ' + xunit;

        // Add proportional top-padding.
        axes.yaxis.max = data[0][1] * 1.5;
      }

      axes.yaxis.min = 0;

      return axes;
    },

    /**
     * Detect a formatted date/time's unit and add an amount of them
     *
     * @param str {String} Ex. '03/12/2012'.
     * @param amount {Number} Ex. 1
     * @return {String} Ex. '03/11/2012'
     */
    addDateUnit: function(str, amount) {
      var unit = diana.helpers.Graph.detectDateUnit(str);
      var format = diana.helpers.Graph.unitFormat[unit];
      switch (unit) {
        case 'minute':
        case 'hour':
        case 'day':
        case 'month':
        case 'year':
          return moment(str).add(unit + 's', amount).format(format);
        default: return null;
      }
    },

    /**
     * Detect a formatted date/time's unit and subtract an amount of them.
     *
     * @param str {String} Ex. '03/12/2012'.
     * @param amount {Number} Ex. 1
     * @return {String} Ex. '03/11/2012'
     */
    subtractDateUnit: function(str, amount) {
      return diana.helpers.Graph.addDateUnit(str, -1 * amount);
    },

    /**
     * Detect a formatted date/time's unit.
     *
     * @param str {String} Ex. '03/12/2012'.
     * @return {String} minute, hour, day, month, year
     */
    detectDateUnit: function(str) {
      var match = null;
      _.any(diana.helpers.Graph.formatRegex, function(regex, unit) {
        if (str.match(regex)) {
          match = unit;
          return true;
        }
        return false;
      });
      return match;
    }
  };
});
<commit_msg>Remove x-axis padding for multi-point graph data.<commit_after>define([], function() {

  'use strict';

  window.diana = window.diana || {};
  window.diana.helpers = window.diana.helpers || {};
  var diana = window.diana;

  diana.helpers.Graph = {

    formatRegex: {
      minute: /^\d{2}\/\d{2}\/\d{4} \d{2}:\d{2}:00$/,
      hour: /^\d{2}\/\d{2}\/\d{4} \d{2}:00$/,
      day: /^\d{2}\/\d{2}\/\d{4}$/,
      month: /^\d{4}-\d{2}$/,
      year: /^\d{4}$/
    },

    unitFormat: {
      minute: 'MM/DD/YYYY HH:mm:ss',
      hour: 'MM/DD/YYYY HH:mm',
      day: 'MM/DD/YYYY',
      month: 'YYYY-MM',
      year: 'YYYY'
    },

    /**
     * Adjust graph axis options based on a data set and default options.
     *
     * Ex. if there is only one data point, then ensure the axis scope is zoomed
     * in on that point.
     *
     * @param data {Array} Array of [x,y] arrays.
     * @param axes {Object} Default options for each axis.
     * - x {Object}
     * - y {Object}
     * @return {Object} Modified 'axes'.
     */
    adjustAxes: function(data, axes) {
      axes = _.clone(axes);
      axes.xaxis = axes.xaxis || {};
      axes.yaxis = axes.yaxis || {};

      var xunit = diana.helpers.Graph.detectDateUnit(data[0][0]);

      if (data.length == 1) {
        // Zoom in, one unit padding on both sides.
        axes.xaxis.min = diana.helpers.Graph.subtractDateUnit(data[0][0], 1);
        axes.xaxis.max = diana.helpers.Graph.addDateUnit(data[0][0], 1);
        axes.xaxis.tickInterval = '1 ' + xunit;

        // Add proportional top-padding.
        axes.yaxis.max = data[0][1] * 1.5;
      } else {
        // Remove all x-axis padding.
        axes.xaxis.min = data[0][0];
        axes.xaxis.max = data[data.length - 1][0];
      }

      axes.yaxis.min = 0;

      return axes;
    },

    /**
     * Detect a formatted date/time's unit and add an amount of them
     *
     * @param str {String} Ex. '03/12/2012'.
     * @param amount {Number} Ex. 1
     * @return {String} Ex. '03/11/2012'
     */
    addDateUnit: function(str, amount) {
      var unit = diana.helpers.Graph.detectDateUnit(str);
      var format = diana.helpers.Graph.unitFormat[unit];
      switch (unit) {
        case 'minute':
        case 'hour':
        case 'day':
        case 'month':
        case 'year':
          return moment(str).add(unit + 's', amount).format(format);
        default: return null;
      }
    },

    /**
     * Detect a formatted date/time's unit and subtract an amount of them.
     *
     * @param str {String} Ex. '03/12/2012'.
     * @param amount {Number} Ex. 1
     * @return {String} Ex. '03/11/2012'
     */
    subtractDateUnit: function(str, amount) {
      return diana.helpers.Graph.addDateUnit(str, -1 * amount);
    },

    /**
     * Detect a formatted date/time's unit.
     *
     * @param str {String} Ex. '03/12/2012'.
     * @return {String} minute, hour, day, month, year
     */
    detectDateUnit: function(str) {
      var match = null;
      _.any(diana.helpers.Graph.formatRegex, function(regex, unit) {
        if (str.match(regex)) {
          match = unit;
          return true;
        }
        return false;
      });
      return match;
    }
  };
});
<|endoftext|>


<commit_before>import assert from 'assert';

import bucketclient from 'bucketclient';

import Config from '../../Config';

class BucketClientInterface {
    constructor() {
        const config = new Config();
        assert(config.bucketd.bootstrap.length > 0,
               'bucketd bootstrap list is empty');
        this.client = new bucketclient.RESTClient(config.bucketd.bootstrap,
                                                  config.log);
    }

    createBucket(bucketName, bucketMD, log, cb) {
        this.client.createBucket(bucketName, log.getSerializedUids(),
            JSON.stringify(bucketMD), err => {
                if (err instanceof Error) {
                    return cb(err.message);
                }
                return cb(err);
            });
    }

    getBucketAttributes(bucketName, log, cb) {
        this.client.getBucketAttributes(bucketName, log.getSerializedUids(),
            (err, data) => {
                if (err instanceof Error) {
                    return cb(err.message);
                }
                return cb(err, JSON.parse(data));
            });
    }

    putBucketAttributes(bucketName, bucketMD, log, cb) {
        this.client.putBucketAttributes(bucketName, log.getSerializedUids(),
            JSON.stringify(bucketMD), (err, data) => {
                if (err instanceof Error) {
                    return cb(err.message);
                }
                return cb(err, data);
            });
    }

    deleteBucket(bucketName, log, cb) {
        this.client.deleteBucket(bucketName, log.getSerializedUids(), err => {
            if (err instanceof Error) {
                return cb(err.message);
            }
            return cb(err);
        });
    }

    putObject(bucketName, objName, objVal, log, cb) {
        this.client.putObject(bucketName, objName, JSON.stringify(objVal),
            log.getSerializedUids(), err => {
                if (err instanceof Error) {
                    return cb(err.message);
                }
                return cb(err);
            });
    }

    getObject(bucketName, objName, log, cb) {
        this.client.getObject(bucketName, objName, log.getSerializedUids(),
            (err, data) => {
                if (err instanceof Error) {
                    return cb(err.message);
                }
                return cb(err, JSON.parse(data));
            });
    }

    deleteObject(bucketName, objName, log, cb) {
        this.client.deleteObject(bucketName, objName, log.getSerializedUids(),
            err => {
                if (err instanceof Error) {
                    return cb(err.message);
                }
                return cb(err);
            });
    }

    listObject(bucketName, params, log, cb) {
        this.client.listObject(bucketName, log.getSerializedUids(), params,
            (err, data) => {
                if (err instanceof Error) {
                    return cb(err.message);
                }
                return cb(err, JSON.parse(data));
            });
    }
}

export default BucketClientInterface;
<commit_msg>(errors) Update metadata backend<commit_after>import assert from 'assert';

import bucketclient from 'bucketclient';

import Config from '../../Config';

class BucketClientInterface {
    constructor() {
        const config = new Config();
        assert(config.bucketd.bootstrap.length > 0,
               'bucketd bootstrap list is empty');
        this.client = new bucketclient.RESTClient(config.bucketd.bootstrap,
                                                  config.log);
    }

    createBucket(bucketName, bucketMD, log, cb) {
        this.client.createBucket(bucketName, log.getSerializedUids(),
            JSON.stringify(bucketMD), err => {
                if (err instanceof Error) {
                    return cb(err);
                }
                return cb(err);
            });
    }

    getBucketAttributes(bucketName, log, cb) {
        this.client.getBucketAttributes(bucketName, log.getSerializedUids(),
            (err, data) => {
                if (err instanceof Error) {
                    return cb(err);
                }
                return cb(err, JSON.parse(data));
            });
    }

    putBucketAttributes(bucketName, bucketMD, log, cb) {
        this.client.putBucketAttributes(bucketName, log.getSerializedUids(),
            JSON.stringify(bucketMD), (err, data) => {
                if (err instanceof Error) {
                    return cb(err);
                }
                return cb(err, data);
            });
    }

    deleteBucket(bucketName, log, cb) {
        this.client.deleteBucket(bucketName, log.getSerializedUids(), err => {
            if (err instanceof Error) {
                return cb(err);
            }
            return cb(err);
        });
    }

    putObject(bucketName, objName, objVal, log, cb) {
        this.client.putObject(bucketName, objName, JSON.stringify(objVal),
            log.getSerializedUids(), err => {
                if (err instanceof Error) {
                    return cb(err);
                }
                return cb(err);
            });
    }

    getObject(bucketName, objName, log, cb) {
        this.client.getObject(bucketName, objName, log.getSerializedUids(),
            (err, data) => {
                if (err instanceof Error) {
                    return cb(err);
                }
                return cb(err, JSON.parse(data));
            });
    }

    deleteObject(bucketName, objName, log, cb) {
        this.client.deleteObject(bucketName, objName, log.getSerializedUids(),
            err => {
                if (err instanceof Error) {
                    return cb(err);
                }
                return cb(err);
            });
    }

    listObject(bucketName, params, log, cb) {
        this.client.listObject(bucketName, log.getSerializedUids(), params,
            (err, data) => {
                if (err instanceof Error) {
                    return cb(err);
                }
                return cb(err, JSON.parse(data));
            });
    }
}

export default BucketClientInterface;
<|endoftext|>


<commit_before>import { getEmptyPositions } from './getEmptyPositions';
import { move } from './move';

/**
 * Get Win Positions or Empty Positions
 * @func
 * @param {[Number]} oldGame old game
 * @return {Number} random empty position
 */
const getBestPositions = (oldGame) => {
  const emptyPositions = getEmptyPositions(oldGame.board);

  const winPositions = emptyPositions.filter(position => {
    const testGame = move(oldGame, position);
    return testGame.ended;
  });

  return winPositions.length > 0
    ? winPositions
    : emptyPositions;
};

export {
  getBestPositions
};
<commit_msg>Add losePositions<commit_after>import { getEmptyPositions } from './getEmptyPositions';
import { move } from './move';
import { filter, head, pipe } from 'ramda';

/**
 * Get another item from the list
 *
 * #TODO: Move to ptz-fp
 *
 * @param {*} item item to be excluded
 * @param {*} list list to get a different item
 * @return {*} another item
 */
const getOther = (item, list) => pipe(
  filter(i => i !== item),
  head
)(list);

/**
 * Get Win Positions or Empty Positions
 * @func
 * @param {[Number]} oldGame old game
 * @return {Number} random empty position
 */
const getBestPositions = (oldGame) => {
  const emptyPositions = getEmptyPositions(oldGame.board);

  const winPositions = emptyPositions.filter(position => {
    const testGame = move(oldGame, position);
    return testGame.ended;
  });

  if (winPositions.length > 0) {
    return winPositions;
  }

  const losePositions = emptyPositions.filter(position => {
    const testGame = move(oldGame, getOther(position, emptyPositions));
    const testGame2 = move(testGame, position);
    return testGame2.ended;
  });

  return losePositions.length > 0
    ? losePositions
    : emptyPositions;
};

export {
  getBestPositions
};
<|endoftext|>


<commit_before>Andamio.Application = function (options) {
  _.extend(this, options);
  this.vent = _.extend({}, Backbone.Events);
};

_.extend(Andamio.Application.prototype, Backbone.Events, {
  // Selector where the main appview will be rendered
  container: 'main',

  // data-region where every page will be displayed
  el: 'page',

  // Starts the app
  start: function (options) {
    if (options.appView) {
      this._initAppView(options.appView);
    }

    if (this.router) {
      this._initRouter();
    }

    this.initialize.apply(this, arguments);
  },

  initialize: function () {},

  // Initialize app router
  _initRouter: function () {
    var that = this;

    // Instantiate the Router if it's the constructor
    if (_.isFunction(this.router)) {
      this.router = new this.router();
    }

    // Application region manages all views that are requested upon navigation
    this.appRegion = new Andamio.Region({
      el: this.el,
      initialize: function () {
        this.listenTo(that.router, 'navigate', this.show);
      },
      onShow: function () {
        that.vent.trigger('navigate', this.currentView);
      }
    });

    Backbone.history.start();

    // Navigate to default route
    if (!Backbone.history.fragment) {
      var defaultRoute = _.findWhere(this.router.routes, {default: true});

      if (defaultRoute) {
        this.router.navigate(defaultRoute.url, {trigger: true});
      }
    }
  },

  // Initialize app view
  _initAppView: function (appView) {
    $(this.container).empty().append(appView.render().el);
    this.appView = appView;
  }
});

Andamio.Application.extend = Andamio.extend;
<commit_msg>Improve and fix Andamio.Application to allow using the vent object<commit_after>Andamio.Application = function (options) {
  _.extend(this, options);
  this.vent = _.extend({}, Backbone.Events);
};

_.extend(Andamio.Application.prototype, Backbone.Events, {
  // Selector where the main appview will be rendered
  container: 'main',

  // data-region where every page will be displayed
  el: 'page',

  // Starts the app
  start: function (options) {
    if (options.appView) {
      this._initAppView(options.appView);
    }

    // Initialize the router
    if (options.router) {
      this._initRouter(options.router);
    }

    this.initialize.apply(this, arguments);
  },

  initialize: function () {},

  // Initialize app router
  _initRouter: function (router) {

    // Instantiate the Router
    this.router = _.isFunction(router) ? new router() : router;

    this._initAppRegion();

    Backbone.history.start();

    // Navigate to default route
    if (!Backbone.history.fragment) {
      var defaultRoute = _.findWhere(this.router.routes, {default: true});

      if (defaultRoute) {
        this.router.navigate(defaultRoute.url, {trigger: true});
      }
    }
  },

  // Application region that manages all requested views upon navigation
  _initAppRegion: function () {
    var app = this;

    this.appRegion = new Andamio.Region({
      el: this.el,
      initialize: function () {
        this.listenTo(app.router, 'navigate', this.show);
      },
      onShow: function () {
        app.vent.trigger('navigate', this.currentView);
      }
    });
  },

  // Initialize app view
  _initAppView: function (appView) {
    this.appView = _.isFunction(appView) ? new appView() : appView;

    $(this.container).empty().append(this.appView.render().el);
  }
});

Andamio.Application.extend = Andamio.extend;
<|endoftext|>


<commit_before>var https = require('https');
var CronJob = require('cron').CronJob;

var currentWeek = 'UNKNOWN';

module.exports.currentWeek = function() {
  if (currentWeek != 'UNKNOWN') {
    return currentWeek;
  } else {
    console.log('currentWeek ' + currentWeek);
    var week = '';
    var schedulePageOptions = {
      host: 'www.portergaud.edu',
      port: 443,
      path: '/page.cfm?p=1346&period=week',
      method: 'GET',
      headers: {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0'
      }
    };
    var req = https.request(schedulePageOptions, function(res) {
      var data = '';
      res.on('data', function(d) {
        data += d;
      });
      res.on('end', function() {
        data = data.toLowerCase();
        if (data.indexOf('special schedule') > -1 || data.indexOf('special day') > -1) {
          week = 'A';
          alert("Today is a special schedule");
        } else if (data.indexOf('a week') > -1 || data.indexOf('week a') > -1) {
          week = 'A';
        } else if (data.indexOf('b week') > -1 || data.indexOf('week b') > -1) {
          week = 'B';
        } else {
          week = 'WEEKEND';
        }
        currentWeek = week;
        return week;
      });
    });
    req.end();
  }
};

module.exports.currentWeek();

new CronJob('00 01 00 * * *', function() {
  console.log('Automatically updating the week...');
  currentWeek = 'UNKNOWN';
  module.exports.currentWeek();
}, null, true, 'America/New_York');

module.exports.getFutureWeek = function(day) {
  if (day.getDay() === 0 || day.getDay() === 6) {
    return ''; // will be implemented closer to 2017
  }
  return (day.getWeek() % 2 === 0) ? 'A' : 'B';
  // var week = '';
  // console.log('http://www.portergaud.edu/page.cfm?p=1346&start=' + month + '/' + date + '/' + year + '&period=week');
  // var request = http.get('http://www.portergaud.edu/page.cfm?p=1346&start=' + month + '/' + date + '/' + year + '&period=week', function(response) {
  //   var data = '';
  //   response.on('end', function(d) {
  //     data += d;
  //   });
  //   response.on('end', function() {
  //     if (data.indexOf('A Week' > -1) || data.indexOf('Week A' > -1)) {
  //       week = 'A';
  //     } else if (data.indexOf('B Week' > -1) || data.indexOf('Week B' > -1)) {
  //       week = 'B';
  //     } else {
  //       week = 'WEEKEND';
  //     }
  //     console.log(week);
  //     return week;
  //   });
  // });
};

Date.prototype.getWeek = function() {
  var d = new Date(+this);
  d.setHours(0, 0, 0);
  d.setDate(d.getDate() + 4 - (d.getDay() || 7));
  return Math.ceil((((d - new Date(d.getFullYear(), 0, 1)) / 8.64e7) + 1) / 7);
};
<commit_msg>Update getweek.js to remove special schedule alert<commit_after>var https = require('https');
var CronJob = require('cron').CronJob;

var currentWeek = 'UNKNOWN';

module.exports.currentWeek = function() {
  if (currentWeek != 'UNKNOWN') {
    return currentWeek;
  } else {
    console.log('currentWeek ' + currentWeek);
    var week = '';
    var schedulePageOptions = {
      host: 'www.portergaud.edu',
      port: 443,
      path: '/page.cfm?p=1346&period=week',
      method: 'GET',
      headers: {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0'
      }
    };
    var req = https.request(schedulePageOptions, function(res) {
      var data = '';
      res.on('data', function(d) {
        data += d;
      });
      res.on('end', function() {
        data = data.toLowerCase();
        if (data.indexOf('a week') > -1 || data.indexOf('week a') > -1) {
          week = 'A';
        } else if (data.indexOf('b week') > -1 || data.indexOf('week b') > -1) {
          week = 'B';
        } else {
          week = 'WEEKEND';
        }
        currentWeek = week;
        return week;
      });
    });
    req.end();
  }
};

module.exports.currentWeek();

new CronJob('00 01 00 * * *', function() {
  console.log('Automatically updating the week...');
  currentWeek = 'UNKNOWN';
  module.exports.currentWeek();
}, null, true, 'America/New_York');

module.exports.getFutureWeek = function(day) {
  if (day.getDay() === 0 || day.getDay() === 6) {
    return ''; // will be implemented closer to 2017
  }
  return (day.getWeek() % 2 === 0) ? 'A' : 'B';
  // var week = '';
  // console.log('http://www.portergaud.edu/page.cfm?p=1346&start=' + month + '/' + date + '/' + year + '&period=week');
  // var request = http.get('http://www.portergaud.edu/page.cfm?p=1346&start=' + month + '/' + date + '/' + year + '&period=week', function(response) {
  //   var data = '';
  //   response.on('end', function(d) {
  //     data += d;
  //   });
  //   response.on('end', function() {
  //     if (data.indexOf('A Week' > -1) || data.indexOf('Week A' > -1)) {
  //       week = 'A';
  //     } else if (data.indexOf('B Week' > -1) || data.indexOf('Week B' > -1)) {
  //       week = 'B';
  //     } else {
  //       week = 'WEEKEND';
  //     }
  //     console.log(week);
  //     return week;
  //   });
  // });
};

Date.prototype.getWeek = function() {
  var d = new Date(+this);
  d.setHours(0, 0, 0);
  d.setDate(d.getDate() + 4 - (d.getDay() || 7));
  return Math.ceil((((d - new Date(d.getFullYear(), 0, 1)) / 8.64e7) + 1) / 7);
};
<|endoftext|>


<commit_before>/* eslint-disable no-param-reassign */
/*
 * Copyright 2019 Expedia Group
 *
 *         Licensed under the Apache License, Version 2.0 (the "License");
 *         you may not use this file except in compliance with the License.
 *         You may obtain a copy of the License at
 *
 *             http://www.apache.org/licenses/LICENSE-2.0
 *
 *         Unless required by applicable law or agreed to in writing, software
 *         distributed under the License is distributed on an "AS IS" BASIS,
 *         WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *         See the License for the specific language governing permissions and
 *         limitations under the License.
 */

const {type} = require('./enums');
const {detectCycles} = require('./detectCycles');
const {edge, gateway, mesh, database, outbound, service} = require('../../config/config').connectors.serviceInsights.spanTypes;

/**
 * createNode()
 * Function to create a graph node and enforce data schema for creating a node
 * @param {object} data
 */
function createNode(data) {
    // Sanity check required properties
    ['id', 'name'].forEach((requiredProperty) => {
        /* istanbul ignore if -- this is to identify misconfiguration during development */
        if (typeof data[requiredProperty] === 'undefined') {
            throw new Error(`Missing required property ${requiredProperty} when calling createNode()`);
        }
    });
    return {
        count: 1,
        ...data
    };
}

/**
 * createLink()
 * Function to create a graph edge and enforce data schema for creating a edge
 * @param {object} data
 */
function createLink(data) {
    // Sanity check required properties
    ['source', 'target'].forEach((requiredProperty) => {
        /* istanbul ignore if -- this is to identify misconfiguration during development */
        if (typeof data[requiredProperty] === 'undefined') {
            throw new Error(`Missing required property ${requiredProperty} when calling createLink()`);
        }
    });
    return {
        isUninstrumented: false,
        count: 1,
        tps: 1,
        ...data
    };
}

/**
 * getNodeNameFromSpan()
 * Gets the display name given a span object
 * @param {object} span - Haystack span object
 */
function getNodeNameFromSpan(span) {
    if (edge && edge.isType(span)) {
        return edge.nodeName(span);
    }
    if (gateway && gateway.isType(span)) {
        return gateway.nodeName(span);
    }
    if (mesh && mesh.isType(span)) {
        return mesh.nodeName(span);
    }
    if (database && database.isType(span)) {
        return database.nodeName(span);
    }
    if (outbound && outbound.isType(span)) {
        return outbound.nodeName(span);
    }
    /* istanbul ignore else -- required configuration */
    if (service) {
        return service.nodeName(span);
    }
    /* istanbul ignore next */
    throw new Error('Missing required configuration: connectors.serviceInsights.spanTypes.service');
}

/**
 * getNodeIdFromSpan()
 * Gets the unique id given a span object, considering when to treat spans as the same node or separate
 * @param {object} span - Haystack span object
 */
function getNodeIdFromSpan(span) {
    if (edge && edge.isType(span)) {
        return edge.nodeId(span);
    }
    if (gateway && gateway.isType(span)) {
        return gateway.nodeId(span);
    }
    if (mesh && mesh.isType(span)) {
        return mesh.nodeId(span);
    }
    if (database && database.isType(span)) {
        return database.nodeId(span);
    }
    if (outbound && outbound.isType(span)) {
        return outbound.nodeId(span);
    }
    /* istanbul ignore else -- required configuration */
    if (service) {
        return service.nodeId(span);
    }
    /* istanbul ignore next */
    throw new Error('Missing required configuration: connectors.serviceInsights.spanTypes.service');
}

/**
 * processNodesAndLinks()
 * Process nodes and links
 * @param {string} serviceName - Name of central dependency
 * @param {Map} nodes - Map of nodes
 * @param {Map} links - Map of links
 * @returns {object}
 */
function processNodesAndLinks(serviceName, nodes, links) {
    // Marks nodes and links with invalid DAG cyces
    const cyclesFound = detectCycles({nodes, links});

    // Store unique traces to calculate how many traces were considered
    const uniqueTraces = new Set();

    // Store count of uninstrumented
    let uninstrumentedCount = 0;

    // Process Links
    links.forEach((link) => {
        // NOTE: here source means source side of the link, not necessarily source end of the graph
        const source = nodes.get(link.source);
        const target = nodes.get(link.target);

        // Process invalid DAG cycle
        if (source.invalidCycleDetected === true && target.invalidCycleDetected === true) {
            link.invalidCycleDetected = true;
            link.invalidCyclePath = source.invalidCyclePath;
        }

        // Node on the source side of the link is not a leaf
        source.isLeaf = false;
    });

    // Process nodes
    nodes.forEach((node) => {
        // Process Central Node
        if (node.serviceName === serviceName && node.type !== type.outbound) {
            node.isCentral = true;
        }

        // Detect unique traces
        node.traceIds.forEach((traceId) => {
            uniqueTraces.add(traceId);
        });

        // Nodes not explicitly not a leaf (see above) are leaves
        if (!(node.isLeaf === false)) {
            node.isLeaf = true;
        }

        // Check if un-instrumented
        if (node.isLeaf && node.type === type.mesh) {
            uninstrumentedCount++;

            // Create uninstrumented node and add it to the map
            const uninstrumentedNode = createNode({
                ...node,
                id: `${node.id}-missing-trace`,
                name: 'Uninstrumented Service',
                serviceName: 'unknown',
                type: type.uninstrumented
            });
            nodes.set(uninstrumentedNode.id, uninstrumentedNode);

            // Create link to uninstrumented node
            const linkId = `${node.id}→${uninstrumentedNode.id}`;
            links.set(
                linkId,
                createLink({
                    source: node.id,
                    target: uninstrumentedNode.id,
                    isUninstrumented: true
                })
            );
        }

        // Check if uninstrumented client span
        if (node.isLeaf && node.type === type.outbound) {
            node.type = type.uninstrumented;
            uninstrumentedCount++;
        }
    });

    // Define map of violations
    const violations = {};

    // Summarize cycle violations
    if (cyclesFound > 0) {
        violations.cycles = cyclesFound;
    }

    // Summarize unique count of uninstrumented dependencies
    if (uninstrumentedCount > 0) {
        violations.uninstrumented = uninstrumentedCount;
    }

    // Summarize if any types of violations found
    const hasViolations = Object.keys(violations).length > 0;

    return {
        violations,
        hasViolations,
        tracesConsidered: uniqueTraces.size
    };
}

/**
 * buildNodes()
 * Builds a map of nodes.
 * @param {Array<span>} spans - Array of fully hydrated Haystack spans
 */
function buildNodes(spans) {
    const nodes = new Map();

    spans.forEach((span) => {
        const nodeId = getNodeIdFromSpan(span);
        const nodeName = getNodeNameFromSpan(span);

        const node = createNode({
            id: nodeId,
            name: nodeName,
            serviceName: span.serviceName,
            duration: span.duration,
            operations: {[`${span.operationName}`]: 1},
            traceIds: [span.traceId]
        });

        if (edge && edge.isType(span)) {
            node.type = type.edge;
        } else if (gateway && gateway.isType(span)) {
            node.type = type.gateway;
        } else if (mesh && mesh.isType(span)) {
            node.type = type.mesh;
        } else if (outbound && outbound.isType(span)) {
            node.type = type.outbound;
        } else if (database && database.isType(span)) {
            node.type = type.database;
            node.databaseType = database.databaseType(span);
        } else {
            node.type = type.service;
        }

        const currentNode = nodes.get(nodeId);

        // If new node, set
        if (!currentNode) {
            nodes.set(node.id, node);
        } else {
            // Else, update operation
            currentNode.operations[span.operationName] = currentNode.operations[span.operationName]
                ? currentNode.operations[span.operationName] + 1
                : 1;
            currentNode.count++;
            currentNode.duration += span.duration;
            currentNode.avgDuration = `${Math.floor(currentNode.duration / currentNode.count / 1000)} ms`;
            currentNode.traceIds.push(span.traceId);
        }
    });
    return nodes;
}

/**
 * buildLinks()
 * Builds a map of links.
 * @param {*} spans
 */
function buildLinks(spans) {
    const linkMap = new Map(); // linkId: link
    const spansById = new Map(); // spanId: span

    spans.forEach((span) => {
        spansById.set(span.spanId, span);
    });

    spans.forEach((span) => {
        const parentSpanId = span.parentSpanId;
        if (parentSpanId) {
            const parentSpan = spansById.get(parentSpanId);
            if (parentSpan) {
                const parentNodeId = getNodeIdFromSpan(parentSpan);
                const childNodeId = getNodeIdFromSpan(span);
                const linkId = `${parentNodeId}→${childNodeId}`;
                if (parentNodeId !== childNodeId) {
                    const currentLink = linkMap.get(linkId);
                    // If link does not exist in map, create it
                    if (!currentLink) {
                        linkMap.set(
                            linkId,
                            createLink({
                                source: parentNodeId,
                                target: childNodeId
                            })
                        );
                    } else {
                        // else, calculate magnitude
                        currentLink.count++;
                        currentLink.tps++;
                    }
                }
            }
        }
    });

    return linkMap;
}

/**
 * extractNodesAndLinks()
 * Given an array of spans and a service name, perform transform to build a nodes + links structure from multiple traces
 * @param {*} spans - Array of fully hydrated span objects related to multiple traces
 * @param {*} serviceName - Service name to search for
 */
const extractNodesAndLinks = ({spans, serviceName}) => {
    // build map of nodes
    const nodes = buildNodes(spans);

    // build map of links
    const links = buildLinks(spans);

    // Process nodes and links for consumption of graphing library
    const summary = processNodesAndLinks(serviceName, nodes, links);

    return {
        summary,
        nodes: [...nodes.values()],
        links: [...links.values()]
    };
};

module.exports = {
    extractNodesAndLinks
};
<commit_msg>database type has precedence over outbound type<commit_after>/* eslint-disable no-param-reassign */
/*
 * Copyright 2019 Expedia Group
 *
 *         Licensed under the Apache License, Version 2.0 (the "License");
 *         you may not use this file except in compliance with the License.
 *         You may obtain a copy of the License at
 *
 *             http://www.apache.org/licenses/LICENSE-2.0
 *
 *         Unless required by applicable law or agreed to in writing, software
 *         distributed under the License is distributed on an "AS IS" BASIS,
 *         WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *         See the License for the specific language governing permissions and
 *         limitations under the License.
 */

const {type} = require('./enums');
const {detectCycles} = require('./detectCycles');
const {edge, gateway, mesh, database, outbound, service} = require('../../config/config').connectors.serviceInsights.spanTypes;

/**
 * createNode()
 * Function to create a graph node and enforce data schema for creating a node
 * @param {object} data
 */
function createNode(data) {
    // Sanity check required properties
    ['id', 'name'].forEach((requiredProperty) => {
        /* istanbul ignore if -- this is to identify misconfiguration during development */
        if (typeof data[requiredProperty] === 'undefined') {
            throw new Error(`Missing required property ${requiredProperty} when calling createNode()`);
        }
    });
    return {
        count: 1,
        ...data
    };
}

/**
 * createLink()
 * Function to create a graph edge and enforce data schema for creating a edge
 * @param {object} data
 */
function createLink(data) {
    // Sanity check required properties
    ['source', 'target'].forEach((requiredProperty) => {
        /* istanbul ignore if -- this is to identify misconfiguration during development */
        if (typeof data[requiredProperty] === 'undefined') {
            throw new Error(`Missing required property ${requiredProperty} when calling createLink()`);
        }
    });
    return {
        isUninstrumented: false,
        count: 1,
        tps: 1,
        ...data
    };
}

/**
 * getNodeNameFromSpan()
 * Gets the display name given a span object
 * @param {object} span - Haystack span object
 */
function getNodeNameFromSpan(span) {
    if (edge && edge.isType(span)) {
        return edge.nodeName(span);
    }
    if (gateway && gateway.isType(span)) {
        return gateway.nodeName(span);
    }
    if (mesh && mesh.isType(span)) {
        return mesh.nodeName(span);
    }
    if (database && database.isType(span)) {
        return database.nodeName(span);
    }
    if (outbound && outbound.isType(span)) {
        return outbound.nodeName(span);
    }
    /* istanbul ignore else -- required configuration */
    if (service) {
        return service.nodeName(span);
    }
    /* istanbul ignore next */
    throw new Error('Missing required configuration: connectors.serviceInsights.spanTypes.service');
}

/**
 * getNodeIdFromSpan()
 * Gets the unique id given a span object, considering when to treat spans as the same node or separate
 * @param {object} span - Haystack span object
 */
function getNodeIdFromSpan(span) {
    if (edge && edge.isType(span)) {
        return edge.nodeId(span);
    }
    if (gateway && gateway.isType(span)) {
        return gateway.nodeId(span);
    }
    if (mesh && mesh.isType(span)) {
        return mesh.nodeId(span);
    }
    if (database && database.isType(span)) {
        return database.nodeId(span);
    }
    if (outbound && outbound.isType(span)) {
        return outbound.nodeId(span);
    }
    /* istanbul ignore else -- required configuration */
    if (service) {
        return service.nodeId(span);
    }
    /* istanbul ignore next */
    throw new Error('Missing required configuration: connectors.serviceInsights.spanTypes.service');
}

/**
 * processNodesAndLinks()
 * Process nodes and links
 * @param {string} serviceName - Name of central dependency
 * @param {Map} nodes - Map of nodes
 * @param {Map} links - Map of links
 * @returns {object}
 */
function processNodesAndLinks(serviceName, nodes, links) {
    // Marks nodes and links with invalid DAG cyces
    const cyclesFound = detectCycles({nodes, links});

    // Store unique traces to calculate how many traces were considered
    const uniqueTraces = new Set();

    // Store count of uninstrumented
    let uninstrumentedCount = 0;

    // Process Links
    links.forEach((link) => {
        // NOTE: here source means source side of the link, not necessarily source end of the graph
        const source = nodes.get(link.source);
        const target = nodes.get(link.target);

        // Process invalid DAG cycle
        if (source.invalidCycleDetected === true && target.invalidCycleDetected === true) {
            link.invalidCycleDetected = true;
            link.invalidCyclePath = source.invalidCyclePath;
        }

        // Node on the source side of the link is not a leaf
        source.isLeaf = false;
    });

    // Process nodes
    nodes.forEach((node) => {
        // Process Central Node
        if (node.serviceName === serviceName && node.type !== type.outbound) {
            node.isCentral = true;
        }

        // Detect unique traces
        node.traceIds.forEach((traceId) => {
            uniqueTraces.add(traceId);
        });

        // Nodes not explicitly not a leaf (see above) are leaves
        if (!(node.isLeaf === false)) {
            node.isLeaf = true;
        }

        // Check if un-instrumented
        if (node.isLeaf && node.type === type.mesh) {
            uninstrumentedCount++;

            // Create uninstrumented node and add it to the map
            const uninstrumentedNode = createNode({
                ...node,
                id: `${node.id}-missing-trace`,
                name: 'Uninstrumented Service',
                serviceName: 'unknown',
                type: type.uninstrumented
            });
            nodes.set(uninstrumentedNode.id, uninstrumentedNode);

            // Create link to uninstrumented node
            const linkId = `${node.id}→${uninstrumentedNode.id}`;
            links.set(
                linkId,
                createLink({
                    source: node.id,
                    target: uninstrumentedNode.id,
                    isUninstrumented: true
                })
            );
        }

        // Check if uninstrumented client span
        if (node.isLeaf && node.type === type.outbound) {
            node.type = type.uninstrumented;
            uninstrumentedCount++;
        }
    });

    // Define map of violations
    const violations = {};

    // Summarize cycle violations
    if (cyclesFound > 0) {
        violations.cycles = cyclesFound;
    }

    // Summarize unique count of uninstrumented dependencies
    if (uninstrumentedCount > 0) {
        violations.uninstrumented = uninstrumentedCount;
    }

    // Summarize if any types of violations found
    const hasViolations = Object.keys(violations).length > 0;

    return {
        violations,
        hasViolations,
        tracesConsidered: uniqueTraces.size
    };
}

/**
 * buildNodes()
 * Builds a map of nodes.
 * @param {Array<span>} spans - Array of fully hydrated Haystack spans
 */
function buildNodes(spans) {
    const nodes = new Map();

    spans.forEach((span) => {
        const nodeId = getNodeIdFromSpan(span);
        const nodeName = getNodeNameFromSpan(span);

        const node = createNode({
            id: nodeId,
            name: nodeName,
            serviceName: span.serviceName,
            duration: span.duration,
            operations: {[`${span.operationName}`]: 1},
            traceIds: [span.traceId]
        });

        if (edge && edge.isType(span)) {
            node.type = type.edge;
        } else if (gateway && gateway.isType(span)) {
            node.type = type.gateway;
        } else if (mesh && mesh.isType(span)) {
            node.type = type.mesh;
        } else if (database && database.isType(span)) {
            node.type = type.database;
            node.databaseType = database.databaseType(span);
        } else if (outbound && outbound.isType(span)) {
            node.type = type.outbound;
        } else {
            node.type = type.service;
        }

        const currentNode = nodes.get(nodeId);

        // If new node, set
        if (!currentNode) {
            nodes.set(node.id, node);
        } else {
            // Else, update operation
            currentNode.operations[span.operationName] = currentNode.operations[span.operationName]
                ? currentNode.operations[span.operationName] + 1
                : 1;
            currentNode.count++;
            currentNode.duration += span.duration;
            currentNode.avgDuration = `${Math.floor(currentNode.duration / currentNode.count / 1000)} ms`;
            currentNode.traceIds.push(span.traceId);
        }
    });
    return nodes;
}

/**
 * buildLinks()
 * Builds a map of links.
 * @param {*} spans
 */
function buildLinks(spans) {
    const linkMap = new Map(); // linkId: link
    const spansById = new Map(); // spanId: span

    spans.forEach((span) => {
        spansById.set(span.spanId, span);
    });

    spans.forEach((span) => {
        const parentSpanId = span.parentSpanId;
        if (parentSpanId) {
            const parentSpan = spansById.get(parentSpanId);
            if (parentSpan) {
                const parentNodeId = getNodeIdFromSpan(parentSpan);
                const childNodeId = getNodeIdFromSpan(span);
                const linkId = `${parentNodeId}→${childNodeId}`;
                if (parentNodeId !== childNodeId) {
                    const currentLink = linkMap.get(linkId);
                    // If link does not exist in map, create it
                    if (!currentLink) {
                        linkMap.set(
                            linkId,
                            createLink({
                                source: parentNodeId,
                                target: childNodeId
                            })
                        );
                    } else {
                        // else, calculate magnitude
                        currentLink.count++;
                        currentLink.tps++;
                    }
                }
            }
        }
    });

    return linkMap;
}

/**
 * extractNodesAndLinks()
 * Given an array of spans and a service name, perform transform to build a nodes + links structure from multiple traces
 * @param {*} spans - Array of fully hydrated span objects related to multiple traces
 * @param {*} serviceName - Service name to search for
 */
const extractNodesAndLinks = ({spans, serviceName}) => {
    // build map of nodes
    const nodes = buildNodes(spans);

    // build map of links
    const links = buildLinks(spans);

    // Process nodes and links for consumption of graphing library
    const summary = processNodesAndLinks(serviceName, nodes, links);

    return {
        summary,
        nodes: [...nodes.values()],
        links: [...links.values()]
    };
};

module.exports = {
    extractNodesAndLinks
};
<|endoftext|>


<commit_before>import _ from 'lodash'
import { EventEmitter } from 'events'
import { setImmediate } from 'timers'
import readyMixin from 'ready-mixin'
import bitcore from 'bitcore'
import p2p from 'bitcore-p2p'
import RpcClient from 'bitcoind-rpc-client'

import config from '../lib/config'
import errors from '../lib/errors'
import logger from '../lib/logger'
import util from '../lib/util'

/**
 * @event Network#block
 * @param {string} hash
 */

/**
 * @event Network#tx
 * @param {string} txid
 */

/**
 * @class Network
 */
export default class Network extends EventEmitter {
  /**
   * @constructor
   */
  constructor () {
    super()

    Promise.all([
      this._initBitcoind(),
      this._initTrustedPeer()
    ])
    .then(() => { this._ready(null) }, (err) => { this._ready(err) })

    this.ready
      .then(() => { logger.info('Network ready ...') })
  }

  /**
   * @return {Promise}
   */
  async _initBitcoind () {
    // create rpc client
    this._bitcoind = new RpcClient({
      host: config.get('bitcoind.rpc.host'),
      port: config.get('bitcoind.rpc.port'),
      user: config.get('bitcoind.rpc.user'),
      pass: config.get('bitcoind.rpc.pass'),
      ssl: config.get('bitcoind.rpc.protocol') === 'https'
    })

    // request info
    let {result} = await this._bitcoind.getInfo()

    // check network
    let bitcoindNetwork = result.testnet ? 'testnet' : 'livenet'
    let chromanodeNetwork = config.get('chromanode.network')
    if (bitcoindNetwork !== chromanodeNetwork &&
        !(bitcoindNetwork === 'livenet' && chromanodeNetwork === 'regtest')) {
      throw new errors.InvalidBitcoindNetwork(bitcoindNetwork, chromanodeNetwork)
    }

    // show info
    logger.info(
      `Bitcoind checked. (version ${result.version}, bestHeight: ${result.blocks}, connections: ${result.connections})`)
  }

  /**
   * @return {Promise}
   */
  _initTrustedPeer () {
    // create trusted peer
    this._peer = new p2p.Peer({
      host: config.get('bitcoind.peer.host'),
      port: config.get('bitcoind.peer.port'),
      network: config.get('chromanode.network')
    })

    setImmediate(::this._peer.connect)

    // inv event
    this._peer.on('inv', (message) => {
      let names = []

      for (let inv of message.inventory) {
        // store inv type name
        names.push(p2p.Inventory.TYPE_NAME[inv.type])

        // store inv if tx type
        if (inv.type === p2p.Inventory.TYPE.TX) {
          this.emit('tx', util.encode(inv.hash))
        }

        // emit block if block type
        if (inv.type === p2p.Inventory.TYPE.BLOCK) {
          this.emit('block', util.encode(inv.hash))
        }
      }

      logger.verbose(
        `Receive inv (${names.join(', ')}) message from peer ${this._peer.host}:${this._peer.port}`)
    })

    // connect event
    this._peer.on('connect', () => {
      logger.info(`Connected to peer ${this._peer.host}:${this._peer.port}`)
    })

    // disconnect event
    this._peer.on('disconnect', () => {
      logger.info(`Disconnected from peer ${this._peer.host}:${this._peer.port}`)
    })

    // ready event
    this._peer.on('ready', () => {
      logger.info(
        `Peer ${this._peer.host}:${this._peer.port} is ready (version: ${this._peer.version}, subversion: ${this._peer.subversion}, bestHeight: ${this._peer.bestHeight})`)
    })

    // waiting peer ready
    return new Promise((resolve) => {
      this._peer.once('ready', resolve)
    })
  }

  /**
   * @return {Promise<Object>}
   */
  async getBitcoindInfo () {
    let {result} = await this._bitcoind.getInfo()
    return result
  }

  /**
   * @return {Promise<number>}
   */
  async getBlockCount () {
    let {result} = await this._bitcoind.getBlockCount()
    return result
  }

  /**
   * @param {number} height
   * @return {Promise<string>}
   */
  async getBlockHash (height) {
    let {result} = await this._bitcoind.getBlockHash(height)
    return result
  }

  /**
   * @param {(number|string)} hash
   * @return {Promise<bitcore.Block>}
   */
  async getBlock (hash) {
    if (_.isNumber(hash)) {
      hash = await this.getBlockHash(hash)
    }

    let {result} = await this._bitcoind.getBlock(hash, false)
    let rawBlock = new Buffer(result, 'hex')
    return new bitcore.Block(rawBlock)
  }

  /**
   * @return {Promise<{hash: string, height: number}>}
   */
  async getLatest () {
    let height = await this.getBlockCount()
    let hash = await this.getBlockHash(height)
    return {hash: hash, height: height}
  }

  /**
   * @param {string} txid
   * @return {Promise<bitcore.Transaction>}
   */
  async getTx (txid) {
    let {result} = await this._bitcoind.getRawTransaction(txid)
    let rawtx = new Buffer(result, 'hex')
    return new bitcore.Transaction(rawtx)
  }

  /**
   * @param {string} rawtx
   * @return {Promise}
   */
  async sendTx (rawtx) {
    await this._bitcoind.sendRawTransaction(rawtx)
  }

  /**
   * @return {Promise<string[]>}
   */
  getMempoolTxs () {
    let {result} = this._bitcoind.getRawMemPool()
    return result
  }
}

readyMixin(Network.prototype)
<commit_msg>fix getMempoolTxs in master network<commit_after>import _ from 'lodash'
import { EventEmitter } from 'events'
import { setImmediate } from 'timers'
import readyMixin from 'ready-mixin'
import bitcore from 'bitcore'
import p2p from 'bitcore-p2p'
import RpcClient from 'bitcoind-rpc-client'

import config from '../lib/config'
import errors from '../lib/errors'
import logger from '../lib/logger'
import util from '../lib/util'

/**
 * @event Network#block
 * @param {string} hash
 */

/**
 * @event Network#tx
 * @param {string} txid
 */

/**
 * @class Network
 */
export default class Network extends EventEmitter {
  /**
   * @constructor
   */
  constructor () {
    super()

    Promise.all([
      this._initBitcoind(),
      this._initTrustedPeer()
    ])
    .then(() => { this._ready(null) }, (err) => { this._ready(err) })

    this.ready
      .then(() => { logger.info('Network ready ...') })
  }

  /**
   * @return {Promise}
   */
  async _initBitcoind () {
    // create rpc client
    this._bitcoind = new RpcClient({
      host: config.get('bitcoind.rpc.host'),
      port: config.get('bitcoind.rpc.port'),
      user: config.get('bitcoind.rpc.user'),
      pass: config.get('bitcoind.rpc.pass'),
      ssl: config.get('bitcoind.rpc.protocol') === 'https'
    })

    // request info
    let {result} = await this._bitcoind.getInfo()

    // check network
    let bitcoindNetwork = result.testnet ? 'testnet' : 'livenet'
    let chromanodeNetwork = config.get('chromanode.network')
    if (bitcoindNetwork !== chromanodeNetwork &&
        !(bitcoindNetwork === 'livenet' && chromanodeNetwork === 'regtest')) {
      throw new errors.InvalidBitcoindNetwork(bitcoindNetwork, chromanodeNetwork)
    }

    // show info
    logger.info(
      `Bitcoind checked. (version ${result.version}, bestHeight: ${result.blocks}, connections: ${result.connections})`)
  }

  /**
   * @return {Promise}
   */
  _initTrustedPeer () {
    // create trusted peer
    this._peer = new p2p.Peer({
      host: config.get('bitcoind.peer.host'),
      port: config.get('bitcoind.peer.port'),
      network: config.get('chromanode.network')
    })

    setImmediate(::this._peer.connect)

    // inv event
    this._peer.on('inv', (message) => {
      let names = []

      for (let inv of message.inventory) {
        // store inv type name
        names.push(p2p.Inventory.TYPE_NAME[inv.type])

        // store inv if tx type
        if (inv.type === p2p.Inventory.TYPE.TX) {
          this.emit('tx', util.encode(inv.hash))
        }

        // emit block if block type
        if (inv.type === p2p.Inventory.TYPE.BLOCK) {
          this.emit('block', util.encode(inv.hash))
        }
      }

      logger.verbose(
        `Receive inv (${names.join(', ')}) message from peer ${this._peer.host}:${this._peer.port}`)
    })

    // connect event
    this._peer.on('connect', () => {
      logger.info(`Connected to peer ${this._peer.host}:${this._peer.port}`)
    })

    // disconnect event
    this._peer.on('disconnect', () => {
      logger.info(`Disconnected from peer ${this._peer.host}:${this._peer.port}`)
    })

    // ready event
    this._peer.on('ready', () => {
      logger.info(
        `Peer ${this._peer.host}:${this._peer.port} is ready (version: ${this._peer.version}, subversion: ${this._peer.subversion}, bestHeight: ${this._peer.bestHeight})`)
    })

    // waiting peer ready
    return new Promise((resolve) => {
      this._peer.once('ready', resolve)
    })
  }

  /**
   * @return {Promise<Object>}
   */
  async getBitcoindInfo () {
    let {result} = await this._bitcoind.getInfo()
    return result
  }

  /**
   * @return {Promise<number>}
   */
  async getBlockCount () {
    let {result} = await this._bitcoind.getBlockCount()
    return result
  }

  /**
   * @param {number} height
   * @return {Promise<string>}
   */
  async getBlockHash (height) {
    let {result} = await this._bitcoind.getBlockHash(height)
    return result
  }

  /**
   * @param {(number|string)} hash
   * @return {Promise<bitcore.Block>}
   */
  async getBlock (hash) {
    if (_.isNumber(hash)) {
      hash = await this.getBlockHash(hash)
    }

    let {result} = await this._bitcoind.getBlock(hash, false)
    let rawBlock = new Buffer(result, 'hex')
    return new bitcore.Block(rawBlock)
  }

  /**
   * @return {Promise<{hash: string, height: number}>}
   */
  async getLatest () {
    let height = await this.getBlockCount()
    let hash = await this.getBlockHash(height)
    return {hash: hash, height: height}
  }

  /**
   * @param {string} txid
   * @return {Promise<bitcore.Transaction>}
   */
  async getTx (txid) {
    let {result} = await this._bitcoind.getRawTransaction(txid)
    let rawtx = new Buffer(result, 'hex')
    return new bitcore.Transaction(rawtx)
  }

  /**
   * @param {string} rawtx
   * @return {Promise}
   */
  async sendTx (rawtx) {
    await this._bitcoind.sendRawTransaction(rawtx)
  }

  /**
   * @return {Promise<string[]>}
   */
  async getMempoolTxs () {
    let {result} = await this._bitcoind.getRawMemPool()
    return result
  }
}

readyMixin(Network.prototype)
<|endoftext|>


<commit_before>import Ember from 'ember';
import loadAll from 'ember-osf/utils/load-relationship';
import config from 'ember-get-config';
import Analytics from '../mixins/analytics';
import permissions from 'ember-osf/const/permissions';

/**
 * Takes an object with query parameter name as the key and value, or [value, maxLength] as the values.
 *
 * @param queryParams {!object}
 * @param queryParams.key {!array|!string}
 * @param queryParams.key[0] {!string}
 * @param queryParams.key[1] {int}
 * @returns {string}
 */
function queryStringify(queryParams) {
    const query = [];

    // TODO set up ember to transpile Object.entries
    for (const param in queryParams) {
        let value = queryParams[param];
        let maxLength = null;

        if (Array.isArray(value)) {
            maxLength = value[1];
            value = value[0];
        }

        if (!value)
            continue;

        value = encodeURIComponent(value);

        if (maxLength)
            value = value.slice(0, maxLength);

        query.push(`${param}=${value}`);
    }

    return query.join('&');
}

export default Ember.Controller.extend(Analytics, {
    theme: Ember.inject.service(),
    fullScreenMFR: false,
    expandedAuthors: true,
    showLicenseText: false,
    isAdmin: Ember.computed('node', function() {
        // True if the current user has admin permissions for the node that contains the preprint
        return (this.get('node.currentUserPermissions') || []).includes(permissions.ADMIN);
    }),
    twitterHref: Ember.computed('node', function() {
        const queryParams = {
            url: window.location.href,
            text: this.get('node.title'),
            via: 'OSFramework'
        };

        return `https://twitter.com/intent/tweet?${queryStringify(queryParams)}`;
    }),
    /* TODO: Update this with new Facebook Share Dialog, but an App ID is required
     * https://developers.facebook.com/docs/sharing/reference/share-dialog
     */
    facebookHref: Ember.computed('model', function() {
        const queryParams = {
            app_id: config.FB_APP_ID,
            display: 'popup',
            href: window.location.href,
            redirect_uri: window.location.href
        };

        return `https://www.facebook.com/dialog/share?${queryStringify(queryParams)}`;
    }),
    // https://developer.linkedin.com/docs/share-on-linkedin
    linkedinHref: Ember.computed('node', function() {
        const queryParams = {
            url: [window.location.href, 1024],          // required
            mini: ['true', 4],                          // required
            title: [this.get('node.title'), 200],      // optional
            summary: [this.get('node.description'), 256], // optional
            source: ['Open Science Framework', 200]     // optional
        };

        return `https://www.linkedin.com/shareArticle?${queryStringify(queryParams)}`;
    }),
    emailHref: Ember.computed('node', function() {
        const queryParams = {
            subject: this.get('node.title'),
            body: window.location.href
        };

        return `mailto:?${queryStringify(queryParams)}`;
    }),
    // The currently selected file (defaults to primary)
    activeFile: null,

    disciplineReduced: Ember.computed('model.subjects', function() {
        // Preprint disciplines are displayed in collapsed form on content page
        return this.get('model.subjects').reduce((acc, val) => acc.concat(val), []).uniqBy('id');
    }),

    hasTag: Ember.computed('node.tags', function() {
        return (this.get('node.tags') || []).length;
    }),

    getAuthors: Ember.observer('node', function() {
        // Cannot be called until node has loaded!
        const node = this.get('node');
        if (!node) return [];

        const contributors = Ember.A();
        loadAll(node, 'contributors', contributors).then(() =>
            this.set('authors', contributors)
        );
    }),

    doiUrl: Ember.computed('model.doi', function() {
        return `https://dx.doi.org/${this.get('model.doi')}`;
    }),

    fullLicenseText: Ember.computed('model.license', function() {
        let text = this.get('model.license.text');
        if (text) {
            text = text.replace(/({{year}})/g, this.get('model.licenseRecord').year || '');
            text = text.replace(/({{copyrightHolders}})/g, this.get('model.licenseRecord').copyright_holders ? this.get('model.licenseRecord').copyright_holders.join(',') : false || '');
        }
        return text;
    }),

    actions: {
        toggleLicenseText() {
            const licenseState = this.toggleProperty('showLicenseText') ? 'Expand' : 'Contract';
            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'button',
                    action: 'click',
                    label: `Preprints - Content - License ${licenseState}`
                });
        },
        expandMFR() {
            // State of fullScreenMFR before the transition (what the user perceives as the action)
            const beforeState = this.toggleProperty('fullScreenMFR') ? 'Expand' : 'Contract';

            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'button',
                    action: 'click',
                    label: `Preprints - Content - MFR ${beforeState}`
                });
        },
        // Unused
        expandAuthors() {
            this.toggleProperty('expandedAuthors');
        },
        // Metrics are handled in the component
        chooseFile(fileItem) {
            this.set('activeFile', fileItem);
        },
        shareLink(href, network, action) {
            const metrics = Ember.get(this, 'metrics');

            if (network.includes('email')) {
                metrics.trackEvent({
                    category: 'link',
                    action,
                    label: `Preprints - Content - Email ${window.location.href}`
                });
            } else {
                window.open(href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=600,height=400');
                // TODO submit PR to ember-metrics for a trackSocial function for Google Analytics. For now, we'll use trackEvent.
                metrics.trackEvent({
                    category: network,
                    action,
                    label: `Preprints - Content - ${window.location.href}`
                });
            }

            return false;
        }
    },
});
<commit_msg>Fix special characters in the email title before showing up in the mailto pop up window EOSF-202<commit_after>import Ember from 'ember';
import loadAll from 'ember-osf/utils/load-relationship';
import config from 'ember-get-config';
import Analytics from '../mixins/analytics';
import permissions from 'ember-osf/const/permissions';

/**
 * Takes an object with query parameter name as the key and value, or [value, maxLength] as the values.
 *
 * @param queryParams {!object}
 * @param queryParams.key {!array|!string}
 * @param queryParams.key[0] {!string}
 * @param queryParams.key[1] {int}
 * @returns {string}
 */
function queryStringify(queryParams) {
    const query = [];

    // TODO set up ember to transpile Object.entries
    for (const param in queryParams) {
        let value = queryParams[param];
        let maxLength = null;

        if (Array.isArray(value)) {
            maxLength = value[1];
            value = value[0];
        }

        if (!value)
            continue;

        value = encodeURIComponent(value);

        if (maxLength)
            value = value.slice(0, maxLength);

        query.push(`${param}=${value}`);
    }

    return query.join('&');
}

function fixSpecialChar(inputString){
  console.log(inputString);
  if (inputString != null){
   return inputString.replace(/&/g,"&").replace(/>/g,">").replace(/</g,"<");
  }else{
   return inputString;
 }
}

export default Ember.Controller.extend(Analytics, {
    theme: Ember.inject.service(),
    fullScreenMFR: false,
    expandedAuthors: true,
    showLicenseText: false,
    isAdmin: Ember.computed('node', function() {
        // True if the current user has admin permissions for the node that contains the preprint
        return (this.get('node.currentUserPermissions') || []).includes(permissions.ADMIN);
    }),
    twitterHref: Ember.computed('node', function() {
        const queryParams = {
            url: window.location.href,
            text: this.get('node.title'),
            via: 'OSFramework'
        };

        return `https://twitter.com/intent/tweet?${queryStringify(queryParams)}`;
    }),
    /* TODO: Update this with new Facebook Share Dialog, but an App ID is required
     * https://developers.facebook.com/docs/sharing/reference/share-dialog
     */
    facebookHref: Ember.computed('model', function() {
        const queryParams = {
            app_id: config.FB_APP_ID,
            display: 'popup',
            href: window.location.href,
            redirect_uri: window.location.href
        };

        return `https://www.facebook.com/dialog/share?${queryStringify(queryParams)}`;
    }),
    // https://developer.linkedin.com/docs/share-on-linkedin
    linkedinHref: Ember.computed('node', function() {
        const queryParams = {
            url: [window.location.href, 1024],          // required
            mini: ['true', 4],                          // required
            title: [this.get('node.title'), 200],      // optional
            summary: [this.get('node.description'), 256], // optional
            source: ['Open Science Framework', 200]     // optional
        };

        return `https://www.linkedin.com/shareArticle?${queryStringify(queryParams)}`;
    }),
    emailHref: Ember.computed('node', function() {
        const queryParams = {
            subject: fixSpecialChar(this.get('node.title')),
            body: window.location.href
        };

        return `mailto:?${queryStringify(queryParams)}`;
    }),
    // The currently selected file (defaults to primary)
    activeFile: null,

    disciplineReduced: Ember.computed('model.subjects', function() {
        // Preprint disciplines are displayed in collapsed form on content page
        return this.get('model.subjects').reduce((acc, val) => acc.concat(val), []).uniqBy('id');
    }),

    hasTag: Ember.computed('node.tags', function() {
        return (this.get('node.tags') || []).length;
    }),

    getAuthors: Ember.observer('node', function() {
        // Cannot be called until node has loaded!
        const node = this.get('node');
        if (!node) return [];

        const contributors = Ember.A();
        loadAll(node, 'contributors', contributors).then(() =>
            this.set('authors', contributors)
        );
    }),

    doiUrl: Ember.computed('model.doi', function() {
        return `https://dx.doi.org/${this.get('model.doi')}`;
    }),

    fullLicenseText: Ember.computed('model.license', function() {
        let text = this.get('model.license.text');
        if (text) {
            text = text.replace(/({{year}})/g, this.get('model.licenseRecord').year || '');
            text = text.replace(/({{copyrightHolders}})/g, this.get('model.licenseRecord').copyright_holders ? this.get('model.licenseRecord').copyright_holders.join(',') : false || '');
        }
        return text;
    }),

    actions: {
        toggleLicenseText() {
            const licenseState = this.toggleProperty('showLicenseText') ? 'Expand' : 'Contract';
            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'button',
                    action: 'click',
                    label: `Preprints - Content - License ${licenseState}`
                });
        },
        expandMFR() {
            // State of fullScreenMFR before the transition (what the user perceives as the action)
            const beforeState = this.toggleProperty('fullScreenMFR') ? 'Expand' : 'Contract';

            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'button',
                    action: 'click',
                    label: `Preprints - Content - MFR ${beforeState}`
                });
        },
        // Unused
        expandAuthors() {
            this.toggleProperty('expandedAuthors');
        },
        // Metrics are handled in the component
        chooseFile(fileItem) {
            this.set('activeFile', fileItem);
        },
        shareLink(href, network, action) {
            const metrics = Ember.get(this, 'metrics');

            if (network.includes('email')) {
                metrics.trackEvent({
                    category: 'link',
                    action,
                    label: `Preprints - Content - Email ${window.location.href}`
                });
            } else {
                window.open(href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=600,height=400');
                // TODO submit PR to ember-metrics for a trackSocial function for Google Analytics. For now, we'll use trackEvent.
                metrics.trackEvent({
                    category: network,
                    action,
                    label: `Preprints - Content - ${window.location.href}`
                });
            }

            return false;
        }
    },
});
<|endoftext|>