<commit_before>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * 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 License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var _ = require('lodash');
var sundial = require('sundial');
var util = require('util');

var annotate = require('../eventAnnotations');
var common = require('../commonSimulations');

/**
 * Creates a new "simulator" for Tandem insulin pump data. The simulator has methods for events like
 *
 * cbg(), smbg(), basal(), bolus(), settingsChange(), etc.
 *
 * This simulator exists as an abstraction over the Tidepool APIs. It was written to simplify the conversion
 * of static, "retrospective" audit logs from devices into events understood by the Tidepool platform.
 *
 * On the input side, you have events extracted from a Tandem insulin pump. They should be delivered to the simulator
 * in time order.
 *
 * Once all input activities are collected, the simulator will have accumulated events that the Tidepool Platform
 * will understand into a local "events" array.  You can retrieve the events by calling `getEvents()`
 *
 * @param config
 * @returns {*}
 */
exports.make = function(config){
  if (config == null) {
    config = {};
  }
  var events = [];

  var currBasal = null;
  var currStatus = null;
  var currTimestamp = null;
  var currTempBasal = null;

  function setCurrBasal(basal) {
    currBasal = basal;
  }

  function setCurrStatus(status) {
    currStatus = status;
  }

  function setCurrTempBasal(tempBasal) {
    currTempBasal = tempBasal;
    console.log("currTempBasal", currTempBasal);
  }

  function ensureTimestamp(e){
    if (currTimestamp > e.time) {
      throw new Error(
        util.format('Timestamps must be in order.  Current timestamp was[%s], but got[%j]', currTimestamp, e)
      );
    }
    currTimestamp = e.time;
    return e;
  }

  function simpleSimulate(e) {
    ensureTimestamp(e);
    events.push(e);
  }

  return {
    alarm: function(event) {
      simpleSimulate(event);
    },
    basal: function(event){
      ensureTimestamp(event);
      if (currBasal != null) {
        if(currBasal.deliveryType === 'temp') {
          if (currTempBasal != null) {
            currBasal.percent = currTempBasal.percent;
            currBasal.payload =  { duration: currTempBasal.duration };
          }
        }

        // ignore repeated broadcasts of events at the same time
        // TODO: do a more thorough (deep equality-ish?) check for same-ness
        if (currBasal.time !== event.time) {
          if (!currBasal.isAssigned('duration')) {
            currBasal.with_duration(Date.parse(event.time) - Date.parse(currBasal.time));
          }
          currBasal = currBasal.done();
          event.previous = _.omit(currBasal, 'previous');
          events.push(currBasal);
        }
      }

      setCurrBasal(event);
    },
    finalBasal: function() {
      if (currBasal != null) {
        if (currBasal.deliveryType !== 'scheduled') {
          if (currBasal.deliveryType === 'temp') {
            if (currBasal.isAssigned('suppressed')) {
              if(currTempBasal && currTempBasal.duration && currTempBasal.time_left) {
                currBasal.duration = currTempBasal.duration - currTempBasal.time_left;
                annotate.annotateEvent(event, 'tandem/basal/fabricated-from-time-left');
              }else{
                currBasal.duration = 0;
                annotate.annotateEvent(currBasal, 'basal/unknown-duration');
              }
            }
            currBasal = currBasal.done();
          }
          else {
            if (!currBasal.isAssigned('duration')) {
              currBasal.duration = 0;
              annotate.annotateEvent(currBasal, 'basal/unknown-duration');
              currBasal = currBasal.done();
            }
            else {
              currBasal = currBasal.done();
            }
          }
        }
        else if (config.settings != null) {
          currBasal.with_scheduleName(_.find(
            config.profiles,
            {idp: currBasal.payload.personalProfileIndex}
          ).name);
          currBasal = common.finalScheduledBasal(currBasal, config.settings, 'tandem');
        }
        else {
          currBasal.with_duration(0);
          annotate.annotateEvent(currBasal, 'basal/unknown-duration');
          currBasal = currBasal.done();
        }

        events.push(currBasal);
      }
    },
    tempBasal: function(event) {
      if(event.subType === 'start') {
        setCurrTempBasal(event);
      }
      else if(event.subType === 'stop') {
        //add time_left in case temp basal was terminated early and it's the final basal
        if(currTempBasal != null) {
          var tempBasal = currTempBasal;
          tempBasal.subType = 'stop';
          tempBasal.time_left = event.time_left;
          setCurrTempBasal(tempBasal);
        }
      }
    },
    newDay: function(event) {
      ensureTimestamp(event);
      if (currBasal !== null) {
         if (currStatus !== null && currStatus.status === 'suspended') {
           return;
           // only insert the new day event if the basal rate is not suspended
         }

        if (currBasal.time !== event.time) {
          if (!currBasal.isAssigned('duration')) {
            currBasal.with_duration(Date.parse(event.time) - Date.parse(currBasal.time));
          }
          currBasal = currBasal.done();

          event.set('type', 'basal');
          event.previous = _.omit(currBasal, 'previous');
          event.with_payload(currBasal.payload);
          annotate.annotateEvent(event, 'tandem/basal/fabricated-from-new-day');

          events.push(currBasal);
          setCurrBasal(event);
        }
      }
    },
    bolus: function(event) {
      simpleSimulate(event);
    },
    changeDeviceTime: function(event) {
      simpleSimulate(event);
    },
    changeReservoir: function(event) {
      simpleSimulate(event);
    },
    pumpSettings: function(event) {
      // TODO: once pumpSettings are a corrected
      // (and tideline is able to render them)
      // then start simulating them again for upload
    },
    smbg: function(event) {
      simpleSimulate(event);
    },
    suspend: function(event) {
      simpleSimulate(event);
      setCurrStatus(event);
    },
    resume: function(event) {
      ensureTimestamp(event);
      if (currStatus !== null) {
          event.previous = _.omit(currStatus, 'previous');
      }
      events.push(event.done());
      setCurrStatus(event);
    },
    wizard: function(event) {
      simpleSimulate(event);
    },
    getEvents: function() {
      function filterOutZeroBoluses() {
        return _.filter(events, function(event) {
          // we include the index on all objects to be able to sort accurately in
          // pump-event order despite date & time settings changes, but it's not
          // part of our data model, so we delete before uploading
          delete event.index;
          if (event.type === 'bolus') {
            if (event.normal === 0 && !event.expectedNormal) {
              return false;
            }
            else {
              return true;
            }
          }
          else if (event.type === 'wizard') {
            var bolus = event.bolus || null;
            if (bolus != null) {
              if (bolus.normal === 0 && !bolus.expectedNormal) {
                return false;
              }
              else {
                return true;
              }
            }
          }
          return true;
        });
      }
      // because we have to wait for the *next* basal to determine the duration of a current
      // basal, basal events get added to `events` out of order wrt other events
      // (although within their own type all the basals are always in order)
      // end result: we have to sort events again before we try to upload them
      var orderedEvents = _.sortBy(filterOutZeroBoluses(), function(e) { return e.time; });

      return orderedEvents;
    }
  };
};
<commit_msg>handle temp basal in new-day event<commit_after>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * 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 License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var _ = require('lodash');
var sundial = require('sundial');
var util = require('util');

var annotate = require('../eventAnnotations');
var common = require('../commonSimulations');

/**
 * Creates a new "simulator" for Tandem insulin pump data. The simulator has methods for events like
 *
 * cbg(), smbg(), basal(), bolus(), settingsChange(), etc.
 *
 * This simulator exists as an abstraction over the Tidepool APIs. It was written to simplify the conversion
 * of static, "retrospective" audit logs from devices into events understood by the Tidepool platform.
 *
 * On the input side, you have events extracted from a Tandem insulin pump. They should be delivered to the simulator
 * in time order.
 *
 * Once all input activities are collected, the simulator will have accumulated events that the Tidepool Platform
 * will understand into a local "events" array.  You can retrieve the events by calling `getEvents()`
 *
 * @param config
 * @returns {*}
 */
exports.make = function(config){
  if (config == null) {
    config = {};
  }
  var events = [];

  var currBasal = null;
  var currStatus = null;
  var currTimestamp = null;
  var currTempBasal = null;

  function setCurrBasal(basal) {
    currBasal = basal;
  }

  function setCurrStatus(status) {
    currStatus = status;
  }

  function setCurrTempBasal(tempBasal) {
    currTempBasal = tempBasal;
    console.log("currTempBasal", currTempBasal);
  }

  function ensureTimestamp(e){
    if (currTimestamp > e.time) {
      throw new Error(
        util.format('Timestamps must be in order.  Current timestamp was[%s], but got[%j]', currTimestamp, e)
      );
    }
    currTimestamp = e.time;
    return e;
  }

  function simpleSimulate(e) {
    ensureTimestamp(e);
    events.push(e);
  }

  return {
    alarm: function(event) {
      simpleSimulate(event);
    },
    basal: function(event){
      ensureTimestamp(event);
      if (currBasal != null) {
        if(currBasal.deliveryType === 'temp') {
          if (currTempBasal != null) {
            currBasal.percent = currTempBasal.percent;
            currBasal.payload =  { duration: currTempBasal.duration };
          }
        }

        // ignore repeated broadcasts of events at the same time
        // TODO: do a more thorough (deep equality-ish?) check for same-ness
        if (currBasal.time !== event.time) {
          if (!currBasal.isAssigned('duration')) {
            currBasal.with_duration(Date.parse(event.time) - Date.parse(currBasal.time));
          }
          currBasal = currBasal.done();
          event.previous = _.omit(currBasal, 'previous');
          events.push(currBasal);
        }
      }

      setCurrBasal(event);
    },
    finalBasal: function() {
      if (currBasal != null) {
        if (currBasal.deliveryType !== 'scheduled') {
          if (currBasal.deliveryType === 'temp') {
            if (currBasal.isAssigned('suppressed')) {
              if(currTempBasal && currTempBasal.duration && currTempBasal.time_left) {
                currBasal.duration = currTempBasal.duration - currTempBasal.time_left;
                annotate.annotateEvent(event, 'tandem/basal/fabricated-from-time-left');
              }else{
                currBasal.duration = 0;
                annotate.annotateEvent(currBasal, 'basal/unknown-duration');
              }
            }
            currBasal = currBasal.done();
          }
          else {
            if (!currBasal.isAssigned('duration')) {
              currBasal.duration = 0;
              annotate.annotateEvent(currBasal, 'basal/unknown-duration');
              currBasal = currBasal.done();
            }
            else {
              currBasal = currBasal.done();
            }
          }
        }
        else if (config.settings != null) {
          currBasal.with_scheduleName(_.find(
            config.profiles,
            {idp: currBasal.payload.personalProfileIndex}
          ).name);
          currBasal = common.finalScheduledBasal(currBasal, config.settings, 'tandem');
        }
        else {
          currBasal.with_duration(0);
          annotate.annotateEvent(currBasal, 'basal/unknown-duration');
          currBasal = currBasal.done();
        }

        events.push(currBasal);
      }
    },
    tempBasal: function(event) {
      if(event.subType === 'start') {
        setCurrTempBasal(event);
      }
      else if(event.subType === 'stop') {
        //add time_left in case temp basal was terminated early and it's the final basal
        if(currTempBasal != null) {
          var tempBasal = currTempBasal;
          tempBasal.subType = 'stop';
          tempBasal.time_left = event.time_left;
          setCurrTempBasal(tempBasal);
        }
      }
    },
    newDay: function(event) {
      ensureTimestamp(event);
      if (currBasal !== null) {
         if (currStatus !== null && currStatus.status === 'suspended') {
           return;
           // only insert the new day event if the basal rate is not suspended
         }

        if (currBasal.time !== event.time) {
          if (!currBasal.isAssigned('duration')) {
            currBasal.with_duration(Date.parse(event.time) - Date.parse(currBasal.time));
          }

          if(currBasal.deliveryType === 'temp') {
            if (currTempBasal != null) {
              currBasal.percent = currTempBasal.percent;
              currBasal.payload =  { duration: currTempBasal.duration };
              event.deliveryType = 'temp';
              event.rate = currBasal.rate;
              event.percent = currTempBasal.percent;
            }
          }
          currBasal = currBasal.done();

          event.set('type', 'basal');
          event.previous = _.omit(currBasal, 'previous');
          event.with_payload(currBasal.payload);
          annotate.annotateEvent(event, 'tandem/basal/fabricated-from-new-day');

          events.push(currBasal);
          setCurrBasal(event);
        }
      }
    },
    bolus: function(event) {
      simpleSimulate(event);
    },
    changeDeviceTime: function(event) {
      simpleSimulate(event);
    },
    changeReservoir: function(event) {
      simpleSimulate(event);
    },
    pumpSettings: function(event) {
      // TODO: once pumpSettings are a corrected
      // (and tideline is able to render them)
      // then start simulating them again for upload
    },
    smbg: function(event) {
      simpleSimulate(event);
    },
    suspend: function(event) {
      simpleSimulate(event);
      setCurrStatus(event);
    },
    resume: function(event) {
      ensureTimestamp(event);
      if (currStatus !== null) {
          event.previous = _.omit(currStatus, 'previous');
      }
      events.push(event.done());
      setCurrStatus(event);
    },
    wizard: function(event) {
      simpleSimulate(event);
    },
    getEvents: function() {
      function filterOutZeroBoluses() {
        return _.filter(events, function(event) {
          // we include the index on all objects to be able to sort accurately in
          // pump-event order despite date & time settings changes, but it's not
          // part of our data model, so we delete before uploading
          delete event.index;
          if (event.type === 'bolus') {
            if (event.normal === 0 && !event.expectedNormal) {
              return false;
            }
            else {
              return true;
            }
          }
          else if (event.type === 'wizard') {
            var bolus = event.bolus || null;
            if (bolus != null) {
              if (bolus.normal === 0 && !bolus.expectedNormal) {
                return false;
              }
              else {
                return true;
              }
            }
          }
          return true;
        });
      }
      // because we have to wait for the *next* basal to determine the duration of a current
      // basal, basal events get added to `events` out of order wrt other events
      // (although within their own type all the basals are always in order)
      // end result: we have to sort events again before we try to upload them
      var orderedEvents = _.sortBy(filterOutZeroBoluses(), function(e) { return e.time; });

      return orderedEvents;
    }
  };
};
<|endoftext|>


<commit_before>define('app/models/metric', ['ember'],
    //
    //  Metric model
    //
    //  @returns Class
    //
    function() {

        'use strict';

        return Ember.Object.extend({

            //
            //
            //  Properties
            //
            //

            id: null,
            unit: null,
            name: null,
            target: null,
            maxValue: null,

            //
            //
            //  Initialization
            //
            //

            init: function () {
                this._super();
                info(this.metric_id);
                this.set('id', this.metric_id);
                this.set('alias', this.id);
                this.set('target', this._target);
                this.set('maxValue', this.max_value || 99999999);
            }
        });
    }
);
<commit_msg>Adjust metric model to new specs<commit_after>define('app/models/metric', ['ember'],
    //
    //  Metric model
    //
    //  @returns Class
    //
    function() {

        'use strict';

        return Ember.Object.extend({

            //
            //
            //  Properties
            //
            //

            id: null,
            name: null,
            unit: null,
            maxValue: null,
            minValue: null,
            hasRange: null,

            //
            //
            //  Initialization
            //
            //

            init: function () {
                this._super();
                this.set('maxValue', this.max_value)
                    .set('minValue', this.min_value)
                    .set('hasRange',
                        typeof this.maxValue != 'undefined' &&
                        typeof this.minValue != 'undefined');
                delete this.max_value;
                delete this.min_value;
            }
        });
    }
);
<|endoftext|>


<commit_before>var _ = require('underscore');
var cdb = require('cartodb.js');
var WidgetViewFactory = require('./widgets/widget-view-factory');
var TimeSeriesContentView = require('./widgets/time-series/content-view');
var TorqueTimeSeriesContentView = require('./widgets/time-series/torque-content-view');

module.exports = cdb.core.View.extend({
  className: 'CDB-Dashboard-belowMap',

  initialize: function (options) {
    this._widgetViewFactory = new WidgetViewFactory([
      {
        // same type as below, but also check if the associated layer is a a torque layer
        match: function (widgetModel) {
          if (widgetModel.get('type') === 'time-series') {
            var d = widgetModel.dataviewModel;
            return d && d.layer.get('type') === 'torque' && widgetModel.get('animated') === true;
          }
          return false;
        },
        createContentView: function (widgetModel) {
          return new TorqueTimeSeriesContentView({
            model: widgetModel
          });
        },
        customizeWidgetAttrs: function (attrs) {
          attrs.className += ' CDB-Widget--timeSeries';
          return attrs;
        }
      }, {
        type: 'time-series',
        createContentView: function (widgetModel) {
          return new TimeSeriesContentView({
            model: widgetModel
          });
        },
        customizeWidgetAttrs: function (attrs) {
          attrs.className += ' CDB-Widget--timeSeries';
          return attrs;
        }
      }
    ]);

    this._widgets = options.widgets;
    this._widgets.bind('add', this._maybeRenderWidgetView, this);
    this._widgets.bind('reset', this.render, this);
    this._widgets.bind('add reset remove', this._onWidgetsChange, this);
    this.add_related_model(this._widgets);
  },

  render: function () {
    this.clearSubViews();
    this.$el.empty();

    this._widgets.each(this._maybeRenderWidgetView, this);
    this._toggleVisiblity();

    return this;
  },

  _maybeRenderWidgetView: function (widgetModel) {
    var view = this._widgetViewFactory.createWidgetView(widgetModel);
    if (view) {
      this.addView(view);
      this.$el.append(view.render().el);
    }
  },

  _toggleVisiblity: function () {
    this.$el.toggle(!_.isEmpty(this._subviews));
  },

  _onWidgetsChange: function () {
    this._widgets.each(function (widget) {
      if (widget.get('type') === 'time-series') {
        widget.trigger('forceResize');
      }
    }, this);
    this._toggleVisiblity();
  }

});
<commit_msg>Use TIME_SERIES constant<commit_after>var _ = require('underscore');
var cdb = require('cartodb.js');
var WidgetViewFactory = require('./widgets/widget-view-factory');
var TimeSeriesContentView = require('./widgets/time-series/content-view');
var TorqueTimeSeriesContentView = require('./widgets/time-series/torque-content-view');

var TIME_SERIES_TYPE = 'time-series';

module.exports = cdb.core.View.extend({
  className: 'CDB-Dashboard-belowMap',

  initialize: function (options) {
    this._widgetViewFactory = new WidgetViewFactory([
      {
        // same type as below, but also check if the associated layer is a a torque layer
        match: function (widgetModel) {
          if (widgetModel.get('type') === TIME_SERIES_TYPE) {
            var d = widgetModel.dataviewModel;
            return d && d.layer.get('type') === 'torque' && widgetModel.get('animated') === true;
          }
          return false;
        },
        createContentView: function (widgetModel) {
          return new TorqueTimeSeriesContentView({
            model: widgetModel
          });
        },
        customizeWidgetAttrs: function (attrs) {
          attrs.className += ' CDB-Widget--timeSeries';
          return attrs;
        }
      }, {
        type: TIME_SERIES_TYPE,
        createContentView: function (widgetModel) {
          return new TimeSeriesContentView({
            model: widgetModel
          });
        },
        customizeWidgetAttrs: function (attrs) {
          attrs.className += ' CDB-Widget--timeSeries';
          return attrs;
        }
      }
    ]);

    this._widgets = options.widgets;
    this._widgets.bind('add', this._maybeRenderWidgetView, this);
    this._widgets.bind('reset', this.render, this);
    this._widgets.bind('add reset remove', this._onWidgetsChange, this);
    this.add_related_model(this._widgets);
  },

  render: function () {
    this.clearSubViews();
    this.$el.empty();

    this._widgets.each(this._maybeRenderWidgetView, this);
    this._toggleVisiblity();

    return this;
  },

  _maybeRenderWidgetView: function (widgetModel) {
    var view = this._widgetViewFactory.createWidgetView(widgetModel);
    if (view) {
      this.addView(view);
      this.$el.append(view.render().el);
    }
  },

  _toggleVisiblity: function () {
    this.$el.toggle(!_.isEmpty(this._subviews));
  },

  _onWidgetsChange: function () {
    this._widgets.each(function (widget) {
      if (widget.get('type') === TIME_SERIES_TYPE) {
        widget.trigger('forceResize');
      }
    }, this);
    this._toggleVisiblity();
  }

});
<|endoftext|>


<commit_before>module.exports = {
    exec: async function (msg, args) {
        let inviteData;
        let ownerMember = bot.users.get(config.oid);
        try {
            inviteData = await bot.getInvite(args, true);
        } catch (err) {
            return msg.channel.createMessage("I cannot get the invite data? Maybe I've been banné from there or it's revoked?");
        }
        let fields = [{
            name: `Invite is for ${inviteData.channel.type == 0 ? "text" : "voice"} channel #${inviteData.channel.name}`,
            value: `The channel ID is ${inviteData.channel.id} and the channel mention is <#${inviteData.channel.id}>`,
            inline: true
        }, {
            name: `The guild ID is ${inviteData.guild.id}`,
            value: `You can also click [here](https://discord.gg/${inviteData.code}) to join the server.\nNote: The invite link placement is **NOT** for advertisement purposes. I, ${ownerMember ? ownerMember.username : (await bot.getUserWithoutRESTMode(config.oid)).username}, am not responsible for any advertisement problems caused by abuse of this command. I can log you at any time.`,
            inline: true
        }];
        if (inviteData.guild.textChannelCount && inviteData.guild.voiceChannelCount) {
            fields.push({
                name: "More information",
                value: `In the server, there are totally ${inviteData.guild.textChannelCount + inviteData.guild.voiceChannelCount} channels (${inviteData.guild.textChannelCount} text, ${inviteData.guild.voiceChannelCount} voice). There is approximately ${inviteData.memberCount} members, from which ${inviteData.presenceCount} are online.`
            });
        }
        msg.channel.createMessage({
            embed: {
                author: {
                    name: `Inspected invite data from the guild ${inviteData.guild.name}`,
                    icon_url: inviteData.guild.icon ? `https://cdn.discordapp.com/icons/${inviteData.guild.id}/${inviteData.guild.icon}.webp` : ""
                },
                description: "These information might change at any time.",
                fields,
                footer: inviteData.inviter ? {
                    text: `Invite made by ${inviteData.inviter.username}#${inviteData.inviter.discriminator}`,
                    icon_url: inviteData.inviter.staticAvatarURL
                } : undefined,
                color: 0x008800
            }
        });
    },
    isCmd: true,
    display: true,
    category: 1,
    description: "~~Spy~~ Get information on the invites",
    args: "<invite code>",
    aliases: [
        "iinspector"
    ]
};<commit_msg>the channel count is gone for good<commit_after>module.exports = {
    exec: async function (msg, args) {
        let inviteData;
        let ownerMember = bot.users.get(config.oid);
        try {
            inviteData = await bot.getInvite(args, true);
        } catch (err) {
            return msg.channel.createMessage("I cannot get the invite data? Maybe I've been banné from there or it's revoked?");
        }
        let fields = [{
            name: `Invite is for ${inviteData.channel.type == 0 ? "text" : "voice"} channel #${inviteData.channel.name}`,
            value: `The channel ID is ${inviteData.channel.id} and the channel mention is <#${inviteData.channel.id}>`,
            inline: true
        }, {
            name: `The guild ID is ${inviteData.guild.id}`,
            value: `You can also click [here](https://discord.gg/${inviteData.code}) to join the server.\nNote: The invite link placement is **NOT** for advertisement purposes. I, ${ownerMember ? ownerMember.username : (await bot.getUserWithoutRESTMode(config.oid)).username}, am not responsible for any advertisement problems caused by abuse of this command.`,
            inline: true
        }];
        if (inviteData.memberCount && inviteData.presenceCount) {
            fields.push({
                name: "More information",
                value: `There is approximately ${inviteData.memberCount} members, from which ${inviteData.presenceCount} are online.`
            });
        }
        msg.channel.createMessage({
            embed: {
                author: {
                    name: `Inspected invite data from the guild ${inviteData.guild.name}`,
                    icon_url: inviteData.guild.icon ? `https://cdn.discordapp.com/icons/${inviteData.guild.id}/${inviteData.guild.icon}.webp` : ""
                },
                description: "These information might change at any time.",
                fields,
                footer: inviteData.inviter ? {
                    text: `Invite made by ${inviteData.inviter.username}#${inviteData.inviter.discriminator}`,
                    icon_url: inviteData.inviter.staticAvatarURL
                } : undefined,
                color: 0x008800
            }
        });
    },
    isCmd: true,
    display: true,
    category: 1,
    description: "~~Spy~~ Get information on the invites",
    args: "<invite code>",
    aliases: [
        "iinspector"
    ]
};<|endoftext|>


<commit_before>(function() {
    var Ext = window.Ext4 || window.Ext;

    /**
     * Iteration Tracking Board App
     * The Iteration Tracking Board can be used to visualize and manage your User Stories and Defects within an Iteration.
     */
    Ext.define('Rally.apps.iterationtrackingboard.IterationTrackingBoardApp', {
        extend: 'Rally.app.TimeboxScopedApp',
        requires: [
            'Rally.data.ModelFactory',
            'Rally.ui.gridboard.GridBoard',
            'Rally.ui.gridboard.plugin.GridBoardAddNew',
            'Rally.ui.gridboard.plugin.GridBoardOwnerFilter',
            'Rally.ui.gridboard.plugin.GridBoardFilterInfo',
            'Rally.ui.gridboard.plugin.GridBoardArtifactTypeChooser',
            'Rally.ui.cardboard.plugin.ColumnPolicy',
            'Rally.ui.gridboard.plugin.GridBoardFilterInfo'
        ],
        mixins: ['Rally.app.CardFieldSelectable'],
        componentCls: 'iterationtrackingboard',
        alias: 'widget.rallyiterationtrackingboard',

        settingsScope: 'project',
        scopeType: 'iteration',

        config: {
            defaultSettings: {
                showCardAge: true,
                cardAgeThreshold: 3
            }
        },

        onScopeChange: function(scope) {
            this.remove('gridBoard');
            this._loadModels();
        },

        getSettingsFields: function () {
            var fields = this.callParent(arguments);

            if (!this.isShowingBlankSlate()) {
                this.appendCardFieldPickerSetting(fields);
            }

            if(this.showCardAgeEnabled)  {
                fields.push({
                    type: 'cardage',
                    config: {
                        margin: '0 0 0 80'
                    }
                });
            }

            return fields;
        },

        launch: function() {
            this.showFieldPicker = this.getContext().isFeatureEnabled('SHOW_FIELD_PICKER_IN_ITERATION_BOARD_SETTINGS');
            this.showCardAgeEnabled = this.getContext().isFeatureEnabled('SHOW_CARD_AGE_IN_ITERATION_BOARD_SETTINGS');
            this.callParent(arguments);
        },

        _addGridBoard: function() {
            var plugins = [
                {
                    ptype: 'rallygridboardfilterinfo',
                    isGloballyScoped: Ext.isEmpty(this.getSetting('project')) ? true : false,
                    stateId: 'iteration-tracking-owner-filter-' + this.getAppId()
                },
                'rallygridboardaddnew',
                'rallygridboardownerfilter'
            ];

            if (this.getContext().isFeatureEnabled('SHOW_ARTIFACT_CHOOSER_ON_ITERATION_BOARDS')) {
                plugins.splice(2, 0, {
                    ptype: 'rallygridboardartifacttypechooser',
                    artifactTypePreferenceKey: 'artifact-types',
                    showAgreements: true
                });
            }

            this.gridboard = this.add({
                itemId: 'gridBoard',
                xtype: 'rallygridboard',
                context: this.getContext(),
                enableToggle: this.getContext().isFeatureEnabled('ITERATION_TRACKING_BOARD_GRID_TOGGLE'),
                plugins: plugins,
                modelNames: this.modelNames,
                cardBoardConfig: {
                    columnConfig: {
                        additionalFetchFields: ['PortfolioItem'],
                        plugins: [{
                            ptype: 'rallycolumnpolicy',
                            app: this
                        }]
                    },
                    cardConfig: {
                        fields: this.getCardFieldNames(['Parent', 'Tasks', 'Defects', 'Discussion', 'PlanEstimate']),
                        showAge: this.getSetting('showCardAge') ? this.getSetting('cardAgeThreshold') : -1
                    },
                    listeners: {
                        filter: this._onBoardFilter,
                        filtercomplete: this._onBoardFilterComplete
                    }
                },
                listeners: {
                    load: this._onLoad,
                    toggle: this._publishContentUpdated,
                    recordupdate: this._publishContentUpdatedNoDashboardLayout,
                    recordcreate: this._publishContentUpdatedNoDashboardLayout,
                    scope: this
                }
            });
        },

        _loadModels: function() {
            Rally.data.ModelFactory.getModels({
                types: ['User Story', 'Defect', 'Defect Suite', 'Test Set'],
                context: this.getContext().getDataContext(),
                success: function(models) {
                    this.modelNames = Ext.Object.getKeys(models);
                    this._addGridBoard();
                },
                scope: this
            });
        },

        _onLoad: function() {
            this._publishContentUpdated();
            if (Rally.BrowserTest) {
                Rally.BrowserTest.publishComponentReady(this);
            }
        },

        _onBoardFilter: function() {
           this.setLoading(true);
        },

        _onBoardFilterComplete: function() {
           this.setLoading(false);
        },

        _publishContentUpdated: function() {
            this.fireEvent('contentupdated');
        },

        _publishContentUpdatedNoDashboardLayout: function() {
            this.fireEvent('contentupdated', {dashboardLayout: false});
        },

        _getAddNewParams: function() {
            return this.gridboard.addNewPlugin._getAddNewParams();
        },

        _onAddNewBeforeCreate: function(addNew, record, params) {
            this.gridboard.addNewPlugin._onAddNewBeforeCreate(addNew, record, params);
        },

        _onAddNewBeforeEditorShow: function(addNew, params) {
            params.Iteration = this.getIterationRef() || 'u';
            params.Release = 'u';
            Ext.apply(params, this._getAddNewParams());
        },

        _onAddNewCreate: function(addNew, record) {
            this.gridboard.addNewPlugin._onAddNewBeforeCreate(addNew, record);
        }
    });
})();
<commit_msg>DE17899 - Do not display card field age on tracking board when setting toggle is disabled.<commit_after>(function() {
    var Ext = window.Ext4 || window.Ext;

    /**
     * Iteration Tracking Board App
     * The Iteration Tracking Board can be used to visualize and manage your User Stories and Defects within an Iteration.
     */
    Ext.define('Rally.apps.iterationtrackingboard.IterationTrackingBoardApp', {
        extend: 'Rally.app.TimeboxScopedApp',
        requires: [
            'Rally.data.ModelFactory',
            'Rally.ui.gridboard.GridBoard',
            'Rally.ui.gridboard.plugin.GridBoardAddNew',
            'Rally.ui.gridboard.plugin.GridBoardOwnerFilter',
            'Rally.ui.gridboard.plugin.GridBoardFilterInfo',
            'Rally.ui.gridboard.plugin.GridBoardArtifactTypeChooser',
            'Rally.ui.cardboard.plugin.ColumnPolicy',
            'Rally.ui.gridboard.plugin.GridBoardFilterInfo'
        ],
        mixins: ['Rally.app.CardFieldSelectable'],
        componentCls: 'iterationtrackingboard',
        alias: 'widget.rallyiterationtrackingboard',

        settingsScope: 'project',
        scopeType: 'iteration',

        config: {
            defaultSettings: {
                showCardAge: true,
                cardAgeThreshold: 3
            }
        },

        onScopeChange: function(scope) {
            this.remove('gridBoard');
            this._loadModels();
        },

        getSettingsFields: function () {
            var fields = this.callParent(arguments);

            if (!this.isShowingBlankSlate()) {
                this.appendCardFieldPickerSetting(fields);
            }

            if(this.showCardAgeEnabled)  {
                fields.push({
                    type: 'cardage',
                    config: {
                        margin: '0 0 0 80'
                    }
                });
            }

            return fields;
        },

        launch: function() {
            this.showFieldPicker = this.getContext().isFeatureEnabled('SHOW_FIELD_PICKER_IN_ITERATION_BOARD_SETTINGS');
            this.showCardAgeEnabled = this.getContext().isFeatureEnabled('SHOW_CARD_AGE_IN_ITERATION_BOARD_SETTINGS');
            this.callParent(arguments);
        },

        _addGridBoard: function() {
            var plugins = [
                {
                    ptype: 'rallygridboardfilterinfo',
                    isGloballyScoped: Ext.isEmpty(this.getSetting('project')) ? true : false,
                    stateId: 'iteration-tracking-owner-filter-' + this.getAppId()
                },
                'rallygridboardaddnew',
                'rallygridboardownerfilter'
            ];

            if (this.getContext().isFeatureEnabled('SHOW_ARTIFACT_CHOOSER_ON_ITERATION_BOARDS')) {
                plugins.splice(2, 0, {
                    ptype: 'rallygridboardartifacttypechooser',
                    artifactTypePreferenceKey: 'artifact-types',
                    showAgreements: true
                });
            }

            this.gridboard = this.add({
                itemId: 'gridBoard',
                xtype: 'rallygridboard',
                context: this.getContext(),
                enableToggle: this.getContext().isFeatureEnabled('ITERATION_TRACKING_BOARD_GRID_TOGGLE'),
                plugins: plugins,
                modelNames: this.modelNames,
                cardBoardConfig: {
                    columnConfig: {
                        additionalFetchFields: ['PortfolioItem'],
                        plugins: [{
                            ptype: 'rallycolumnpolicy',
                            app: this
                        }]
                    },
                    cardConfig: {
                        fields: this.getCardFieldNames(['Parent', 'Tasks', 'Defects', 'Discussion', 'PlanEstimate']),
                        showAge: (this.getSetting('showCardAge') && this.showCardAgeEnabled) ? this.getSetting('cardAgeThreshold') : -1
                    },
                    listeners: {
                        filter: this._onBoardFilter,
                        filtercomplete: this._onBoardFilterComplete
                    }
                },
                listeners: {
                    load: this._onLoad,
                    toggle: this._publishContentUpdated,
                    recordupdate: this._publishContentUpdatedNoDashboardLayout,
                    recordcreate: this._publishContentUpdatedNoDashboardLayout,
                    scope: this
                }
            });
        },

        _loadModels: function() {
            Rally.data.ModelFactory.getModels({
                types: ['User Story', 'Defect', 'Defect Suite', 'Test Set'],
                context: this.getContext().getDataContext(),
                success: function(models) {
                    this.modelNames = Ext.Object.getKeys(models);
                    this._addGridBoard();
                },
                scope: this
            });
        },

        _onLoad: function() {
            this._publishContentUpdated();
            if (Rally.BrowserTest) {
                Rally.BrowserTest.publishComponentReady(this);
            }
        },

        _onBoardFilter: function() {
           this.setLoading(true);
        },

        _onBoardFilterComplete: function() {
           this.setLoading(false);
        },

        _publishContentUpdated: function() {
            this.fireEvent('contentupdated');
        },

        _publishContentUpdatedNoDashboardLayout: function() {
            this.fireEvent('contentupdated', {dashboardLayout: false});
        },

        _getAddNewParams: function() {
            return this.gridboard.addNewPlugin._getAddNewParams();
        },

        _onAddNewBeforeCreate: function(addNew, record, params) {
            this.gridboard.addNewPlugin._onAddNewBeforeCreate(addNew, record, params);
        },

        _onAddNewBeforeEditorShow: function(addNew, params) {
            params.Iteration = this.getIterationRef() || 'u';
            params.Release = 'u';
            Ext.apply(params, this._getAddNewParams());
        },

        _onAddNewCreate: function(addNew, record) {
            this.gridboard.addNewPlugin._onAddNewBeforeCreate(addNew, record);
        }
    });
})();
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

// Provides control sap.ui.commons.FormattedTextView.
sap.ui.define(['jquery.sap.global', './library', 'sap/ui/core/Control'],
	function (jQuery, library, Control) {
		"use strict";

		/**
		 * Constructor for a new FormattedTextView.
		 *
		 * @param {string} [sId] ID for the new control, generated automatically if no ID is given
		 * @param {object} [mSettings] Initial settings for the new control
		 *
		 * @class
		 * The FormattedTextView control allows the usage of a limited set of HTML tags for display.
		 * @extends sap.ui.core.Control
		 * @version ${version}
		 *
		 * @constructor
		 * @public
		 * @since 1.9.0
		 * @alias sap.ui.commons.FormattedTextView
		 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
		 */
		var FormattedTextView = Control.extend("sap.ui.commons.FormattedTextView", /** @lends sap.ui.commons.FormattedTextView.prototype */ {
			metadata: {

				library: "sap.ui.commons",
				properties: {
					/**
					 * The ARIA role for the control.
					 */
					accessibleRole: {
						type: "sap.ui.core.AccessibleRole",
						group: "Accessibility",
						defaultValue: sap.ui.core.AccessibleRole.Document
					},

					/**
					 * Determines text with placeholders.
					 */
					htmlText: {type: "string", group: "Misc", defaultValue: ""}
				},
				aggregations: {

					/**
					 * Array of controls that should be replaced within htmlText.
					 */
					controls: {type: "sap.ui.core.Control", multiple: true, singularName: "control"}
				}
			}
		});

		/**
		 * Initialization hook for the FormattedTextView, which creates a list of rules with allowed tags and attributes.
		 */
		FormattedTextView.prototype.init = function () {

			/*
			 * these are the rules for the FormattedTextView
			 */
			this._renderingRules = {};

			// rules for the allowed attributes
			this._renderingRules.ATTRIBS = {
				'span::class': 1,
				'div::class': 1,
				'div::id': 1,
				'span::id': 1,
				'embed::data-index': 1
			};

			// rules for the allowed tags
			this._renderingRules.ELEMENTS = {
				// Text Module Tags
				'abbr': 1,
				'acronym': 1,
				'address': 1,
				'blockquote': 1,
				'br': 1,
				'cite': 1,
				'code': 1,
				'dfn': 1,
				'div': 1,
				'em': 1,
				'h1': 1,
				'h2': 1,
				'h3': 1,
				'h4': 1,
				'h5': 1,
				'h6': 1,
				'kbd': 1,
				'p': 1,
				'pre': 1,
				'q': 1,
				'samp': 1,
				'strong': 1,
				'span': 1,
				'var': 1,

				// List Module Tags
				'dl': 1,
				'dt': 1,
				'dd': 1,
				'ol': 1,
				'ul': 1,
				'li': 1,

				// Special Tags
				// this is the placeholder for the controls
				'embed': 1

				// 'a' : 1, currently used via commons.Link Control
				// 'img' : 1, currently used via commons.Image Control
			};
		};

		/**
		 * Destroys the instance of the control.
		 * Called by Element#destroy().
		 */
		FormattedTextView.prototype.exit = function () {
			delete this._renderingRules;
		};

		/**
		 * Indicates whether the FormattedTextView contains other controls.
		 * @returns {boolean}
		 * @public
		 */
		FormattedTextView.prototype.hasControls = function () {
			var aControls = this.getAggregation("controls");
			return aControls && aControls.length > 0;
		};

		/**
		 * Sanitizes attributes on an HTML tag.
		 *
		 * @param {string} tagName An HTML tag name in lowercase
		 * @param {array} attribs An array of alternating names and values
		 * @return {array} The sanitized attributes as a list of alternating names and values. Null value means to omit the attribute
		 * @private
		 */
		var fnSanitizeAttribs = function (tagName, attribs) {

			var intPattern = /^[0-9]*$/;
			for (var i = 0; i < attribs.length; i += 2) {
				// attribs[i] is the name of the tag's attribute.
				// attribs[i+1] is its corresponding value.
				// (i.e.  -> attribs[i] = "class" | attribs[i+1] =
				// "foo")

				var sAttribKey = tagName + "::" + attribs[i];

				if (this._renderingRules.ATTRIBS[sAttribKey]) {
					// keep the value of this class
					if (tagName === "embed" && !(attribs[i + 1].match(intPattern))) {
						return null;
					}
				} else {
					var sWarning = '<' + tagName + '> with attribute [' + attribs[i] + '="' + attribs[i + 1] + '"] is not allowed and cut';
					jQuery.sap.log.warning(sWarning, this);

					// to remove this attribute by the sanitizer the value has to be
					// set to null
					attribs[i + 1] = null;
				}

			}
			return attribs;
		};

		/**
		 * Sanitizes HTML tags and attributes according to a given policy.
		 *
		 * @param {string} inputHtml The HTML to sanitize
		 * @param {function(string,string[])} tagPolicy Determines which
		 *            tags to accept and sanitizes their attributes (see
		 *            makeHtmlSanitizer above for details)
		 * @return {string} The sanitized HTML
		 * @private
		 */
		var fnPolicy = function (tagName, attribs) {
			if (this._renderingRules.ELEMENTS[tagName]) {
				var proxiedSanatizedAttribs = jQuery.proxy(fnSanitizeAttribs, this);
				return proxiedSanatizedAttribs(tagName, attribs);
			} else {
				var sWarning = '<' + tagName + '> is not allowed';
				jQuery.sap.log.warning(sWarning, this);
			}
		};

		/**
		 * Sets the HTML text to be displayed.
		 * @param {string} sText HTML text as a string
		 * @public
		 */
		FormattedTextView.prototype.setHtmlText = function (sText) {
			var sSanitizedText = "";

			// use a proxy for policy to access the control's private variables
			var fnProxiedPolicy = jQuery.proxy(fnPolicy, this);

			// using the sanitizer that is already set to the encoder
			sSanitizedText = jQuery.sap._sanitizeHTML(sText, {
				tagPolicy: fnProxiedPolicy
			});

			this.setProperty("htmlText", sSanitizedText);
		};

		/**
		 * Sets the controls to be rendered.
		 * @param {array} aControls Controls should be rendered
		 * @private
		 */
		var fnSetControls = function (aControls) {
			if (this.hasControls()) {
				this.removeAllAggregation("controls");
			}

			var bIsArray = jQuery.isArray(aControls);
			if (bIsArray && aControls.length > 0) {
				// iterate through the given array but suppress invalidate
				for (var i = 0; i < aControls.length; i++) {
					this.addAggregation("controls", aControls[i], true);
				}
				this.invalidate();
			}
		};

		/**
		 * Sets text with placeholders and given array of controls.
		 *
		 * @param {string} sHtmlText Contains the corresponding HTML text
		 * @param {sap.ui.commons.FormattedTextViewControl} aControls Array of controls that should be used within given HTML text
		 * @ui5-metamodel This method also will be described in the UI5 (legacy) designtime metamodel
		 * @public
		 */
		FormattedTextView.prototype.setContent = function (sHtmlText, aControls) {
			// set the text using existing checks and method
			this.setHtmlText(sHtmlText);

			// validate and set content of controls corresponding to given HTML-text
			// with place holders
			fnSetControls.call(this, aControls);
		};

		return FormattedTextView;

	}, /* bExport= */ true);
<commit_msg>[INTERNAL] sap.ui.commons.FormattedTextView: simplifies a return statement"<commit_after>/*!
 * ${copyright}
 */

// Provides control sap.ui.commons.FormattedTextView.
sap.ui.define(['jquery.sap.global', './library', 'sap/ui/core/Control'],
	function (jQuery, library, Control) {
		"use strict";

		/**
		 * Constructor for a new FormattedTextView.
		 *
		 * @param {string} [sId] ID for the new control, generated automatically if no ID is given
		 * @param {object} [mSettings] Initial settings for the new control
		 *
		 * @class
		 * The FormattedTextView control allows the usage of a limited set of HTML tags for display.
		 * @extends sap.ui.core.Control
		 * @version ${version}
		 *
		 * @constructor
		 * @public
		 * @since 1.9.0
		 * @alias sap.ui.commons.FormattedTextView
		 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
		 */
		var FormattedTextView = Control.extend("sap.ui.commons.FormattedTextView", /** @lends sap.ui.commons.FormattedTextView.prototype */ {
			metadata: {

				library: "sap.ui.commons",
				properties: {
					/**
					 * The ARIA role for the control.
					 */
					accessibleRole: {
						type: "sap.ui.core.AccessibleRole",
						group: "Accessibility",
						defaultValue: sap.ui.core.AccessibleRole.Document
					},

					/**
					 * Determines text with placeholders.
					 */
					htmlText: {type: "string", group: "Misc", defaultValue: ""}
				},
				aggregations: {

					/**
					 * Array of controls that should be replaced within htmlText.
					 */
					controls: {type: "sap.ui.core.Control", multiple: true, singularName: "control"}
				}
			}
		});

		/**
		 * Initialization hook for the FormattedTextView, which creates a list of rules with allowed tags and attributes.
		 */
		FormattedTextView.prototype.init = function () {

			/*
			 * these are the rules for the FormattedTextView
			 */
			this._renderingRules = {};

			// rules for the allowed attributes
			this._renderingRules.ATTRIBS = {
				'span::class': 1,
				'div::class': 1,
				'div::id': 1,
				'span::id': 1,
				'embed::data-index': 1
			};

			// rules for the allowed tags
			this._renderingRules.ELEMENTS = {
				// Text Module Tags
				'abbr': 1,
				'acronym': 1,
				'address': 1,
				'blockquote': 1,
				'br': 1,
				'cite': 1,
				'code': 1,
				'dfn': 1,
				'div': 1,
				'em': 1,
				'h1': 1,
				'h2': 1,
				'h3': 1,
				'h4': 1,
				'h5': 1,
				'h6': 1,
				'kbd': 1,
				'p': 1,
				'pre': 1,
				'q': 1,
				'samp': 1,
				'strong': 1,
				'span': 1,
				'var': 1,

				// List Module Tags
				'dl': 1,
				'dt': 1,
				'dd': 1,
				'ol': 1,
				'ul': 1,
				'li': 1,

				// Special Tags
				// this is the placeholder for the controls
				'embed': 1

				// 'a' : 1, currently used via commons.Link Control
				// 'img' : 1, currently used via commons.Image Control
			};
		};

		/**
		 * Destroys the instance of the control.
		 * Called by Element#destroy().
		 */
		FormattedTextView.prototype.exit = function () {
			delete this._renderingRules;
		};

		/**
		 * Indicates whether the FormattedTextView contains other controls.
		 * @returns {boolean}
		 * @public
		 */
		FormattedTextView.prototype.hasControls = function () {
			var aControls = this.getAggregation("controls");
			return !!(aControls && aControls.length > 0);
		};

		/**
		 * Sanitizes attributes on an HTML tag.
		 *
		 * @param {string} tagName An HTML tag name in lowercase
		 * @param {array} attribs An array of alternating names and values
		 * @return {array} The sanitized attributes as a list of alternating names and values. Null value means to omit the attribute
		 * @private
		 */
		var fnSanitizeAttribs = function (tagName, attribs) {

			var intPattern = /^[0-9]*$/;
			for (var i = 0; i < attribs.length; i += 2) {
				// attribs[i] is the name of the tag's attribute.
				// attribs[i+1] is its corresponding value.
				// (i.e.  -> attribs[i] = "class" | attribs[i+1] =
				// "foo")

				var sAttribKey = tagName + "::" + attribs[i];

				if (this._renderingRules.ATTRIBS[sAttribKey]) {
					// keep the value of this class
					if (tagName === "embed" && !(attribs[i + 1].match(intPattern))) {
						return null;
					}
				} else {
					var sWarning = '<' + tagName + '> with attribute [' + attribs[i] + '="' + attribs[i + 1] + '"] is not allowed and cut';
					jQuery.sap.log.warning(sWarning, this);

					// to remove this attribute by the sanitizer the value has to be
					// set to null
					attribs[i + 1] = null;
				}

			}
			return attribs;
		};

		/**
		 * Sanitizes HTML tags and attributes according to a given policy.
		 *
		 * @param {string} inputHtml The HTML to sanitize
		 * @param {function(string,string[])} tagPolicy Determines which
		 *            tags to accept and sanitizes their attributes (see
		 *            makeHtmlSanitizer above for details)
		 * @return {string} The sanitized HTML
		 * @private
		 */
		var fnPolicy = function (tagName, attribs) {
			if (this._renderingRules.ELEMENTS[tagName]) {
				var proxiedSanatizedAttribs = jQuery.proxy(fnSanitizeAttribs, this);
				return proxiedSanatizedAttribs(tagName, attribs);
			} else {
				var sWarning = '<' + tagName + '> is not allowed';
				jQuery.sap.log.warning(sWarning, this);
			}
		};

		/**
		 * Sets the HTML text to be displayed.
		 * @param {string} sText HTML text as a string
		 * @public
		 */
		FormattedTextView.prototype.setHtmlText = function (sText) {
			var sSanitizedText = "";

			// use a proxy for policy to access the control's private variables
			var fnProxiedPolicy = jQuery.proxy(fnPolicy, this);

			// using the sanitizer that is already set to the encoder
			sSanitizedText = jQuery.sap._sanitizeHTML(sText, {
				tagPolicy: fnProxiedPolicy
			});

			this.setProperty("htmlText", sSanitizedText);
		};

		/**
		 * Sets the controls to be rendered.
		 * @param {array} aControls Controls should be rendered
		 * @private
		 */
		var fnSetControls = function (aControls) {
			if (this.hasControls()) {
				this.removeAllAggregation("controls");
			}

			var bIsArray = jQuery.isArray(aControls);
			if (bIsArray && aControls.length > 0) {
				// iterate through the given array but suppress invalidate
				for (var i = 0; i < aControls.length; i++) {
					this.addAggregation("controls", aControls[i], true);
				}
				this.invalidate();
			}
		};

		/**
		 * Sets text with placeholders and given array of controls.
		 *
		 * @param {string} sHtmlText Contains the corresponding HTML text
		 * @param {sap.ui.commons.FormattedTextViewControl} aControls Array of controls that should be used within given HTML text
		 * @ui5-metamodel This method also will be described in the UI5 (legacy) designtime metamodel
		 * @public
		 */
		FormattedTextView.prototype.setContent = function (sHtmlText, aControls) {
			// set the text using existing checks and method
			this.setHtmlText(sHtmlText);

			// validate and set content of controls corresponding to given HTML-text
			// with place holders
			fnSetControls.call(this, aControls);
		};

		return FormattedTextView;

	}, /* bExport= */ true);
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2015 Kagilum SAS.
 *
 * This file is part of iceScrum.
 *
 * iceScrum 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.
 *
 * iceScrum 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 General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with iceScrum.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *
 * Vincent Barrier (vbarrier@kagilum.com)
 * Nicolas Noullet (nnoullet@kagilum.com)
 *
 */

services.service("PushService", ['$rootScope', '$http', 'atmosphereService', 'IceScrumEventType', 'FormService', function($rootScope, $http, atmosphereService, IceScrumEventType, FormService) {
    var self = this;
    self.push = {};
    this.listeners = {};
    var logLevel = 'info';
    var _canLog = function(level) {
        if (level == 'debug') {
            return logLevel === 'debug';
        } else if (level == 'info') {
            return logLevel === 'info' || logLevel === 'debug';
        } else if (level == 'warn') {
            return logLevel === 'warn' || logLevel === 'info' || logLevel === 'debug';
        } else if (level == 'error') {
            return logLevel === 'error' || logLevel === 'warn' || logLevel === 'info' || logLevel === 'debug';
        } else {
            return false;
        }
    };
    this.initPush = function(projectId) {
        var options = {
            url: $rootScope.serverUrl + '/stream/app' + (projectId ? ('/product-' + projectId) : ''),
            contentType: 'application/json',
            logLevel: logLevel,
            transport: 'websocket',
            fallbackTransport: 'streaming',
            trackMessageLength: true,
            reconnectInterval: 5000,
            enableXDR: true,
            timeout: 60000
        };
        options.onOpen = function(response) {
            self.push.transport = response.transport;
            self.push.connected = true;
            self.push.uuid = response.request.uuid;
            $http.defaults.headers.common['X-Atmosphere-tracking-id'] = response.request.uuid;
            if (_canLog('debug')) {
                atmosphere.util.debug('Atmosphere connected using ' + response.transport);
            }
        };
        options.onClientTimeout = function(response) {
            self.push.connected = false;
            setTimeout(function() {
                atmosphereService.subscribe(options);
            }, options.reconnectInterval);
            if (_canLog('debug')) {
                atmosphere.util.debug('Client closed the connection after a timeout. Reconnecting in ' + options.reconnectInterval);
            }
        };
        options.onReopen = function(response) {
            self.push.connected = true;
            if (_canLog('debug')) {
                atmosphere.util.debug('Atmosphere re-connected using ' + response.transport);
            }
        };
        options.onTransportFailure = function(errorMsg, request) {
            if (_canLog('info')) {
                atmosphere.util.info(errorMsg);
            }
            if (_canLog('debug')) {
                atmosphere.util.debug('Default transport is WebSocket, fallback is ' + options.fallbackTransport);
            }
        };
        options.onMessage = function(response) {
            $rootScope.app.loading = true;
            var textBody = response.responseBody;
            try {
                var jsonBody = atmosphere.util.parseJSON(textBody);
                if (jsonBody.eventType) {
                    self.publishEvent(jsonBody);
                }
            } catch (e) {
                if (_canLog('debug')) {
                    atmosphere.util.debug("Error parsing JSON: " + textBody);
                }
            } finally {
                $rootScope.app.loading = false;
            }
        };
        options.onClose = function(response) {
            self.push.connected = false;
            if (_canLog('debug')) {
                atmosphere.util.debug('Server closed the connection after a timeout');
            }
        };
        options.onError = function(response) {
            if (_canLog('debug')) {
                atmosphere.util.debug("Sorry, but there's some problem with your socket or the server is down");
            }
        };
        options.onReconnect = function(request, response) {
            self.push.connected = false;
            if (_canLog('debug')) {
                atmosphere.util.debug('Connection lost. Trying to reconnect ' + request.reconnectInterval);
            }
        };
        atmosphereService.subscribe(options);
    };
    this.registerListener = function(domain, eventType, listener) {
        domain = domain.toLowerCase();
        if (_.isUndefined(self.listeners[domain])) {
            self.listeners[domain] = {};
        }
        if (_.isUndefined(self.listeners[domain][eventType])) {
            self.listeners[domain][eventType] = [];
        }
        var listeners = self.listeners[domain][eventType];
        if (_canLog('debug')) {
            atmosphere.util.debug('Register listener on ' + eventType + ' ' + domain);
        }
        listeners.push(listener);
        return {
            unregister: function() {
                if (_canLog('debug')) {
                    atmosphere.util.debug('Unregister listener on ' + eventType + ' ' + domain);
                }
                _.remove(listeners, function(registeredListener) {
                    return registeredListener == listener;
                });
            }
        };
    };
    this.registerScopedListener = function(namespace, eventType, listener, $scope) {
        var registeredListener = self.registerListener(namespace, eventType, listener);
        $scope.$on('$destroy', function() {
            registeredListener.unregister();
        });
    };
    this.publishEvent = function(jsonBody) {
        var object = jsonBody.object;
        var domain = object['class'].toLowerCase();
        if (!_.isEmpty(self.listeners[domain])) {
            var eventType = jsonBody.eventType;
            _.each(self.listeners[domain][eventType], function(listener) {
                if (_canLog('debug')) {
                    atmosphere.util.debug('Call listener on ' + eventType + ' ' + domain);
                }
                FormService.transformStringToDate(object);
                listener(object);
            });
        }
    };
}]);
<commit_msg>Push refactoring: - Generic "namespaces" instead of "domain" to support push for things that are not domain events - Remove broadcastToSingleUser (special case of broadcastToUsers) - Rename broadcastToProduct - Add broadcastToChannel<commit_after>/*
 * Copyright (c) 2015 Kagilum SAS.
 *
 * This file is part of iceScrum.
 *
 * iceScrum 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.
 *
 * iceScrum 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 General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with iceScrum.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *
 * Vincent Barrier (vbarrier@kagilum.com)
 * Nicolas Noullet (nnoullet@kagilum.com)
 *
 */

services.service("PushService", ['$rootScope', '$http', 'atmosphereService', 'IceScrumEventType', 'FormService', function($rootScope, $http, atmosphereService, IceScrumEventType, FormService) {
    var self = this;
    self.push = {};
    this.listeners = {};
    var logLevel = 'info';
    var _canLog = function(level) {
        if (level == 'debug') {
            return logLevel === 'debug';
        } else if (level == 'info') {
            return logLevel === 'info' || logLevel === 'debug';
        } else if (level == 'warn') {
            return logLevel === 'warn' || logLevel === 'info' || logLevel === 'debug';
        } else if (level == 'error') {
            return logLevel === 'error' || logLevel === 'warn' || logLevel === 'info' || logLevel === 'debug';
        } else {
            return false;
        }
    };
    this.initPush = function(projectId) {
        var options = {
            url: $rootScope.serverUrl + '/stream/app' + (projectId ? ('/product-' + projectId) : ''),
            contentType: 'application/json',
            logLevel: logLevel,
            transport: 'websocket',
            fallbackTransport: 'streaming',
            trackMessageLength: true,
            reconnectInterval: 5000,
            enableXDR: true,
            timeout: 60000
        };
        options.onOpen = function(response) {
            self.push.transport = response.transport;
            self.push.connected = true;
            self.push.uuid = response.request.uuid;
            $http.defaults.headers.common['X-Atmosphere-tracking-id'] = response.request.uuid;
            if (_canLog('debug')) {
                atmosphere.util.debug('Atmosphere connected using ' + response.transport);
            }
        };
        options.onClientTimeout = function(response) {
            self.push.connected = false;
            setTimeout(function() {
                atmosphereService.subscribe(options);
            }, options.reconnectInterval);
            if (_canLog('debug')) {
                atmosphere.util.debug('Client closed the connection after a timeout. Reconnecting in ' + options.reconnectInterval);
            }
        };
        options.onReopen = function(response) {
            self.push.connected = true;
            if (_canLog('debug')) {
                atmosphere.util.debug('Atmosphere re-connected using ' + response.transport);
            }
        };
        options.onTransportFailure = function(errorMsg, request) {
            if (_canLog('info')) {
                atmosphere.util.info(errorMsg);
            }
            if (_canLog('debug')) {
                atmosphere.util.debug('Default transport is WebSocket, fallback is ' + options.fallbackTransport);
            }
        };
        options.onMessage = function(response) {
            $rootScope.app.loading = true;
            var textBody = response.responseBody;
            try {
                var jsonBody = atmosphere.util.parseJSON(textBody);
                if (jsonBody.eventType) {
                    self.publishEvent(jsonBody);
                }
            } catch (e) {
                if (_canLog('debug')) {
                    atmosphere.util.debug("Error parsing JSON: " + textBody);
                }
            } finally {
                $rootScope.app.loading = false;
            }
        };
        options.onClose = function(response) {
            self.push.connected = false;
            if (_canLog('debug')) {
                atmosphere.util.debug('Server closed the connection after a timeout');
            }
        };
        options.onError = function(response) {
            if (_canLog('debug')) {
                atmosphere.util.debug("Sorry, but there's some problem with your socket or the server is down");
            }
        };
        options.onReconnect = function(request, response) {
            self.push.connected = false;
            if (_canLog('debug')) {
                atmosphere.util.debug('Connection lost. Trying to reconnect ' + request.reconnectInterval);
            }
        };
        atmosphereService.subscribe(options);
    };
    this.registerListener = function(namespace, eventType, listener) {
        namespace = namespace.toLowerCase();
        if (_.isUndefined(self.listeners[namespace])) {
            self.listeners[namespace] = {};
        }
        if (_.isUndefined(self.listeners[namespace][eventType])) {
            self.listeners[namespace][eventType] = [];
        }
        var listeners = self.listeners[namespace][eventType];
        if (_canLog('debug')) {
            atmosphere.util.debug('Register listener on ' + eventType + ' ' + namespace);
        }
        listeners.push(listener);
        return {
            unregister: function() {
                if (_canLog('debug')) {
                    atmosphere.util.debug('Unregister listener on ' + eventType + ' ' + namespace);
                }
                _.remove(listeners, function(registeredListener) {
                    return registeredListener == listener;
                });
            }
        };
    };
    this.registerScopedListener = function(namespace, eventType, listener, $scope) {
        var registeredListener = self.registerListener(namespace, eventType, listener);
        $scope.$on('$destroy', function() {
            registeredListener.unregister();
        });
    };
    this.publishEvent = function(jsonBody) {
        var object = jsonBody.object;
        var namespace = jsonBody.namespace.toLowerCase();
        if (!_.isEmpty(self.listeners[namespace])) {
            var eventType = jsonBody.eventType;
            _.each(self.listeners[namespace][eventType], function(listener) {
                if (_canLog('debug')) {
                    atmosphere.util.debug('Call listener on ' + eventType + ' ' + namespace);
                }
                FormService.transformStringToDate(object);
                listener(object);
            });
        }
    };
}]);<|endoftext|>


<commit_before>// comparison.js: Comparison service
//
// This service is used to interact with the server regarding
// scenario comparisons, and to provide the data to pages.
'use strict';

angular.module('arachne')
.factory('Comparison',
['$http', '$q', 'Arachne', 'Entities',
function($http, $q, Arachne, Entities) {
    //---------------------------------------------------------
    // Service Data

    var service = {};

    //-------------------------------------------------
    // Look-up Tables

    var catNames = {
        political:      "Political",
        military:       "Military",
        economic:       "Economic",
        social:         "Social",
        information:    "Information",
        infrastructure: "Infrastructure"
    };

    //----------------------------------------------------------
    // Object Store

    var comps = Entities.retriever('/comparison/index.json');
    var byCat = {};
    var byName = {};
    var chains = {};

    //----------------------------------------------------------
    // Delegated Methods

    service.refresh = comps.refresh;
    service.get     = comps.get;
    service.all     = comps.all;

    //----------------------------------------------------------
    // Requests

    // request(tag, caseId1, caseId2)
    //
    // Request a comparison from the server.  The tag is an
    // Arachne request tag.

    service.request = function(tag,caseId1, caseId2) {
        var deferred = $q.defer();

        Arachne.request(tag, '/comparison/request.json', {
            case1: caseId1,
            case2: caseId2
        }).then(function (stat) {
            if (stat.ok) {
                comps.add(stat.result[0]);
                PopulateIndices();
            }
            deferred.resolve(stat);
        });

        return deferred.promise;
    }

    service.compId = function(caseId1, caseId2) {
        if (caseId2) {
            return caseId1 + '/' + caseId2;
        } else {
            return caseId1;
        }
    }

    // requestChain(compId, varname)
    //
    // Requests the chain data structure for the named variable
    // from the server.

    service.requestChain = function(compId, varname) {
        Arachne.request('comp-chain', '/comparison/chain.json', {
            comp: compId,
            varname: varname
        }).then(function (stat) {
            if (stat.ok) {
                chains[compId] = chains[compId] || {}
                chains[compId][varname] = ProcessChain(stat.result[0]);
            }
        });
    }

    // chain(compId, varname)
    //
    // Retrieves the chain data structure for the named variable,
    // requesting it if need be.

    service.chain = function(compId, varname) {
        // FIRST, if we have it just return it.
        if (chains[compId] && chains[compId][varname]) {
            return chains[compId][varname];
        } else {
            return [];
        }
    }

    //----------------------------------------------------------
    // Processing the Chain

    // ProcessChain(raw) returns the processed chain.
    var ProcessChain = function(raw) {
        // FIRST, make an index, by name.
        var ndx = {};

        for (var i = 0; i < raw.length; i++) {
            ndx[raw[i].name] = raw[i];
        }

        // NEXT, build up the chain.
        var chain = [];
        var next  = raw[0].name;
        ExtendChain(chain, null, 0, ndx, next);

        return chain;
    };

    var ExtendChain = function ExtendChain(chain, parent, level, ndx, next) {
        var diff = ndx[next];
        diff.numInputs = Object.keys(diff.inputs).length;

        // FIRST, copy the inputs into an array, and annotate them.
        var inputs = [];

        for (name in diff.inputs) {
            var input = ndx[name];
            input.parent = parent;
            input.level  = level;
            input.class  = "indent"+level;
            input.score  = diff.inputs[name];
            inputs.push(input);
        }

        // NEXT, sort the array in decreasing order by score.
        inputs.sort(function(a, b) {
            if (a.score < b.score) {
                return 1;
            } else if (a.score > b.score) {
                return -1;
            } else {
                return 0;
            }
        });

        // NEXT, add the array to the chain, finding child inputs.
        for (var i = 0; i < inputs.length; i++) {
            inputs[i].id = chain.length;
            chain.push(inputs[i]);

            ExtendChain(chain, inputs[i].id, level+1, ndx, inputs[i].name);
        }
    }

    //----------------------------------------------------------
    // Queries

    // categories() -- Return the list of output categories
    service.categories = function() {
        return Object.keys(catNames);
    }

    // catName(cat) -- Return the category name
    service.catName = function(cat) {
        return catNames[cat] || 'Unknown';
    }

    // case1(compId) -- Return case1 metadata
    service.case1 = function(compId) {
        var comp = comps.get(compId);

        if (comp) {
            return Arachne.getCase(comp.case1);
        }
    }

    // case2(compId) -- Return case2 metadata
    service.case2 = function(compId) {
        var comp = comps.get(compId);

        if (comp) {
            return Arachne.getCase(comp.case2);
        }
    }

    // size() -- Number of outputs
    service.size = function(compId) {
        return service.outputs(compId).length;
    }

    // catSize(cat) -- Number of outputs in the category
    service.catSize = function(compId,cat) {
        if (byCat[compId]) {
            return byCat[compId][cat].length;
        } else {
            return 0;
        }
    }

    // byCat(compId, cat) -- Returns the outputs by category
    service.byCat = function(compId, cat) {
        var comp = comps.get(compId);

        if (!comp) {
            return [];
        }

        var result = [];

        for (var i = 0; i < byCat[compId][cat].length; i++) {
            var ndx = byCat[compId][cat][i];
            result.push(comp.outputs[ndx]);
        }

        return result;
    }

    // outputs(compId) -- Returns a possibly empty array of outputs.
    service.outputs = function(compId) {
        var comp = comps.get(compId);

        if (comp) {
            return comp.outputs;
        } else {
            return [];
        }
    }

    service.outputNames = function(compId) {
        var outputs = service.outputs(compId);
        var result = [];

        for (var i = 0; i < outputs.length; i++) {
            result.push(outputs[i].name);
        }

        return result.sort();
    }

    service.output = function(compId, varname) {
        var outputs = service.outputs(compId);

        if (byName[compId] && byName[compId][varname]) {
            return outputs[byName[compId][varname]];
        }

        return null;
    }

    //----------------------------------------------------------
    // Comparison wrapper

    // wrapper(compId)
    //
    // compId   - A comparison ID, e.g., 'case00/case01'
    //
    // Returns a wrapper object for this comparison ID.
    // TBD: caseId1, caseId2 instead of compId?

    service.wrapper = function(compId) {
        return {
            categories: service.categories,
            catName:    service.catName,
            meta:    function()     { return service.get(compId);          },
            case1:   function()     { return service.case1(compId);        },
            case2:   function()     { return service.case2(compId);        },
            size:    function()     { return service.size(compId);         },
            outputs: function()     { return service.outputs(compId);      },
            catSize: function(cat)  { return service.catSize(compId, cat); },
            byCat:   function(cat)  { return service.byCat(compId, cat);   },
            output:  function(name) { return service.output(compId, name); }
        };
    }

    //----------------------------------------------------------
    // Chain wrapper

    // chainWrapper(compId,varname)
    //
    // compId   - A comparison ID, e.g., 'case00/case01'
    // varname  - A variable name included in the comparison.
    //
    // Returns a wrapper object for this comparison ID and chain variable.

    service.chainWrapper = function(compId,varname) {
        var w = {
            categories: service.categories,
            catName:    service.catName,
            meta:    function()     { return service.get(compId);          },
            case1:   function()     { return service.case1(compId);        },
            case2:   function()     { return service.case2(compId);        },
            size:    function()     { return service.size(compId);         },
            outputs: function()     { return service.outputs(compId);      },
            catSize: function(cat)  { return service.catSize(compId, cat); },
            byCat:   function(cat)  { return service.byCat(compId, cat);   },
            output:  function(name) { return service.output(compId, name); }
        };

        w.items = function() { return service.chain(compId, varname); }

        return w;
    }

    //----------------------------------------------------------
    // Helpers

    var PopulateIndices = function() {
        byCat = {};
        byName = {};

        for (var i = 0; i < comps.all().length; i++) {
            var comp = comps.all()[i];

            byCat[comp.id] = {};
            byName[comp.id] = {};

            for (var cat in catNames) {
                byCat[comp.id][cat] = [];
            }

            for (var j = 0; j < comp.outputs.length; j++) {
                var output = comp.outputs[j];
                output.numInputs = Object.keys(output.inputs).length;
                byCat[comp.id][output.category].push(j);
                byName[comp.id][output.name] = j;
            }
        }
    };

    //----------------------------------------------------------
    // Dynamic Initialization
    service.refresh().then(function() {
        PopulateIndices();
    });

    // Return the new service.
    return service;
}]);<commit_msg>Added debugging note.<commit_after>// comparison.js: Comparison service
//
// This service is used to interact with the server regarding
// scenario comparisons, and to provide the data to pages.
'use strict';

angular.module('arachne')
.factory('Comparison',
['$http', '$q', 'Arachne', 'Entities',
function($http, $q, Arachne, Entities) {
    //---------------------------------------------------------
    // Service Data

    var service = {};

    //-------------------------------------------------
    // Look-up Tables

    var catNames = {
        political:      "Political",
        military:       "Military",
        economic:       "Economic",
        social:         "Social",
        information:    "Information",
        infrastructure: "Infrastructure"
    };

    //----------------------------------------------------------
    // Object Store

    var comps = Entities.retriever('/comparison/index.json');
    var byCat = {};
    var byName = {};
    var chains = {};

    //----------------------------------------------------------
    // Delegated Methods

    service.refresh = comps.refresh;
    service.get     = comps.get;
    service.all     = comps.all;

    //----------------------------------------------------------
    // Requests

    // request(tag, caseId1, caseId2)
    //
    // Request a comparison from the server.  The tag is an
    // Arachne request tag.

    service.request = function(tag,caseId1, caseId2) {
        var deferred = $q.defer();

        Arachne.request(tag, '/comparison/request.json', {
            case1: caseId1,
            case2: caseId2
        }).then(function (stat) {
            if (stat.ok) {
                comps.add(stat.result[0]);
                PopulateIndices();
            }
            deferred.resolve(stat);
        });

        return deferred.promise;
    }

    service.compId = function(caseId1, caseId2) {
        if (caseId2) {
            return caseId1 + '/' + caseId2;
        } else {
            return caseId1;
        }
    }

    // requestChain(compId, varname)
    //
    // Requests the chain data structure for the named variable
    // from the server.

    service.requestChain = function(compId, varname) {
        Arachne.request('comp-chain', '/comparison/chain.json', {
            comp: compId,
            varname: varname
        }).then(function (stat) {
            if (stat.ok) {
                chains[compId] = chains[compId] || {}
                chains[compId][varname] = ProcessChain(stat.result[0]);
            }
        });
    }

    // chain(compId, varname)
    //
    // Retrieves the chain data structure for the named variable,
    // requesting it if need be.

    service.chain = function(compId, varname) {
        // FIRST, if we have it just return it.
        if (chains[compId] && chains[compId][varname]) {
            return chains[compId][varname];
        } else {
            return [];
        }
    }

    //----------------------------------------------------------
    // Processing the Chain

    // ProcessChain(raw) returns the processed chain.
    var ProcessChain = function(raw) {
        // FIRST, make an index, by name.
        var ndx = {};

        for (var i = 0; i < raw.length; i++) {
            ndx[raw[i].name] = raw[i];
        }

        // NEXT, build up the chain.
        var chain = [];
        var next  = raw[0].name;
        ExtendChain(chain, null, 0, ndx, next);

        return chain;
    };

    var ExtendChain = function ExtendChain(chain, parent, level, ndx, next) {
        var diff = ndx[next];
        diff.numInputs = Object.keys(diff.inputs).length;

        // FIRST, copy the inputs into an array, and annotate them.
        var inputs = [];

        for (name in diff.inputs) {
            var input = ndx[name];
            input.parent = parent;
            input.level  = level;
            input.class  = "indent"+level;
            input.score  = diff.inputs[name];
            inputs.push(input);
        }

        // NEXT, sort the array in decreasing order by score.
        inputs.sort(function(a, b) {
            if (a.score < b.score) {
                return 1;
            } else if (a.score > b.score) {
                return -1;
            } else {
                return 0;
            }
        });

        // NEXT, add the array to the chain, finding child inputs.
        for (var i = 0; i < inputs.length; i++) {
            inputs[i].id = chain.length;
            chain.push(inputs[i]);

            ExtendChain(chain, inputs[i].id, level+1, ndx, inputs[i].name);
        }
    }

    //----------------------------------------------------------
    // Queries

    // categories() -- Return the list of output categories
    service.categories = function() {
        return Object.keys(catNames);
    }

    // catName(cat) -- Return the category name
    service.catName = function(cat) {
        return catNames[cat] || 'Unknown';
    }

    // case1(compId) -- Return case1 metadata
    service.case1 = function(compId) {
        var comp = comps.get(compId);

        if (comp) {
            return Arachne.getCase(comp.case1);
        }
    }

    // case2(compId) -- Return case2 metadata
    service.case2 = function(compId) {
        var comp = comps.get(compId);

        if (comp) {
            return Arachne.getCase(comp.case2);
        }
    }

    // size() -- Number of outputs
    service.size = function(compId) {
        return service.outputs(compId).length;
    }

    // catSize(cat) -- Number of outputs in the category
    service.catSize = function(compId,cat) {
        if (byCat[compId]) {
            return byCat[compId][cat].length;
        } else {
            return 0;
        }
    }

    // byCat(compId, cat) -- Returns the outputs by category
    service.byCat = function(compId, cat) {
        var comp = comps.get(compId);

        if (!comp) {
            return [];
        }

        var result = [];

        for (var i = 0; i < byCat[compId][cat].length; i++) {
            var ndx = byCat[compId][cat][i];
            result.push(comp.outputs[ndx]);
        }

        return result;
    }

    // outputs(compId) -- Returns a possibly empty array of outputs.
    service.outputs = function(compId) {
        var comp = comps.get(compId);

        if (comp) {
            return comp.outputs;
        } else {
            return [];
        }
    }

    service.outputNames = function(compId) {
        var outputs = service.outputs(compId);
        var result = [];

        for (var i = 0; i < outputs.length; i++) {
            result.push(outputs[i].name);
        }

        return result.sort();
    }

    service.output = function(compId, varname) {
        var outputs = service.outputs(compId);

        if (byName[compId] && byName[compId][varname]) {
            return outputs[byName[compId][varname]];
        }

        return null;
    }

    //----------------------------------------------------------
    // Comparison wrapper

    // wrapper(compId)
    //
    // compId   - A comparison ID, e.g., 'case00/case01'
    //
    // Returns a wrapper object for this comparison ID.
    // TBD: caseId1, caseId2 instead of compId?

    service.wrapper = function(compId) {
        return {
            categories: service.categories,
            catName:    service.catName,
            meta:    function()     { return service.get(compId);          },
            case1:   function()     { return service.case1(compId);        },
            case2:   function()     { return service.case2(compId);        },
            size:    function()     { return service.size(compId);         },
            outputs: function()     { return service.outputs(compId);      },
            catSize: function(cat)  { return service.catSize(compId, cat); },
            byCat:   function(cat)  { return service.byCat(compId, cat);   },
            output:  function(name) { return service.output(compId, name); }
        };
    }

    //----------------------------------------------------------
    // Chain wrapper

    // chainWrapper(compId,varname)
    //
    // compId   - A comparison ID, e.g., 'case00/case01'
    // varname  - A variable name included in the comparison.
    //
    // Returns a wrapper object for this comparison ID and chain variable.

    service.chainWrapper = function(compId,varname) {
        var w = {
            categories: service.categories,
            catName:    service.catName,
            meta:    function()     { return service.get(compId);          },
            case1:   function()     { return service.case1(compId);        },
            case2:   function()     { return service.case2(compId);        },
            size:    function()     { return service.size(compId);         },
            outputs: function()     { return service.outputs(compId);      },
            catSize: function(cat)  { return service.catSize(compId, cat); },
            byCat:   function(cat)  { return service.byCat(compId, cat);   },
            output:  function(name) { return service.output(compId, name); }
        };

        w.items = function() { return service.chain(compId, varname); }

        return w;
    }

    //----------------------------------------------------------
    // Helpers

    var PopulateIndices = function() {
        byCat = {};
        byName = {};

        for (var i = 0; i < comps.all().length; i++) {
            var comp = comps.all()[i];

            byCat[comp.id] = {};
            byName[comp.id] = {};

            for (var cat in catNames) {
                byCat[comp.id][cat] = [];
            }

            for (var j = 0; j < comp.outputs.length; j++) {
                var output = comp.outputs[j];
                // TBD: I think this should be done before the data
                // is saved.
                output.numInputs = Object.keys(output.inputs).length;
                byCat[comp.id][output.category].push(j);
                byName[comp.id][output.name] = j;
            }
        }
    };

    //----------------------------------------------------------
    // Dynamic Initialization
    service.refresh().then(function() {
        PopulateIndices();
    });

    // Return the new service.
    return service;
}]);<|endoftext|>