var ds = window.ds || {}
ds.models = ds.models || {}
ds.models.data = ds.models.data || {}
ds.models.transform = ds.models.transform || {}
ds.edit = ds.edit || {}
ds.edit.properties = ds.edit.properties || {}

/**
 * Return a context object based on the current URL.
 */
ds.context = function(context) {
  context = context || {}
  var params = URI(window.location).query(true)
  var variables = {}

  context.from = params.from || ds.config.DEFAULT_FROM_TIME
  context.until = params.until

  for (var key in params) {
    /* compatibility w/gdash params */
    if (key.indexOf('p[') == 0) {
      var name = key.slice(2, -1)
      variables[name] = params[key]
    } else {
      variables[key] = params[key]
    }
  }
  context.variables = variables
  context.params = params

  return context
}


/**
 * Helper function to (potentially) render a template with a given
 * context object. If the string does not contain any handlebars tags,
 * it will be returned as-is.
 */
ds.render_template = function(str, context) {
  if (str == null) {
    return str
  }
  if (str.indexOf('{{') == -1) {
    return str
  } else {
    var template = Handlebars.compile(str)
    return template(context)
  }
}

ds.safe_render_template = function(str, context) {
  try {
    return ds.render_template(str, context)
  } catch (e) {
    console.log('ds.safe_render_template(): ' + e)
    return str
  }
}

/**
 * Yo dawg, I heard you like objects, so I put some objects in your
 * objects...
 */
ds.extend = function() {
  var target = {}
  for (var i = 0; i < arguments.length; i++) {
    var source = arguments[i] || {}
    for (var key in source) {
      if (source.hasOwnProperty(key)) {
        target[key] = source[key]
      }
    }
  }
  return target
}

ds.json = function(thing) {
  if (thing.toJSON && typeof(thing.toJSON) === 'function') {
    return thing.toJSON()
  } else {
    return thing
  }
}
;ds.log = ds.log || {}

ds.log.Level = {
  OFF:   { name: 'OFF',   value: 0 },
  FATAL: { name: 'FATAL', value: 1 },
  ERROR: { name: 'ERROR', value: 2 },
  WARN:  { name: 'WARN ', value: 3 },
  INFO:  { name: 'INFO ', value: 4 },
  DEBUG: { name: 'DEBUG', value: 5 },
  TRACE: { name: 'TRACE', value: 6 }
}

ds.log.default_log_time_format = 'YYYY-MM-DD hh:mm:ss A'

ds.log.default_level = ds.log.Level.INFO

ds.log.cache = {}

ds.log.set_level = function(level) {
  ds.log.default_level = level
  var keys = Object.keys(ds.log.cache)
  for (var i in keys) {
    ds.log.cache[keys[i]].level(level)
  }
}

ds.log.logger = function(options) {

  var self = {}
    , time_format = ds.log.default_log_time_format
    , level = ds.log.default_level
    , name = 'default'

  if (options) {
    if (typeof(options) === 'string') {
      name = options
    } else {
      time_format = options.time_format || time_format
      level       = options.level || level
      name        = options.name || name
    }
  }

  if (ds.log.cache[name]) {
    return ds.log.cache[name]
  }

  function timestamp() {
    return moment().format(time_format)
  }

  function format(msg_level, msg) {
    return timestamp() + ' | ' + msg_level.name + ' | ' + name + ' | ' + msg
  }

  function logger(msg_level) {
    return function(msg) {
      if (level.value >= msg_level.value) {
        console.log(format(msg_level, msg))
      }
      return self
    }
  }

  self.trace = logger(ds.log.Level.TRACE)
  self.debug = logger(ds.log.Level.DEBUG)
  self.info  = logger(ds.log.Level.INFO)
  self.warn  = logger(ds.log.Level.WARN)
  self.error = logger(ds.log.Level.ERROR)
  self.fatal = logger(ds.log.Level.FATAL)

  self.level = function(_) {
    if (arguments.length) {
      level = _
      return self
    }
    return level
  }

  self.is_enabled = function(log_level) {
    return log_level && level.value >= log_level.value
  }

  self.name = function() {
    return name
  }

  self.time_format = function(_) {
    if (arguments.level) {
      time_format = _
      return self
    }
    return time_format
  }

  ds.log.cache[name] = self

  return self
}
;ds.event =
  (function() {

    var self = {}
      , log = ds.log.logger('tessera.events')

    self.on = function(target, event, handler) {
      log.debug('on(): ' + event)
      bean.on(target, event, handler)
    }

    self.off = function(target, event) {
      log.debug('off(): ' + event)
      bean.off(target, event)
    }

    self.fire = function(target, event, data) {
      log.debug('fire(): ' + event)
      bean.fire(target, event, data)
    }

    return self

  })()
;/**
 * A simple registry for organizing and retrieving ordered sets of
 * named things.
 */
ds.registry = function(init) {
  'use strict'

  var data = {}
    , self = {}
    , DEFAULT_CATEGORY = 'default'

  if (init) {
    self.name = init.name
    self.process = init.process
  }

  function get_data(category) {
    if (typeof(data[category]) === 'undefined') {
      data[category] = {
        list: [],
        index: {}
      }
    }
    return data[category]
  }

  self.register = function(category, thing) {
    if (arguments.length === 1) {
      thing = category
      category = DEFAULT_CATEGORY
    }
    if (thing instanceof Array) {
      for (var i = 0; i < thing.length; i++) {
        self.register(category, thing[i])
      }
    } else {
      var category_data = get_data(category)
      if (self.process && self.process instanceof Function) {
        thing = self.process(thing)
      }
      if (typeof(category_data.index[thing.name]) === 'undefined') {
        category_data.index[thing.name] = thing
      }
      category_data.list.push(thing)
    }
    return self
  }

  self.list = function(category) {
    if (arguments.length === 0)
      category = DEFAULT_CATEGORY
    return get_data(category).list
  }

  self.get = function(category, name) {
    if (arguments.length === 1) {
      name = category
      category = DEFAULT_CATEGORY
    }
    return get_data(category).index[name]
  }

  self.categories = function() {
    return Object.keys(data)
  }

  return self
}
;ds.action = function(data) {

  var self = limivorous.observable()
                       .property('name')
                       .property('display')
                       .property('icon')
                       .property('show')
                       .property('hide')
                       .property('class')
                       .property('options')
                       .property('handler')
                       .property('divider')
                       .property('actions')
                       .property('category')
                       .build()

  if (data) {
    self.name = data.name
    self.display = data.display
    self.icon = data.icon
    self.options = data.options
    self.handler = data.handler
    self.show = data.show
    self.hide = data.hide
    self.divider = data.divider
    self.class = data.class
    self.actions = data.actions
    self.category = data.category
    self.is_action = true
  }

  self.toJSON = function() {
    return {
      name: self.name,
      category: self.category,
      show: self.show,
      hide: self.hide,
      class: self.class,
      options: self.options
    }
  }

  return self
}

ds.action.divider = ds.action({divider:true})

ds.actions = ds.registry({
  name: 'actions',
  process: function(data) {
    if (data.is_action)
      return data
    return ds.action(data)
  }
})
;/**
 * Mixin for transform types common fields
 */
ds.transform = function(data) {

  var self = {}

  if (data) {
    self.name = data.name
    self.display_name = data.display_name
    self.transform_type = data.transform_type
    self.transform = data.transform
    self.icon = data.icon
  }

  /**
   * Create a UI action object to invoke this transform.
   */
  self.action = function() {
    return ds.action({
      name:    self.name + '_action',
      display: self.display_name + '...',
      icon:    self.icon || 'fa fa-eye',
      hide:    ds.app.Mode.TRANSFORM,
      handler: function(action, item) {
        ds.manager.apply_transform(self, item)
      }
    })
  }

  self.toJSON = function() {
    return {
      name: self.name
    }
  }

  return self
}

ds.transforms = ds.registry({
  name: 'transforms',
  process: function(data) {
    var transform = ds.transform(data)
    var action_category = transform.transform_type + '-transform-actions'
    ds.actions.register(action_category, transform.action())
    return transform
  }
})
;/**
 * The central application object. At present, its primary
 * responsibility is to handle switching between different application
 * modes, which is mainly about hiding & showing various elements in
 * the UI.
 */
ds.app =
  (function() {

    var mode_stack = []
      , ANIMATION_DELAY = 300
      , self = {}
      , log = ds.log.logger('tessera.app')

    self.current_mode = 'standard'

    /**
     * Valid user interface modes.
     */
    self.Mode = {
      EDIT:      'edit',
      TRANSFORM: 'transform',
      DISPLAY:   'display',
      STANDARD:  'standard'
    }

    self.Event = {
      MODE_ENTER:         'ds-enter:',
      MODE_EXIT:          'ds-exit:',
      MODE_REFRESH:       'ds-refresh:',
      DASHBOARD_LOADED:   'ds-dashboard-loaded',
      DASHBOARD_RENDERED: 'ds-dashboard-rendered',
      RANGE_CHANGED:      'ds-ranged-changed'
    }

    function do_exit_mode(mode) {
      if (log.is_enabled(ds.log.Level.DEBUG)) {
        log.debug('mode <- ' + mode)
      }
      ds.event.fire(self, self.Event.MODE_EXIT + mode)
      var state = mode_stack.pop()
      if (state) {
        state.hidden.show(ANIMATION_DELAY)
        state.shown.hide(ANIMATION_DELAY)
      }
    }

    function do_enter_mode(mode) {
      if (log.is_enabled(ds.log.Level.DEBUG)) {
        log.debug('mode -> ' + mode)
      }
      var hidden = $('[data-ds-hide~="' + mode + '"]').hide(ANIMATION_DELAY)
      var shown = $('[data-ds-show~="' + mode + '"]').show(ANIMATION_DELAY)

      self.current_mode = mode
      mode_stack.push({
        mode: mode,
        hidden: hidden,
        shown: shown
      })
      ds.event.fire(self, self.Event.MODE_ENTER + mode)
    }

    /**
     * An alias for switch_to_mode(current_mode)
     */
    self.refresh_mode = function() {
      self.switch_to_mode(self.current_mode, 0)
      ds.event.fire(self, self.Event.MODE_REFRESH + self.current_mode)
      return self
    }

    /**
     * Switch to a named mode. This will first run the exit handlers
     * for the current mode, restoring any previously hidden elements
     * to visibility, before running the enter mode handlers for the
     * new mode and changing element visibility for the new mode.
     *
     * If the requested mode is already the current mode, the mode
     * will be 'refreshed' by re-evaluating what elements are hidden &
     * shown.
     *
     * @param mode {string} A mode name
     */
    self.switch_to_mode = function(mode, delay) {
      delay = typeof(delay) === 'undefined' ? ANIMATION_DELAY : delay
      if (mode === self.current_mode && mode_stack.length > 0) {
        var state = mode_stack[mode_stack.length - 1]
        state.hidden = $('[data-ds-hide~="' + mode + '"]').hide(delay)
        state.shown = $('[data-ds-show~="' + mode + '"]').show(delay)
      } else {
        do_exit_mode(self.current_mode)
        do_enter_mode(mode)
      }
      return self
    }

    /**
     * Toggle between the named mode and 'standard'. Return true if
     * we've switched to mode, otherwise false if we've switched back
     * to 'standard'.
     *
     * @param mode {string} A mode name
     * @return true if switched to mode, else false
     */
    self.toggle_mode = function(mode) {
      if (self.current_mode == mode) {
        self.switch_to_mode(self.Mode.STANDARD)
        return false
      } else {
        self.switch_to_mode(mode)
        return true
      }
    }

    /**
     * Register handlers to run on entrance to and/or exit from an
     * application mode.
     *
     * @param options An object with one or both of 'enter' and 'exit'
     *                attributes bound to functions which will be run
     *                on mode transitions.
     */
    self.add_mode_handler = function(mode, options) {
      if (options.enter && (options.enter instanceof Function)) {
        ds.event.on(self, self.Event.MODE_ENTER + mode, options.enter)
      }
      if (options.exit && (options.exit instanceof Function)) {
        ds.event.on(self, self.Event.MODE_EXIT + mode, options.exit)
      }
      if (options.refresh && (options.refresh instanceof Function)) {
        ds.event.on(self, self.Event.MODE_REFRESH + mode, options.refresh)
      }
      return self
    }

    return self
  })()

Mousetrap.bind('ctrl+shift+d', function(e) {
  ds.app.toggle_mode(ds.app.Mode.DISPLAY)
})

Mousetrap.bind('ctrl+shift+e', function(e) {
  ds.app.toggle_mode(ds.app.Mode.EDIT)
})

Mousetrap.bind('ctrl+shift+s', function(e) {
  ds.manager.update_definition(ds.manager.current.dashboard, function() {
    ds.manager.success('Dashboard saved')
  })
})
;ds.DashboardHolder = function(url, element) {
  "use strict"

  this.url = url
  this.element = element
  this.dashboard = null

    this.setRange = function(from, until) {
        var self = this
        var url = URI(self.url)
        if (from) {
            url.setQuery('from', from)
        }
        if (until) {
            url.setQuery('until', until)
        }
        self.url = url.href()
    }
}

ds.manager =
  (function() {
    "use strict"

    var current
      , self = {}
      , log = ds.log.logger('tessera.app.manager')

    Object.defineProperty(self, 'current', { get: function() { return current }})

    self.set_current = function(_) {
      current = _
      return self
    }

    /**
     * Register an event handler for processing a dashboard once it's
     * loaded and ready.
     */
    self.onDashboardLoaded = function(handler) {
        ds.event.on(self, ds.app.Event.DASHBOARD_LOADED, handler)
        return self
    }

    /**
     * List all dashboards.
     */
    self.list = function(path, handler) {
        var path = path || '/api/dashboard'
        $.ajax({
            dataType: 'json',
            url: path
        }).done(function(data) {
            handler(data)
        }).error(function(xhr, status, error) {
          self.error('Error listing dashboards. ' + error)
        })
    }

    self.default_error_handler = function(xhr, status, error) {
      console.log(xhr)
      console.log(status)
      console.log(error)
      self.error('Oops')
    }

    self.error = function(message, options_) {
      var options = options_ || {}
      options.type = options.type || 'danger'
      $.growl({
        message: message,
        title: 'Error',
        icon: 'fa fa-exclamation-circle'
      }, options)
    }

    self.warning = function(message, options_) {
      var options = options_ || {}
      options.type = options.type || 'warning'
      $.growl({
        message: message,
        title: options.title || 'Warning',
        icon: options.icon || 'fa fa-exclamation-circle'
      }, options)
    }

    self.success = function(message, options_) {
      var options = options_ || {}
      options.type = options.type || 'success'
      $.growl({
        message: message,
        title: options.title || 'Success',
        icon: options.icon || 'fa fa-check-circle'
      }, options)
    }

    /**
     * Recurse through the presentation tree, giving each dashboard
     * item an element ID, and checking to see if we have any
     * components that require the raw data queries to be made.
     */
    self._prep_items = function(dashboard, holder, interactive) {
      dashboard.visit(function(item) {
        if (!item.item_type)
          return
        if (item.requires_data) {
          holder.raw_data_required = true
        }
      })
    }

    /**
     * Set up us the API call.
     */
    self._prep_url = function(base_url, options) {
      var url = URI(base_url).setQuery('rendering', true)
      var context = ds.context(url.query(true))

      context.from = options.from || context.from
      context.until = context.until || options.until

      url.setQuery('from', context.from)
      if (context.until) {
        url.setQuery('until', context.until)
      }

      context.url = url.href()

      if (typeof(options.interactive) != 'undefined') {
        context.interactive = options.interactive
      } else if (context.params.interactive) {
        context.interactive = context.params.interactive != 'false'
      }

      return context
    }

    /**
     * Load and render a dashboard.
     */
    self.load = function(url, element, options_) {
      log.debug('load(): ' + url)
      if (self.current && self.current.dashboard) {
        self.current.dashboard.cleanup()
      }
      var options = options_ || {}
      var holder = new ds.DashboardHolder(url, element)
      var context = self._prep_url(url, options)
      self.set_current(holder)
      $.ajax({
        dataType: "json",
        url: context.url
      }).error(function(xhr, status, error) {
        self.error('Error loading dashboard. ' + error)
      }).done(function(data) {
        var dashboard = ds.models.dashboard(data)
        holder.dashboard = dashboard

        if (data.preferences.renderer && ds.charts[data.preferences.renderer]) {
          ds.charts.provider = ds.charts[data.preferences.renderer]
        }

          ds.event.fire(self, ds.app.Event.DASHBOARD_LOADED, dashboard)

          dashboard.render_templates(context.variables)

          var interactive = data.preferences.interactive
          if (context.interactive != undefined) {
            interactive = context.interactive
          }
          holder.raw_data_required = interactive
          ds.charts.interactive = interactive

          // Build a map from the presentation elements to their
          // model objects.
          self._prep_items(dashboard, holder, interactive)

          // Render the dashboard
          $(element).html(dashboard.definition.render())

          var currentURL = URI(holder.url)
          ds.event.fire(self, ds.app.Event.RANGE_CHANGED, {
            from: currentURL.query('from'),
            until: currentURL.query('until')
          })

          if (self.current_transform) {
            self.apply_transform(self.current_transform.transform, self.current_transform.target, false, context)
          } else {
            // Load the queries
            dashboard.definition.load_all({
              from: context.from,
              until: context.until
            }, !holder.raw_data_required)
          }

          ds.event.fire(self, ds.app.Event.DASHBOARD_RENDERED, dashboard)

          if (context.params.mode) {
            ds.app.switch_to_mode(context.params.mode)
          } else {
            ds.app.refresh_mode()
          }
        })
        return self
    }

    /**
     * Re-render a dashboard item and update its DOM representation.
     */
    self.update_item_view = function(item) {
      var element = $('#' + item.item_id)
      var visible = ds.edit.details_visibility(item)
      element.replaceWith(item.render())
      if (visible) {
        ds.edit.show_details(item.item_id)
      }
      item.visit(function(i) {
        var query = i.query_override || i.query
        if (query && query.is_query) {
          log.debug('update_item_view(): reloading query ' + query.name)
          query.load()
        }
      })
      ds.app.refresh_mode()
    }

    /**
     * Execute a function with re-rendering of the DOM for dashboard
     * items disabled.
     */
    self.without_updates = function(handler) {
      var fn = self.update_item_view
      self.update_item_view = function() { }
      try {
        return handler()
      } finally {
        self.update_item_view = fn
      }
    }

    self.handle_popstate = function(event) {
      if (!event.state) {
        self.current_transform = undefined
        self.refresh()
        ds.app.switch_to_mode('standard')
      } else {
        if (event.state.transform) {
          var item = ds.manager.current.dashboard.get_item(event.state.target.item_id)
          self.apply_transform(event.state.transform.name, item, false)
        } else if (event.state.url) {
          self.current_transform = undefined
          self.refresh()
          ds.app.switch_to_mode('standard')
        }
      }
    }

    window.addEventListener('popstate', self.handle_popstate)

    self.remove_transform = function() {
      window.location = URI(window.location)
                        .path(self.current.dashboard.view_href)
                        .href()
      self.current_transform = undefined
    }

    self.apply_transform = function(transform, target, set_location, context) {
      var dashboard = self.current.dashboard
      if (typeof(set_location) === 'undefined')
        set_location = true

      if (typeof(transform) === 'string')
        transform = ds.transforms.get(transform)

      log.debug('apply_transform(' + transform.name + ')')

      /**
       * Set browser URL state
       */
      if (set_location) {
        var url = URI(window.location)
        var path = url.path()
        if (transform.transform_type == 'dashboard' && typeof(target) === 'undefined') {
          target = dashboard.definition
        } else {
          path = path + '/' + target.item_id
        }
        path = path + '/transform/' + transform.name
        window.history.pushState({ url:self.current.url,
                                   element:self.current.element,
                                   transform:transform.toJSON(),
                                   target:target.toJSON() }, '', url.path(path).href())
      }

      /**
       * update_item_view() reloads queries, which we don't want to do
       * while processing the transform.
       */
      var result = self.without_updates(function() {
                     return transform.transform(target)
                   })

      dashboard.definition.queries = result.get_queries() // this could go in an observer
      dashboard.set_items([result])

      $('#' + dashboard.definition.item_id).replaceWith(dashboard.render())
      dashboard.render_templates(ds.context().variables)
      if (context) {
        dashboard.load_all({
          from: context.from,
          until: context.until
        })
      } else {
        dashboard.load_all()
      }

      if ((transform.transform_type === 'presentation') && (ds.app.current_mode != ds.app.Mode.EDIT)) {
        ds.app.switch_to_mode('transform')
        self.current_transform = {
          transform: transform,
          target: target
        }
      }

      return self
    }

    self.refresh = function() {
      log.debug('refresh()')
      if (self.current && (ds.app.current_mode != ds.app.Mode.EDIT)) {
        self.load(self.current.url, self.current.element)
      } else {
        if (log.is_enabled(ds.log.Level.DEBUG)) {
          log.debug('skipping reload; current mode: ' + ds.app.current_mode)
        }
      }
    }

    // Definitely getting to the point we need some kind of reactive MVC
    // here
    self.toggle_interactive_charts = function() {
        $.get('/api/preferences', function(data) {
            var setting = !data.interactive
            var dashboard_url = URI(self.current.url)
            var window_url = URI(window.location)

            if (window_url.hasQuery('interactive', 'true')) {
                setting = false
            } else if (window_url.hasQuery('interactive', 'false')) {
                setting = true
            }

            dashboard_url.setQuery('interactive', setting)
            window_url.setQuery('interactive', setting)
            self.current.url = dashboard_url.href()
            window.history.pushState({url: self.current.url, element:self.current.element}, '', window_url.href())
            self.refresh()
            return setting == 'true'
        })
    }

    /* -----------------------------------------------------------------------------
       Time range and auto-refresh
       ----------------------------------------------------------------------------- */

    self.set_time_range = function(from, until) {
        var uri = URI(window.location)
        from
            ? uri.setQuery('from', from)
            : uri.removeQuery('from')
        until
            ? uri.setQuery('until', until)
            : uri.removeQuery('until')

        window.history.pushState({url: self.current.url, element:self.current.element}, '', uri.href())

        self.current.setRange(from, until)
        ds.event.fire(self, ds.app.Event.RANGE_CHANGED, {
            from: from, until: until
        })
      self.refresh()
    }

    self.ranges = {
        // TODO - quick hack. Parse the range and generate on the fly
        // for maximum flexibiliy
        '-1h'  : 'Past Hour',
        '-2h'  : 'Past 2 Hours',
        '-3h'  : 'Past 3 Hours',
        '-4h'  : 'Past 4 Hours',
        '-6h'  : 'Past 6 Hours',
        '-12h' : 'Past 12 Hours',
        '-1d'  : 'Past Day',
        '-7d'  : 'Past Week'
    }

    self.getRangeDescription = function(range) {
        if (range in self.ranges) {
            return self.ranges[range]
        } else {
            return null
        }
    }

    self.onRangeChanged = function(handler) {
        var self = this
        ds.event.on(self, ds.app.Event.RANGE_CHANGED, handler)
    }

    self.autoRefreshInterval = null
    self.intervalId = null

    self.set_refresh_interval = function(value) {
        var intervalSeconds = parseInt(value)
        self.autoRefreshInterval = intervalSeconds
        if (self.intervalId) {
          if (log.is_enabled(ds.log.Level.DEBUG)) {
            log.debug('clearing auto-refresh interval; intervalId: ' + self.intervalId)
          }
          window.clearInterval(self.intervalId)
          self.intervalId = undefined
        }
        if (intervalSeconds > 0) {
            self.intervalSeconds = intervalSeconds
            self.intervalId = window.setInterval(self.refresh, intervalSeconds * 1000)
          if (log.is_enabled(ds.log.Level.DEBUG)) {
            log.debug('set auto-refresh interval; intervalId: ' + self.intervalId + '; seconds: ' + intervalSeconds)
          }
        }
    }

    self.delete_with_confirmation = function(href, handler) {
        bootbox.dialog({
            message: "Are you really sure you want to delete this dashboard? Deletion is irrevocable.",
            title: "Confirm dashboard delete",
            buttons: {
                cancel: {
                    label: "Cancel",
                    className: "btn-default",
                    callback: function() {
                        // TODO - notification
                    }
                },
                confirm: {
                    label: "Delete",
                    className: "btn-danger",
                    callback: function() {
                        self.delete_dashboard(href, handler)
                    }
                }
            }
        })
    }

    self.delete_dashboard = function(href, done_) {
      var done = done_ || function() {
                            window.location = '/dashboards'
                            self.success('Successfully deleted dashboard ' + href)
                          }
        $.ajax({
            url: href,
            type: 'DELETE'
        }).done(done).error(function(xhr, status, error) {
          self.error('Error deleting dashboard ' + href + ' ' + error)
        })
    }

    self.delete_current = function() {
        self.delete_with_confirmation(self.current.dashboard.href)
    }

    self.create = function(dashboard, handler) {
      $.ajax({
        type: 'POST',
        url: '/api/dashboard/',
        contentType: 'application/json',
        dataType: 'json',
        data: JSON.stringify(dashboard)
      }).done(function(data) {
        if (handler && handler instanceof Function) {
          handler(data)
        }
      }).error(function(xhr, status, error) {
        self.error('Error creating dashboard. ' + error)
      })
    }

    self.update = function(dashboard, handler) {
      $.ajax({
        type: 'PUT',
        url: dashboard.href,
        contentType: 'application/json',
        dataType: 'json',
        data: JSON.stringify(dashboard)
      }).done(function(data) {
        if (handler && handler instanceof Function) {
          handler(data)
        }
      }).error(function(xhr, status, error) {
        self.error('Error updating dashboard ' + dashboard.title + '. ' + error)
      })
    }

    self.update_definition = function(dashboard, handler) {
      $.ajax({
        type: 'PUT',
        url: dashboard.definition_href,
        contentType: 'application/json',
        dataType: 'json',
        data: JSON.stringify(dashboard.definition)
      }).done(function(data) {
        if (handler && handler instanceof Function) {
          handler(data)
        }
      }).error(function(xhr, status, error) {
        self.error('Error updating dashboard definition ' + dashboard.title + '. ' + error)
      })
    }



    // Oh this is ugly
    self.duplicate = function(href, handler) {
        // Get dashboard
        $.get(href, function(data) {
            var dashboard = data
            dashboard.title = 'Copy of ' + dashboard.title

            // Get definition
            $.get(href + '/definition', function(data) {
                dashboard.definition = data
                // Duplicate dashboard
                $.ajax({
                    type: 'POST',
                    url: '/api/dashboard/',
                    contentType: 'application/json',
                    dataType: 'json',
                    data: JSON.stringify(dashboard)
                }).done(function(data) {
                    if (handler) {
                        handler()
                    }
                }).error(function(xhr, status, error) {
                  self.error('Error duplicating dashboard ' + href + '. ' + error)
                })
            })
        })
    }


   return self
})()
;
marked.setOptions({
  highlight: function(code) {
    return hljs.highlightAuto(code).value
  }
})

Handlebars.registerHelper('markdown', function(value) {
  if (!value)
    return ''
  return new Handlebars.SafeString(marked(value))
})

Handlebars.registerHelper('json', function(value) {
  return new Handlebars.SafeString(hljs.highlightAuto(JSON.stringify(value, null, '  ')).value)
})

Handlebars.registerHelper('moment', function(format, value) {
  if (!value)
    return ''
  if (format == 'fromNow') {
    return moment(value).tz(ds.config.DISPLAY_TIMEZONE).fromNow()
  } else {
    return moment(value).tz(ds.config.DISPLAY_TIMEZONE).format(format)
  }
})

Handlebars.registerHelper('format', function(format, value) {
  if (typeof(value) === 'undefined')
    return ''
  if (!format)
    return value
  try {
    return d3.format(format)(value)
  } catch ( e ) {
    console.log('Error formatting ' + format + ' / ' + value + ': ' + e.message)
    return value
  }
})

Handlebars.registerHelper('height', function(item) {
  var height = item.height
  if (item.is_chart && !height) {
    height = 2
  }
  return height ? 'ds-height' + height : ''
})

Handlebars.registerHelper('span', function(item) {
  return item.span ? 'col-md-' + item.span : ''
})

Handlebars.registerHelper('offset', function(item) {
  return item.offset ? 'col-md-offset-' + item.offset : ''
})

Handlebars.registerHelper('css_class', function(item) {
  return item.css_class ? item.css_class : ''
})

Handlebars.registerHelper('container_class', function(item) {
  switch (item.layout) {
    case 'fixed': return 'container'
    case 'fluid': return 'container-fluid'
    default: return ''
  }
})

Handlebars.registerHelper('ds-edit-bar', function(item) {
  var context = { item: item }
  var template = undefined
  if (item.item_type === 'cell') {
    template = ds.templates["ds-edit-bar-cell"]
  } else if (item.item_type === 'row') {
    template = ds.templates["ds-edit-bar-row"]
  } else if (item.item_type === 'section') {
    template = ds.templates["ds-edit-bar-section"]
  } else {
    template = ds.templates["ds-edit-bar-item"]
  }
  return template ? new Handlebars.SafeString(template(context)) : ''
})

Handlebars.registerHelper('style_class', function(item) {
  if (item.style) {
    switch (item.style) {
      case 'well':
        return 'well'
      case 'callout_neutral':
        return 'bs-callout bs-callout-neutral'
      case 'callout_info':
        return 'bs-callout bs-callout-info'
      case 'callout_success':
        return 'bs-callout bs-callout-success'
      case 'callout_warning':
        return 'bs-callout bs-callout-warning'
      case 'callout_danger':
        return 'bs-callout bs-callout-danger'
      case 'alert_neutral':
        return 'alert alert-neutral'
      case 'alert_info':
        return 'alert alert-info'
      case 'alert_success':
        return 'alert alert-success'
      case 'alert_warning':
        return 'alert alert-warning'
      case 'alert_danger':
        return 'alert alert-danger'
     }
  }  else {
    return ''
  }
})

Handlebars.registerHelper('item', function(item) {
  if (!item)
    return ''
  return new Handlebars.SafeString(item.render())
})

Handlebars.registerHelper('interactive_property', function(property, item) {
  property = ds.property.get(property)
  if (!property)
    return ''
  return new Handlebars.SafeString(property.render(item))
})

Handlebars.registerHelper('actions', function(category, type) {
  var template = ds.templates.action
  if (type === 'button') {
    template = ds.templates.action_button
  }
  var actions = ds.actions.list(category)
  if (actions && (actions instanceof Array) && actions.length) {
    if (typeof(type) === 'boolean' && type) {
      actions = [ds.action.divider].concat(actions)
    }

    var html = ''
    for (var i = 0; i < actions.length; i++) {
      var action = actions[i]
      var tmpl = template
      if (action.actions) {
        tmpl = ds.templates["action-menu-button"]
      }
      html += tmpl({
        category: category,
        action: action
      })
    }
    return new Handlebars.SafeString(html)
  } else {
    return ''
  }
})

Handlebars.registerHelper('dashboards-tagged', function(tag) {
  var markdown = ''
  $.ajax({
    url: '/api/dashboard/tagged/' + tag,
    type: 'GET',
    async: false,
    success: function(data) {
      for (var i = 0; i < data.length; i++) {
        var d = data[i]
        markdown += '  * ['
        if (d.category && d.category !== '') {
          markdown += d.category + ': '
        }
        markdown += d.title + ']('
        markdown += d.view_href + ')\n'
      }
    },
    error: function() {
      var error = 'Unable to retrieve list of dashboards tagged "' + tag + '"'
      ds.manager.warning(error)
      markdown = error
    }
  })
  return new Handlebars.SafeString(markdown)
})
;this["ds"] = this["ds"] || {};
this["ds"]["templates"] = this["ds"]["templates"] || {};

this["ds"] = this["ds"] || {};
this["ds"]["templates"] = this["ds"]["templates"] || {};
this["ds"]["templates"]["edit"] = this["ds"]["templates"]["edit"] || {};

this["ds"] = this["ds"] || {};
this["ds"]["templates"] = this["ds"]["templates"] || {};
this["ds"]["templates"]["flot"] = this["ds"]["templates"]["flot"] || {};

this["ds"] = this["ds"] || {};
this["ds"]["templates"] = this["ds"]["templates"] || {};
this["ds"]["templates"]["listing"] = this["ds"]["templates"]["listing"] || {};

this["ds"] = this["ds"] || {};
this["ds"]["templates"] = this["ds"]["templates"] || {};
this["ds"]["templates"]["models"] = this["ds"]["templates"]["models"] || {};

Handlebars.registerPartial("action-menu-button", this["ds"]["templates"]["action-menu-button"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;

function program1(depth0,data) {
  
  var buffer = "", stack1, helper;
  buffer += "\n  <li role=\"presentation\"\n    data-ds-action=\"";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\"\n    data-ds-category=\"";
  if (helper = helpers.category) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.category); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\"\n    data-ds-show=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.show)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"\n    data-ds-hide=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.hide)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    <a role=\"menuitem\" href=\"#\"><i class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.icon)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"></i> "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.display)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</a>\n";
  return buffer;
  }

  stack1 = helpers.each.call(depth0, (depth0 && depth0.actions), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n";
  return buffer;
  }));

Handlebars.registerPartial("ds-action-menu", this["ds"]["templates"]["ds-action-menu"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;


  buffer += "<div data-ds-hide=\"edit\" class=\"btn-group\" align=\"left\">\n  <button type=\"button\"\n          class=\"btn btn-default btn-xs dropdown-toggle\"\n          data-toggle=\"dropdown\">\n    <span class=\"caret\"></span>\n  </button>\n  <ul class=\"dropdown-menu dropdown-menu-right ds-action-menu\" role=\"menu\">\n    "
    + escapeExpression((helper = helpers.actions || (depth0 && depth0.actions),options={hash:{},data:data},helper ? helper.call(depth0, "presentation-transform-actions", options) : helperMissing.call(depth0, "actions", "presentation-transform-actions", options)))
    + "\n    "
    + escapeExpression((helper = helpers.actions || (depth0 && depth0.actions),options={hash:{},data:data},helper ? helper.call(depth0, "presentation-actions", true, options) : helperMissing.call(depth0, "actions", "presentation-actions", true, options)))
    + "\n    "
    + escapeExpression((helper = helpers.actions || (depth0 && depth0.actions),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_type), true, options) : helperMissing.call(depth0, "actions", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_type), true, options)))
    + "\n  </ul>\n</div>\n";
  return buffer;
  }));

Handlebars.registerPartial("ds-dashboard-listing-action-menu", this["ds"]["templates"]["ds-dashboard-listing-action-menu"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;


  buffer += "<div class=\"btn-group\">\n  <button\n     type=\"button\"\n     class=\"btn btn-default btn-xs dropdown-toggle\"\n     data-toggle=\"dropdown\">\n    <span class=\"caret\"></span>\n  </button>\n  <ul class=\"dropdown-menu dropdown-menu-right ds-dashboard-action-menu\" role=\"menu\" data-ds-href=\"";
  if (helper = helpers.href) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.href); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\" data-ds-view-href=\"";
  if (helper = helpers.view_href) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.view_href); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\">\n\n    "
    + escapeExpression((helper = helpers.actions || (depth0 && depth0.actions),options={hash:{},data:data},helper ? helper.call(depth0, "dashboard-list-actions", options) : helperMissing.call(depth0, "actions", "dashboard-list-actions", options)))
    + "\n  </ul>\n</div>\n";
  return buffer;
  }));

Handlebars.registerPartial("ds-dashboard-listing-entry", this["ds"]["templates"]["ds-dashboard-listing-entry"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, self=this, helperMissing=helpers.helperMissing;

function program1(depth0,data) {
  
  var buffer = "", stack1, helper;
  buffer += "\n            <br/>\n            <small>";
  if (helper = helpers.summary) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.summary); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "</small>\n            ";
  return buffer;
  }

function program3(depth0,data) {
  
  var buffer = "", stack1;
  buffer += "\n          ";
  stack1 = self.invokePartial(partials['ds-dashboard-tag-with-link'], 'ds-dashboard-tag-with-link', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n          ";
  return buffer;
  }

function program5(depth0,data) {
  
  var buffer = "", stack1, helper;
  buffer += "\n          <a href=\"";
  if (helper = helpers.imported_from) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.imported_from); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\" target=\"_blank\"><i class=\"fa fa-cloud\"></i></a><br/>\n          ";
  return buffer;
  }

  buffer += "<tr data-ds-href=\"";
  if (helper = helpers.href) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.href); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\">\n  <td>\n    <div class=\"row\">\n      <div class=\"\"> <!-- col-md-1 -->\n        <!-- <a href=\"/dashboards/";
  if (helper = helpers.id) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.id); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\"><i class=\"fa fa-2x fa-dashboard\"></i></a> -->\n      </div>\n      <div class=\"col-md-12\">\n\n        <div class=\"pull-left\">\n          <a href=\"";
  if (helper = helpers.view_href) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.view_href); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\">\n            <span class=\"ds-dashboard-listing-category\">\n              ";
  if (helper = helpers.category) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.category); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\n            </span>\n            <span class=\"ds-dashboard-listing-title\">\n              ";
  if (helper = helpers.title) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.title); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\n            </span>\n            ";
  stack1 = helpers['if'].call(depth0, (depth0 && depth0.summary), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n          </a><br/>\n          <span class=\"ds-dashboard-listing-last-modified\">\n            Last modified "
    + escapeExpression((helper = helpers.moment || (depth0 && depth0.moment),options={hash:{},data:data},helper ? helper.call(depth0, "fromNow", (depth0 && depth0.last_modified_date), options) : helperMissing.call(depth0, "moment", "fromNow", (depth0 && depth0.last_modified_date), options)))
    + ".\n          </span>\n        </div>\n\n        <div class=\"pull-right\" style=\"margin-left: 0.5em\">\n          ";
  stack1 = self.invokePartial(partials['ds-dashboard-listing-action-menu'], 'ds-dashboard-listing-action-menu', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n        </div>\n\n        <div class=\"pull-right\" style=\"text-align:right\">\n          ";
  stack1 = helpers.each.call(depth0, (depth0 && depth0.tags), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n          <br/>\n          ";
  stack1 = helpers['if'].call(depth0, (depth0 && depth0.imported_from), {hash:{},inverse:self.noop,fn:self.program(5, program5, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n        </div>\n\n      </div>\n\n    </div> <!-- row -->\n  </td>\n</tr>\n";
  return buffer;
  }));

Handlebars.registerPartial("ds-dashboard-tag-with-link", this["ds"]["templates"]["ds-dashboard-tag-with-link"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, functionType="function", escapeExpression=this.escapeExpression, self=this;


  buffer += "<a href=\"/dashboards/tagged/";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\">";
  stack1 = self.invokePartial(partials['ds-dashboard-tag'], 'ds-dashboard-tag', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "</a>\n";
  return buffer;
  }));

Handlebars.registerPartial("ds-dashboard-tag", this["ds"]["templates"]["ds-dashboard-tag"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, functionType="function", escapeExpression=this.escapeExpression, self=this;

function program1(depth0,data) {
  
  var buffer = "", stack1, helper;
  buffer += "\n    background-color: ";
  if (helper = helpers.bgcolor) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.bgcolor); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + ";\n  ";
  return buffer;
  }

function program3(depth0,data) {
  
  var buffer = "", stack1, helper;
  buffer += "\n    color: ";
  if (helper = helpers.fgcolor) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.fgcolor); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + ";\n  ";
  return buffer;
  }

  buffer += "<span class=\"badge badge-neutral\" data-ds-tag=\"";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\"\n  style=\"\n  ";
  stack1 = helpers['if'].call(depth0, (depth0 && depth0.bgcolor), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  ";
  stack1 = helpers['if'].call(depth0, (depth0 && depth0.fgcolor), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += ">\n  \">\n  ";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\n</span>\n";
  return buffer;
  }));

Handlebars.registerPartial("ds-edit-bar-cell", this["ds"]["templates"]["ds-edit-bar-cell"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;


  buffer += "<div class=\"ds-edit-bar ds-edit-bar-cell alert alert-info\" align=\"left\" data-ds-item-id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  <span class=\"badge ds-badge-cell\" data-ds-item-id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"><i class=\"fa fa-cog\"></i> cell "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</span>\n  <div class=\"btn-group btn-group-sm\" data-ds-item-id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\" style=\"display:none\">\n    "
    + escapeExpression((helper = helpers.actions || (depth0 && depth0.actions),options={hash:{},data:data},helper ? helper.call(depth0, "edit-bar-cell", "button", options) : helperMissing.call(depth0, "actions", "edit-bar-cell", "button", options)))
    + "\n  </div>\n</div>\n";
  return buffer;
  }));

Handlebars.registerPartial("ds-edit-bar-item-details", this["ds"]["templates"]["ds-edit-bar-item-details"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;


  buffer += "<div class=\"row\" id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "-details\">\n  <div class=\"col-md-12\">\n    ";
  stack1 = self.invokePartial(partials['ds-item-property-sheet'], 'ds-item-property-sheet', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  </div>\n</div>\n";
  return buffer;
  }));

Handlebars.registerPartial("ds-edit-bar-item", this["ds"]["templates"]["ds-edit-bar-item"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;


  buffer += "<div class=\"row\" data-ds-show=\"edit\" style=\"display:none\">\n  <div class=\"col-md-12\">\n    <div class=\"ds-edit-bar ds-edit-bar-item\" align=\"left\" data-ds-item-id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n      <span class=\"badge ds-badge-item\" data-ds-item-id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"><i class=\"fa fa-cog\"></i> "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_type)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + " "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</span>\n      <div class=\"btn-group btn-group-sm\" data-ds-item-id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\" style=\"display:none\">\n        "
    + escapeExpression((helper = helpers.actions || (depth0 && depth0.actions),options={hash:{},data:data},helper ? helper.call(depth0, "edit-bar-item", "button", options) : helperMissing.call(depth0, "actions", "edit-bar-item", "button", options)))
    + "\n      </div>\n    </div>\n  </div>\n</div>\n";
  return buffer;
  }));

Handlebars.registerPartial("ds-edit-bar-row", this["ds"]["templates"]["ds-edit-bar-row"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;


  buffer += "    <div class=\"ds-edit-bar ds-edit-bar-row\" data-ds-item-id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n      <span class=\"badge ds-badge-row\" data-ds-item-id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"><i class=\"fa fa-cog\"></i> row "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</span>\n      <div class=\"btn-group btn-group-sm\" data-ds-item-id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\" style=\"display:none\">\n        "
    + escapeExpression((helper = helpers.actions || (depth0 && depth0.actions),options={hash:{},data:data},helper ? helper.call(depth0, "edit-bar-row", "button", options) : helperMissing.call(depth0, "actions", "edit-bar-row", "button", options)))
    + "\n      </div>\n    </div>\n";
  return buffer;
  }));

Handlebars.registerPartial("ds-edit-bar-section", this["ds"]["templates"]["ds-edit-bar-section"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;


  buffer += "    <div class=\"ds-edit-bar ds-edit-bar-section\" align=\"left\" data-ds-item-id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n      <span class=\"badge ds-badge-section\" data-ds-item-id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"><i class=\"fa fa-cog\"></i> section "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</span>\n      <div class=\"btn-group btn-group-sm\" data-ds-item-id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\" style=\"display:none\">\n        "
    + escapeExpression((helper = helpers.actions || (depth0 && depth0.actions),options={hash:{},data:data},helper ? helper.call(depth0, "edit-bar-section", "button", options) : helperMissing.call(depth0, "actions", "edit-bar-section", "button", options)))
    + "\n      </div>\n    </div>\n";
  return buffer;
  }));

Handlebars.registerPartial("ds-edit-bar", this["ds"]["templates"]["ds-edit-bar"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;


  buffer += "<div class=\"row\" data-ds-show=\"edit\" style=\"display:none\">\n  <div class=\"col-md-12\">\n    <i title=\"Drag to reposition\" class=\"fa fa-align-justify pull-right ds-drag-handle\" data-ds-item-id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"></i>\n    "
    + escapeExpression((helper = helpers['ds-edit-bar'] || (depth0 && depth0['ds-edit-bar']),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "ds-edit-bar", (depth0 && depth0.item), options)))
    + "\n  </div>\n</div>\n";
  return buffer;
  }));

Handlebars.registerPartial("ds-edit-menu", this["ds"]["templates"]["ds-edit-menu"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;


  buffer += "<div data-ds-show=\"edit\" class=\"btn-group\" align=\"left\" style=\"display:none\">\n  <button type=\"button\"\n          class=\"btn btn-default btn-xs dropdown-toggle\"\n          data-toggle=\"dropdown\">\n          <i class=\"fa fa-cog\"></i> <span class=\"caret\"></span>\n  </button>\n  <ul class=\"dropdown-menu dropdown-menu-right ds-edit-menu\" role=\"menu\">\n    "
    + escapeExpression((helper = helpers.actions || (depth0 && depth0.actions),options={hash:{},data:data},helper ? helper.call(depth0, "presentation-edit", options) : helperMissing.call(depth0, "actions", "presentation-edit", options)))
    + "\n  </ul>\n</div>\n";
  return buffer;
  }));

Handlebars.registerPartial("ds-row-edit-bar", this["ds"]["templates"]["ds-row-edit-bar"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  


  return "<div class=\"row\" data-ds-show=\"edit\">\n  <div class=\"col-md-12\">\n    <div class=\"ds-edit-bar ds-row-edit-bar bs-callout bs-callout-info\">\n\n\n\n\n      row <i class=\"fa fa-trash-o pull-right\"></i>\n\n\n\n    </div>\n  </div>\n</div>\n";
  }));

Handlebars.registerPartial("ds-title-bar", this["ds"]["templates"]["ds-title-bar"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing, self=this;

function program1(depth0,data) {
  
  var buffer = "", stack1;
  buffer += "<h3>"
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.title)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</h3>";
  return buffer;
  }

  buffer += escapeExpression((helper = helpers['ds-edit-bar'] || (depth0 && depth0['ds-edit-bar']),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "ds-edit-bar", (depth0 && depth0.item), options)))
    + "\n<div class=\"row\">\n  <div class=\"col-md-10\">\n    ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.title), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  </div>\n  <div class=\"col-md-2\" align=\"right\">\n    ";
  stack1 = self.invokePartial(partials['ds-action-menu'], 'ds-action-menu', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  </div>\n</div>\n";
  return buffer;
  }));

Handlebars.registerPartial("dashboard-metadata-panel", this["ds"]["templates"]["edit"]["dashboard-metadata-panel"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, options, self=this, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;

function program1(depth0,data) {
  
  var buffer = "", stack1;
  buffer += "\n            ";
  stack1 = self.invokePartial(partials['ds-dashboard-tag'], 'ds-dashboard-tag', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n            ";
  return buffer;
  }

function program3(depth0,data) {
  
  var buffer = "", stack1, helper;
  buffer += "\n          <tr>\n            <th>Imported From</th><td class=\"ds-editable\" id=ds-info-panel-edit-imported-from\" data-type=\"text\"><a href=\"";
  if (helper = helpers.imported_from) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.imported_from); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\" target=\"_blank\">Link</a></td>\n          </tr>\n        ";
  return buffer;
  }

  buffer += "<div class=\"row ds-info-edit-panel\">\n  <!-- Column 1 -->\n  <div class=\"col-md-5\">\n    <h4>Properties</h4>\n\n    <table class=\"table-condensed\">\n      <tbody>\n        <tr>\n          <th>Title</th><td class=\"ds-editable\" id=\"ds-info-panel-edit-title\" data-type=\"text\">";
  if (helper = helpers.title) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.title); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "</td>\n        </tr>\n\n        <tr>\n          <th>Category</th><td class=\"ds-editable\" id=\"ds-info-panel-edit-category\" data-type=\"text\">";
  if (helper = helpers.category) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.category); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "</td>\n        </tr>\n\n        <tr>\n          <th>Summary</th><td class=\"ds-editable\" id=\"ds-info-panel-edit-summary\" data-type=\"text\">";
  if (helper = helpers.summary) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.summary); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "</td>\n        </tr>\n\n        <tr>\n          <th>Tags</th>\n          <td>\n            <!--\n            ";
  stack1 = helpers.each.call(depth0, (depth0 && depth0.tags), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n            -->\n\n            <input id=\"ds-info-panel-edit-tags\" class=\"form-control tm-input tm-input-success tm-input-info\"/>\n\n          </td>\n        </tr>\n\n        <tr>\n          <th>Created</th><td>"
    + escapeExpression((helper = helpers.moment || (depth0 && depth0.moment),options={hash:{},data:data},helper ? helper.call(depth0, "MMMM Do YYYY, h:mm:ss a", (depth0 && depth0.creation_date), options) : helperMissing.call(depth0, "moment", "MMMM Do YYYY, h:mm:ss a", (depth0 && depth0.creation_date), options)))
    + "</td>\n        </tr>\n        <tr>\n          <th>Last Modified</th><td>"
    + escapeExpression((helper = helpers.moment || (depth0 && depth0.moment),options={hash:{},data:data},helper ? helper.call(depth0, "MMMM Do YYYY, h:mm:ss a", (depth0 && depth0.last_modified_date), options) : helperMissing.call(depth0, "moment", "MMMM Do YYYY, h:mm:ss a", (depth0 && depth0.last_modified_date), options)))
    + "</td>\n        </tr>\n\n        ";
  stack1 = helpers['if'].call(depth0, (depth0 && depth0.imported_from), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n\n      </tbody>\n    </table>\n  </div> <!-- column -->\n\n  <!-- Column 2 -->\n  <div class=\"col-md-7\">\n    <h4>Description</h4>\n    <div class=\"ds-editable\"\n         id=\"ds-info-panel-edit-description\"\n         data-type=\"textarea\"\n         data-rows=\"9\"\n         data-showbuttons=\"bottom\"\n         data-inputclass=\"ds-dashboard-description\"\n         style=\"text-align:top;\">\n      "
    + escapeExpression((helper = helpers.markdown || (depth0 && depth0.markdown),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.description), options) : helperMissing.call(depth0, "markdown", (depth0 && depth0.description), options)))
    + "\n    </div>\n  </div>\n\n</div>\n";
  return buffer;
  }));

Handlebars.registerPartial("dashboard-query-panel", this["ds"]["templates"]["edit"]["dashboard-query-panel"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, options, self=this, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;

function program1(depth0,data) {
  
  var buffer = "", stack1;
  buffer += "\n          ";
  stack1 = self.invokePartial(partials['dashboard-query-row'], 'dashboard-query-row', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n        ";
  return buffer;
  }

  buffer += "<div class=\"ds-query-edit-panel\" id=\"ds-query-panel\">\n\n  <div class=\"row\">\n    <div class=\"col-md-12\">\n      <div class=\"btn-group btn-group-sm\">\n        "
    + escapeExpression((helper = helpers.actions || (depth0 && depth0.actions),options={hash:{},data:data},helper ? helper.call(depth0, "dashboard-queries", "button", options) : helperMissing.call(depth0, "actions", "dashboard-queries", "button", options)))
    + "\n      </div>\n      <br/>\n    </div>\n  </div>\n\n  <div class=\"row\">\n    <div class=\"col-md-12\">\n      <table class=\"table table-striped\">\n        ";
  stack1 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.definition)),stack1 == null || stack1 === false ? stack1 : stack1.queries), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n      </table>\n    </div>\n  </div>\n\n</div>\n";
  return buffer;
  }));

Handlebars.registerPartial("dashboard-query-row", this["ds"]["templates"]["edit"]["dashboard-query-row"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, functionType="function", escapeExpression=this.escapeExpression;


  buffer += "<tr data-ds-query-name=\"";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\">\n  <th data-ds-query-name=\"";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\" class=\"ds-query-name\">";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "</th>\n  <td data-ds-query-name=\"";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\" class=\"ds-query-target\">";
  if (helper = helpers.targets) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.targets); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "</td>\n  <td>\n    <div class=\"btn-group\">\n      <button class=\"btn btn-default btn-small ds-duplicate-query-button\"\n              data-toggle=\"tooltip\"\n              title=\"Duplicate this query\"\n              data-ds-query-name=\"";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\">\n        <i class=\"fa fa-copy\"></i>\n      </button>\n      <button class=\"btn btn-default btn-small ds-delete-query-button\"\n              data-toggle=\"tooltip\"\n              title=\"Delete this query\"\n              data-ds-query-name=\"";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\">\n        <i class=\"fa fa-trash-o\"></i>\n      </button>\n    </div>\n  </td>\n</tr>\n";
  return buffer;
  }));

Handlebars.registerPartial("ds-item-property-sheet", this["ds"]["templates"]["edit"]["ds-item-property-sheet"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this, helperMissing=helpers.helperMissing;

function program1(depth0,data,depth1) {
  
  var buffer = "", stack1, helper, options;
  buffer += "\n      <tr>\n        <td><span class=\"ds-property-category\">";
  stack1 = helpers['if'].call(depth0, (depth0 && depth0.category), {hash:{},inverse:self.noop,fn:self.program(2, program2, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "<span class=\"ds-property-name\">";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "</span></td>\n        <td data-ds-property-name=\"";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\">"
    + escapeExpression((helper = helpers.interactive_property || (depth0 && depth0.interactive_property),options={hash:{},data:data},helper ? helper.call(depth0, depth0, (depth1 && depth1.item), options) : helperMissing.call(depth0, "interactive_property", depth0, (depth1 && depth1.item), options)))
    + "</td>\n      </tr>\n    ";
  return buffer;
  }
function program2(depth0,data) {
  
  var buffer = "", stack1, helper;
  if (helper = helpers.category) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.category); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "</span> / ";
  return buffer;
  }

  buffer += "<table class=\"ds-property-sheet\">\n  <tbody>\n    <tr><th>Property</th><th>Value</th></tr>\n    ";
  stack1 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.interactive_properties), {hash:{},inverse:self.noop,fn:self.programWithDepth(1, program1, data, depth0),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  </tbody>\n</table>\n";
  return buffer;
  }));

this["ds"]["templates"]["action-menu-button"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;

function program1(depth0,data) {
  
  var buffer = "", stack1;
  buffer += "\n      ";
  stack1 = helpers['if'].call(depth0, (depth0 && depth0.divider), {hash:{},inverse:self.program(4, program4, data),fn:self.program(2, program2, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n    ";
  return buffer;
  }
function program2(depth0,data) {
  
  var buffer = "", stack1, helper;
  buffer += "\n        <li class=\"divider\" data-ds-show=\"";
  if (helper = helpers.show) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.show); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\" data-ds-hide=\"";
  if (helper = helpers.hide) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.hide); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\"/>\n      ";
  return buffer;
  }

function program4(depth0,data) {
  
  var buffer = "", stack1, helper;
  buffer += "\n        <li class=\"";
  if (helper = helpers['class']) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0['class']); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\"\n          role=\"presentation\"\n          data-ds-action=\"";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\"\n          data-ds-category=\"";
  if (helper = helpers.category) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.category); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\"\n          data-ds-show=\"";
  if (helper = helpers.show) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.show); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\"\n          data-ds-hide=\"";
  if (helper = helpers.hide) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.hide); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\">\n          <a role=\"menuitem\" href=\"#\"><i class=\"fa-fw ";
  if (helper = helpers.icon) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.icon); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\"></i> ";
  if (helper = helpers.display) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.display); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "</a>\n        </li>\n      ";
  return buffer;
  }

  buffer += "<div class=\"btn-group btn-group-sm\" align=\"left\">\n  <button type=\"button\"\n    class=\"btn btn-default dropdown-toggle "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1['class'])),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"\n    data-toggle=\"dropdown\"\n    title=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.display)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    <i class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.icon)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"></i> <span class=\"caret\"></span>\n  </button>\n  <ul class=\"dropdown-menu dropdown-menu-left ds-edit-menu\" role=\"menu\">\n    ";
  stack1 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.actions), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  </ul>\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["action"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;

function program1(depth0,data) {
  
  var buffer = "", stack1;
  buffer += "\n  <li class=\"divider\" data-ds-show=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.show)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\" data-ds-hide=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.hide)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"/>\n";
  return buffer;
  }

function program3(depth0,data) {
  
  var buffer = "", stack1, helper;
  buffer += "\n  <li role=\"presentation\"\n    data-ds-action=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.name)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"\n    data-ds-category=\"";
  if (helper = helpers.category) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.category); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\"\n    data-ds-show=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.show)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"\n    data-ds-hide=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.hide)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    <a role=\"menuitem\" href=\"#\"><i class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.icon)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"></i> "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.display)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</a>\n  </li>\n";
  return buffer;
  }

  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.divider), {hash:{},inverse:self.program(3, program3, data),fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n";
  return buffer;
  });

this["ds"]["templates"]["action_button"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;

function program1(depth0,data) {
  
  
  return "\n";
  }

function program3(depth0,data) {
  
  var buffer = "", stack1, helper;
  buffer += "\n  <button class=\"btn ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1['class']), {hash:{},inverse:self.program(6, program6, data),fn:self.program(4, program4, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\"\n    data-ds-action=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.name)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"\n    data-ds-category=\"";
  if (helper = helpers.category) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.category); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\"\n    data-ds-show=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.show)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"\n    data-ds-hide=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.hide)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"\n    data-toggle=\"tooltip\"\n    title=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.display)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    <i class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.icon)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"></i>\n  </button>\n";
  return buffer;
  }
function program4(depth0,data) {
  
  var stack1;
  return escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1['class'])),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
  }

function program6(depth0,data) {
  
  
  return "btn-default";
  }

  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.action)),stack1 == null || stack1 === false ? stack1 : stack1.divider), {hash:{},inverse:self.program(3, program3, data),fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n";
  return buffer;
  });

this["ds"]["templates"]["edit"]["dashboard_panel"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, self=this;


  buffer += "<ul class=\"nav nav-pills\">\n  <li class=\"active\"><a href=\"#metadata\" data-toggle=\"tab\">Metadata</a></li>\n  <li><a href=\"#queries\" data-toggle=\"tab\">Queries</a></li>\n</ul>\n\n<div class=\"tab-content\">\n  <div class=\"tab-pane active\" id=\"metadata\">\n    ";
  stack1 = self.invokePartial(partials['dashboard-metadata-panel'], 'dashboard-metadata-panel', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  </div>\n  <div class=\"tab-pane\" id=\"queries\">\n    ";
  stack1 = self.invokePartial(partials['dashboard-query-panel'], 'dashboard-query-panel', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  </div>\n\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["edit"]["item_source"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;


  buffer += "<div class=\"ds-item-source\">\n  <pre>"
    + escapeExpression((helper = helpers.json || (depth0 && depth0.json),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "json", (depth0 && depth0.item), options)))
    + "</pre>\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["flot"]["tooltip"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing, self=this;

function program1(depth0,data) {
  
  var buffer = "", stack1, helper, options;
  buffer += "\n      <tr>\n        <td class=\"ds-tooltip-label\">\n          <span class=\"badge\" style=\"background-color: "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.series)),stack1 == null || stack1 === false ? stack1 : stack1.color)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"><i></i></span>\n          "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.series)),stack1 == null || stack1 === false ? stack1 : stack1.label)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\n        </td>\n        <td class=\"ds-tooltip-value\">\n          "
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ",.3s", (depth0 && depth0.value), options) : helperMissing.call(depth0, "format", ",.3s", (depth0 && depth0.value), options)))
    + "\n        </td>\n      </tr>\n    ";
  return buffer;
  }

  buffer += "<table class=\"table-condensed\">\n  <tbody>\n    <tr>\n      <td>\n        <span class=\"ds-tooltip-time\">"
    + escapeExpression((helper = helpers.moment || (depth0 && depth0.moment),options={hash:{},data:data},helper ? helper.call(depth0, "dd, h:mm A", (depth0 && depth0.time), options) : helperMissing.call(depth0, "moment", "dd, h:mm A", (depth0 && depth0.time), options)))
    + "</span>\n      </td>\n    </tr>\n    ";
  stack1 = helpers.each.call(depth0, (depth0 && depth0.items), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  </tbody>\n</table>\n";
  return buffer;
  });

this["ds"]["templates"]["listing"]["dashboard_list"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, self=this;

function program1(depth0,data) {
  
  var buffer = "", stack1;
  buffer += "\n      ";
  stack1 = self.invokePartial(partials['ds-dashboard-listing-entry'], 'ds-dashboard-listing-entry', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n    ";
  return buffer;
  }

function program3(depth0,data) {
  
  
  return "\n      <tr><td><h3>No dashboards defined</h3></td></tr>\n    ";
  }

  buffer += "<table class=\"table table-hover table-condensed\">\n  <tbody>\n    ";
  stack1 = helpers.each.call(depth0, (depth0 && depth0.dashboards), {hash:{},inverse:self.program(3, program3, data),fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  </tbody>\n</table>\n";
  return buffer;
  });

this["ds"]["templates"]["listing"]["dashboard_tag_list"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;

function program1(depth0,data) {
  
  
  return "active";
  }

function program3(depth0,data) {
  
  var buffer = "", stack1, helper;
  buffer += "\n  <li data-ds-tag=\"";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\">\n    <a href=\"/dashboards/tagged/";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\">\n      ";
  if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\n      <span class=\"badge badge-primary pull-right\">";
  if (helper = helpers.count) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.count); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "</span>\n    </a>\n  </li>\n  ";
  return buffer;
  }

  buffer += "<ul class=\"nav nav-pills nav-stacked\">\n  <li class=\"";
  stack1 = helpers.unless.call(depth0, (depth0 && depth0.tag), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\">\n    <a href=\"/dashboards\">\n      All <span class=\"badge badge-primary pull-right\"></span>\n    </a>\n  </li>\n\n    ";
  stack1 = helpers.each.call(depth0, (depth0 && depth0.tags), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n</ul>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["cell"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing, self=this;

function program1(depth0,data) {
  
  var buffer = "", stack1;
  buffer += " align=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.align)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\" ";
  return buffer;
  }

function program3(depth0,data) {
  
  var buffer = "", helper, options;
  buffer += "<div class=\""
    + escapeExpression((helper = helpers.style_class || (depth0 && depth0.style_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "style_class", (depth0 && depth0.item), options)))
    + "\">";
  return buffer;
  }

function program5(depth0,data) {
  
  var buffer = "", helper, options;
  buffer += "\n    "
    + escapeExpression((helper = helpers.item || (depth0 && depth0.item),options={hash:{},data:data},helper ? helper.call(depth0, depth0, options) : helperMissing.call(depth0, "item", depth0, options)))
    + "\n    ";
  return buffer;
  }

function program7(depth0,data) {
  
  
  return "</div>";
  }

  buffer += "<div class=\"ds-cell "
    + escapeExpression((helper = helpers.span || (depth0 && depth0.span),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "span", (depth0 && depth0.item), options)))
    + " "
    + escapeExpression((helper = helpers.offset || (depth0 && depth0.offset),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "offset", (depth0 && depth0.item), options)))
    + " "
    + escapeExpression((helper = helpers.css_class || (depth0 && depth0.css_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "css_class", (depth0 && depth0.item), options)))
    + " "
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + "\"\n     ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.align), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += " id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"\n       >\n       ";
  stack1 = self.invokePartial(partials['ds-edit-bar'], 'ds-edit-bar', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.style), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n\n    ";
  stack1 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.items), {hash:{},inverse:self.noop,fn:self.program(5, program5, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n\n    ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.style), {hash:{},inverse:self.noop,fn:self.program(7, program7, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n      <i data-ds-show=\"\"\n        style=\"display:none\"\n        title=\"Drag to resize\"\n        class=\"fa fa-arrows-alt pull-right ds-resize-handle\"\n        data-ds-item-id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"></i>\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["comparison_summation_table"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, self=this, helperMissing=helpers.helperMissing;

function program1(depth0,data) {
  
  
  return "table-striped";
  }

function program3(depth0,data) {
  
  var stack1;
  return escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.query_other)),stack1 == null || stack1 === false ? stack1 : stack1.name)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
  }

function program5(depth0,data) {
  
  var stack1;
  return escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.query)),stack1 == null || stack1 === false ? stack1 : stack1.name)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
  }

  buffer += "<div class=\"ds-item\" id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  ";
  stack1 = self.invokePartial(partials['ds-title-bar'], 'ds-title-bar', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  <table class=\"table table-condensed ds-timeshift-summation-table "
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + " "
    + escapeExpression((helper = helpers.css_class || (depth0 && depth0.css_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "css_class", (depth0 && depth0.item), options)))
    + "\n                ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.striped), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\">\n    <thead>\n      <tr>\n        <th></th>\n        <th>";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.query_other), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "</th>\n        <th>";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.query), {hash:{},inverse:self.noop,fn:self.program(5, program5, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "</th>\n        <th>Delta</th>\n        <th>%</th>\n      </tr>\n    </thead>\n    <tbody/>\n  </table>\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["comparison_summation_table_body"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function";


  buffer += "<!-- TODO: configure which rows to include. Sum doesnt make sense for rates -->\n<tr>\n  <th>Average</th>\n    <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.mean), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.mean), options)))
    + "</td>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.mean), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.mean), options)))
    + "</td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.mean_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.mean), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.mean), options)))
    + "\n  </td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.mean_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.mean_pct)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\n  </td>\n</tr>\n<tr>\n  <th>Min</th>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.min), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.min), options)))
    + "</td>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.min), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.min), options)))
    + "</td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.min_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.min), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.min), options)))
    + "\n  </td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.min_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.min_pct)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\n  </td>\n</tr>\n<tr>\n  <th>Max</th>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.max), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.max), options)))
    + "</td>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.max), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.max), options)))
    + "</td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.max_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.max), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.max), options)))
    + "\n  </td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.max_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.max_pct)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\n  </td>\n</tr>\n<tr>\n  <th>Sum</th>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options)))
    + "</td>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options)))
    + "</td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.sum_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options)))
    + "\n  </td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.sum_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.sum_pct)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\n  </td>\n</tr>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["definition"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function", self=this;

function program1(depth0,data) {
  
  var buffer = "", helper, options;
  buffer += "\n    "
    + escapeExpression((helper = helpers.item || (depth0 && depth0.item),options={hash:{},data:data},helper ? helper.call(depth0, depth0, options) : helperMissing.call(depth0, "item", depth0, options)))
    + "\n  ";
  return buffer;
  }

  buffer += "<div class=\""
    + escapeExpression((helper = helpers.css_class || (depth0 && depth0.css_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "css_class", (depth0 && depth0.item), options)))
    + " ds-dashboard\" id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  ";
  stack1 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.items), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["donut_chart"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function", self=this;


  buffer += "<div class=\"ds-item ds-graph ds-donut-chart "
    + escapeExpression((helper = helpers.css_class || (depth0 && depth0.css_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "css_class", (depth0 && depth0.item), options)))
    + "\"\n  id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  ";
  stack1 = self.invokePartial(partials['ds-title-bar'], 'ds-title-bar', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  <div class=\"ds-graph-holder "
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + "\">\n    <svg class=\""
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + "\"></svg>\n  </div>\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["heading"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing, self=this;

function program1(depth0,data) {
  
  var buffer = "", stack1;
  buffer += "class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.css_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"";
  return buffer;
  }

function program3(depth0,data) {
  
  var buffer = "", stack1;
  buffer += "<small>"
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.description)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</small>";
  return buffer;
  }

  buffer += "<div class=\"ds-item ds-heading\" id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  "
    + escapeExpression((helper = helpers['ds-edit-bar'] || (depth0 && depth0['ds-edit-bar']),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "ds-edit-bar", (depth0 && depth0.item), options)))
    + "\n  <h"
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.level)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + " ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.css_class), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += ">\n    "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.text)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + " ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.description), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n      </h"
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.level)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + ">\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["jumbotron_singlestat"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;


  buffer += "<div class=\"ds-item ds-jumbotron-singlestat\" id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  "
    + escapeExpression((helper = helpers['ds-edit-bar'] || (depth0 && depth0['ds-edit-bar']),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "ds-edit-bar", (depth0 && depth0.item), options)))
    + "\n  <div class=\""
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + " "
    + escapeExpression((helper = helpers.css_class || (depth0 && depth0.css_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "css_class", (depth0 && depth0.item), options)))
    + "\"\n    >\n    <div><p><span class=\"value\"></span><span class=\"unit\">"
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.units)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</span></p></div>\n    <div><h3>"
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.title)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</h3></div>\n  </div>\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["markdown"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing, self=this;

function program1(depth0,data) {
  
  var buffer = "", stack1;
  buffer += "\n    <pre>"
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.text)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</pre>\n  ";
  return buffer;
  }

function program3(depth0,data) {
  
  var buffer = "", stack1;
  buffer += "\n   ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.expanded_text), {hash:{},inverse:self.program(6, program6, data),fn:self.program(4, program4, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  ";
  return buffer;
  }
function program4(depth0,data) {
  
  var stack1, helper, options;
  return escapeExpression((helper = helpers.markdown || (depth0 && depth0.markdown),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.expanded_text), options) : helperMissing.call(depth0, "markdown", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.expanded_text), options)));
  }

function program6(depth0,data) {
  
  var stack1, helper, options;
  return escapeExpression((helper = helpers.markdown || (depth0 && depth0.markdown),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.text), options) : helperMissing.call(depth0, "markdown", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.text), options)));
  }

  buffer += "<div class=\"ds-item ds-markdown "
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + " "
    + escapeExpression((helper = helpers.css_class || (depth0 && depth0.css_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "css_class", (depth0 && depth0.item), options)))
    + "\" id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  "
    + escapeExpression((helper = helpers['ds-edit-bar'] || (depth0 && depth0['ds-edit-bar']),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "ds-edit-bar", (depth0 && depth0.item), options)))
    + "\n  ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.raw), {hash:{},inverse:self.program(3, program3, data),fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["percentage_table"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;


  buffer += "<div class=\"ds-item\" id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  ";
  stack1 = self.invokePartial(partials['ds-title-bar'], 'ds-title-bar', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  <div class=\"ds-percentage-table-holder\">\n    <h4>No data</h4>\n  </div>\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["percentage_table_data"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing, self=this;

function program1(depth0,data) {
  
  
  return "\n        <th></th>\n        <th>Total</th>\n      ";
  }

function program3(depth0,data) {
  
  var buffer = "", stack1, helper;
  buffer += "\n        <th>";
  if (helper = helpers.target) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.target); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "</th>\n      ";
  return buffer;
  }

function program5(depth0,data) {
  
  
  return "\n        <th>%</th>\n        <th></th>\n      ";
  }

function program7(depth0,data) {
  
  var buffer = "", stack1, helper, options;
  buffer += "\n        <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ",.2%", ((stack1 = (depth0 && depth0.summation)),stack1 == null || stack1 === false ? stack1 : stack1.percent), options) : helperMissing.call(depth0, "format", ",.2%", ((stack1 = (depth0 && depth0.summation)),stack1 == null || stack1 === false ? stack1 : stack1.percent), options)))
    + "</td>\n      ";
  return buffer;
  }

function program9(depth0,data,depth1) {
  
  var buffer = "", stack1, helper, options;
  buffer += "\n      <tr>\n        <th>Total</th>\n        <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = ((stack1 = (depth0 && depth0.query)),stack1 == null || stack1 === false ? stack1 : stack1.summation)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = ((stack1 = (depth0 && depth0.query)),stack1 == null || stack1 === false ? stack1 : stack1.summation)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options)))
    + "</td>\n        ";
  stack1 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.query)),stack1 == null || stack1 === false ? stack1 : stack1.data), {hash:{},inverse:self.noop,fn:self.programWithDepth(10, program10, data, depth1),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n      </tr>\n    ";
  return buffer;
  }
function program10(depth0,data,depth2) {
  
  var buffer = "", stack1, helper, options;
  buffer += "\n          <td>"
    + escapeExpression((helper = helpers.format || (depth2 && depth2.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth2 && depth2.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.summation)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options) : helperMissing.call(depth0, "format", ((stack1 = (depth2 && depth2.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.summation)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options)))
    + "</td>\n        ";
  return buffer;
  }

  buffer += "<table class=\"table table-condensed ds-percentage-table\">\n\n  <thead>\n    <tr>\n      ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.include_sums), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n      ";
  stack1 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.query)),stack1 == null || stack1 === false ? stack1 : stack1.data), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n    </tr>\n  </thead>\n\n  <tbody>\n    <tr>\n      ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.include_sums), {hash:{},inverse:self.noop,fn:self.program(5, program5, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n      ";
  stack1 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.query)),stack1 == null || stack1 === false ? stack1 : stack1.data), {hash:{},inverse:self.noop,fn:self.program(7, program7, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n    </tr>\n\n    ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.include_sums), {hash:{},inverse:self.noop,fn:self.programWithDepth(9, program9, data, depth0),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n\n  </tbody>\n\n</table>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["row"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function", self=this;

function program1(depth0,data) {
  
  var buffer = "", helper, options;
  buffer += "<div class=\""
    + escapeExpression((helper = helpers.style_class || (depth0 && depth0.style_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "style_class", (depth0 && depth0.item), options)))
    + "\">";
  return buffer;
  }

function program3(depth0,data) {
  
  var buffer = "", helper, options;
  buffer += "\n        "
    + escapeExpression((helper = helpers.item || (depth0 && depth0.item),options={hash:{},data:data},helper ? helper.call(depth0, depth0, options) : helperMissing.call(depth0, "item", depth0, options)))
    + "\n      ";
  return buffer;
  }

function program5(depth0,data) {
  
  
  return "</div>";
  }

  buffer += "<div class=\"ds-row\" id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.style), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n\n    ";
  stack1 = self.invokePartial(partials['ds-edit-bar'], 'ds-edit-bar', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n    <div class=\"row "
    + escapeExpression((helper = helpers.css_class || (depth0 && depth0.css_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "css_class", (depth0 && depth0.item), options)))
    + "\">\n      ";
  stack1 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.items), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n    </div>\n    ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.style), {hash:{},inverse:self.noop,fn:self.program(5, program5, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["section"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function", self=this;

function program1(depth0,data) {
  
  var buffer = "", helper, options;
  buffer += "<div class=\""
    + escapeExpression((helper = helpers.style_class || (depth0 && depth0.style_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "style_class", (depth0 && depth0.item), options)))
    + "\">";
  return buffer;
  }

function program3(depth0,data) {
  
  var buffer = "", stack1;
  buffer += "\n      <div>\n        <h1 class=\"ds-section-heading\">"
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.title)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</h1>\n        <hr/>\n      </div>\n    ";
  return buffer;
  }

function program5(depth0,data) {
  
  var buffer = "", helper, options;
  buffer += "\n      "
    + escapeExpression((helper = helpers.item || (depth0 && depth0.item),options={hash:{},data:data},helper ? helper.call(depth0, depth0, options) : helperMissing.call(depth0, "item", depth0, options)))
    + "\n    ";
  return buffer;
  }

function program7(depth0,data) {
  
  
  return "</div>";
  }

  buffer += "<div class=\"ds-section "
    + escapeExpression((helper = helpers.container_class || (depth0 && depth0.container_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "container_class", (depth0 && depth0.item), options)))
    + " "
    + escapeExpression((helper = helpers.css_class || (depth0 && depth0.css_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "css_class", (depth0 && depth0.item), options)))
    + "\" id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  ";
  stack1 = self.invokePartial(partials['ds-edit-bar'], 'ds-edit-bar', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.style), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n    ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.title), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n\n    ";
  stack1 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.items), {hash:{},inverse:self.noop,fn:self.program(5, program5, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n\n    ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.style), {hash:{},inverse:self.noop,fn:self.program(7, program7, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["separator"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing, self=this;

function program1(depth0,data) {
  
  var buffer = "", stack1;
  buffer += "class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.css_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\"";
  return buffer;
  }

  buffer += "<div class=\"ds-item ds-separator\" id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  "
    + escapeExpression((helper = helpers['ds-edit-bar'] || (depth0 && depth0['ds-edit-bar']),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "ds-edit-bar", (depth0 && depth0.item), options)))
    + "\n  <hr ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.css_class), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "></hr>\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["simple_time_series"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function", self=this;


  buffer += "<div class=\"ds-item ds-graph ds-simple-time-series "
    + escapeExpression((helper = helpers.css_class || (depth0 && depth0.css_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "css_class", (depth0 && depth0.item), options)))
    + "\" id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  ";
  stack1 = self.invokePartial(partials['ds-title-bar'], 'ds-title-bar', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  <div class=\"ds-graph-holder "
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + "\">\n    <svg class=\""
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + "\"></svg>\n  </div>\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["singlegraph"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function", self=this;


  buffer += "<div class=\"ds-item ds-graph ds-singlegraph "
    + escapeExpression((helper = helpers.css_class || (depth0 && depth0.css_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "css_class", (depth0 && depth0.item), options)))
    + "\" id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  ";
  stack1 = self.invokePartial(partials['ds-title-bar'], 'ds-title-bar', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  <span class=\"ds-label\"></span>\n  <span class=\"value\"></span>\n  <div class=\"ds-graph-holder "
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + "\">\n    <svg class=\""
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + "\"></svg>\n  </div>\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["singlestat"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;


  buffer += "<div class=\"ds-item\" id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  "
    + escapeExpression((helper = helpers['ds-edit-bar'] || (depth0 && depth0['ds-edit-bar']),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "ds-edit-bar", (depth0 && depth0.item), options)))
    + "\n  <div class=\"ds-singlestat "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.css_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + " "
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + "\">\n    <h3>"
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.title)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</h3>\n    <p><span class=\"value\"></span><span class=\"unit\">"
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.units)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</span></p>\n  </div>\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["stacked_area_chart"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function", self=this;


  buffer += "<div class=\"ds-item ds-graph ds-stacked-area-chart "
    + escapeExpression((helper = helpers.css_class || (depth0 && depth0.css_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "css_class", (depth0 && depth0.item), options)))
    + "\"\n  id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  ";
  stack1 = self.invokePartial(partials['ds-title-bar'], 'ds-title-bar', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  <div class=\"ds-graph-holder "
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + "\">\n    <svg class=\""
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + "\"></svg>\n  </div>\n  <div class=\"ds-legend-holder\" id=\"ds-legend-"
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  </div>\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["standard_time_series"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function", self=this;


  buffer += "<div class=\"ds-item ds-graph ds-standard-time-series "
    + escapeExpression((helper = helpers.css_class || (depth0 && depth0.css_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "css_class", (depth0 && depth0.item), options)))
    + "\"\n  id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  ";
  stack1 = self.invokePartial(partials['ds-title-bar'], 'ds-title-bar', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  <div class=\"ds-graph-holder "
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + "\">\n    <svg class=\""
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + "\"></svg>\n  </div>\n  <div class=\"ds-legend-holder\" id=\"ds-legend-"
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  </div>\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["summation_table"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, self=this, helperMissing=helpers.helperMissing;

function program1(depth0,data) {
  
  
  return "table-striped";
  }

  buffer += "<div class=\"ds-item\" id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  ";
  stack1 = self.invokePartial(partials['ds-title-bar'], 'ds-title-bar', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  <table class=\"table table-condensed ds-summation-table "
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + " "
    + escapeExpression((helper = helpers.css_class || (depth0 && depth0.css_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "css_class", (depth0 && depth0.item), options)))
    + "\n                ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.striped), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\">\n    <thead>\n      <tr>\n        <th></th>\n        <th>current</th>\n        <th>min</th>\n        <th>max</th>\n        <th>mean</th>\n        <th>median</th>\n        <th>sum</th>\n      </tr>\n    </thead>\n    <tbody>\n    </tbody>\n  </table>\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["summation_table_row"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, self=this, helperMissing=helpers.helperMissing;

function program1(depth0,data) {
  
  var buffer = "", stack1, helper;
  buffer += "\n      <span class=\"ds-summation-color-cell\" style=\"background:";
  if (helper = helpers.color) { stack1 = helper.call(depth0, {hash:{},data:data}); }
  else { helper = (depth0 && depth0.color); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
  buffer += escapeExpression(stack1)
    + "\"></span>\n    ";
  return buffer;
  }

  buffer += "<tr>\n    <th>\n    ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.show_color), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n      "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.series)),stack1 == null || stack1 === false ? stack1 : stack1.target)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "</th>\n    <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = ((stack1 = (depth0 && depth0.series)),stack1 == null || stack1 === false ? stack1 : stack1.summation)),stack1 == null || stack1 === false ? stack1 : stack1.last), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = ((stack1 = (depth0 && depth0.series)),stack1 == null || stack1 === false ? stack1 : stack1.summation)),stack1 == null || stack1 === false ? stack1 : stack1.last), options)))
    + "</td>\n    <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = ((stack1 = (depth0 && depth0.series)),stack1 == null || stack1 === false ? stack1 : stack1.summation)),stack1 == null || stack1 === false ? stack1 : stack1.min), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = ((stack1 = (depth0 && depth0.series)),stack1 == null || stack1 === false ? stack1 : stack1.summation)),stack1 == null || stack1 === false ? stack1 : stack1.min), options)))
    + "</td>\n    <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = ((stack1 = (depth0 && depth0.series)),stack1 == null || stack1 === false ? stack1 : stack1.summation)),stack1 == null || stack1 === false ? stack1 : stack1.max), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = ((stack1 = (depth0 && depth0.series)),stack1 == null || stack1 === false ? stack1 : stack1.summation)),stack1 == null || stack1 === false ? stack1 : stack1.max), options)))
    + "</td>\n    <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = ((stack1 = (depth0 && depth0.series)),stack1 == null || stack1 === false ? stack1 : stack1.summation)),stack1 == null || stack1 === false ? stack1 : stack1.mean), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = ((stack1 = (depth0 && depth0.series)),stack1 == null || stack1 === false ? stack1 : stack1.summation)),stack1 == null || stack1 === false ? stack1 : stack1.mean), options)))
    + "</td>\n    <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = ((stack1 = (depth0 && depth0.series)),stack1 == null || stack1 === false ? stack1 : stack1.summation)),stack1 == null || stack1 === false ? stack1 : stack1.median), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = ((stack1 = (depth0 && depth0.series)),stack1 == null || stack1 === false ? stack1 : stack1.summation)),stack1 == null || stack1 === false ? stack1 : stack1.median), options)))
    + "</td>\n    <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = ((stack1 = (depth0 && depth0.series)),stack1 == null || stack1 === false ? stack1 : stack1.summation)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = ((stack1 = (depth0 && depth0.series)),stack1 == null || stack1 === false ? stack1 : stack1.summation)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options)))
    + "</td>\n  </tr>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["timeshift_summation_table"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
  var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, self=this, helperMissing=helpers.helperMissing;

function program1(depth0,data) {
  
  
  return "table-striped";
  }

  buffer += "<div class=\"ds-item\" id=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.item_id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n  ";
  stack1 = self.invokePartial(partials['ds-title-bar'], 'ds-title-bar', depth0, helpers, partials, data);
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\n  <table class=\"table table-condensed ds-timeshift-summation-table "
    + escapeExpression((helper = helpers.height || (depth0 && depth0.height),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "height", (depth0 && depth0.item), options)))
    + " "
    + escapeExpression((helper = helpers.css_class || (depth0 && depth0.css_class),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.item), options) : helperMissing.call(depth0, "css_class", (depth0 && depth0.item), options)))
    + "\n                ";
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.striped), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
  if(stack1 || stack1 === 0) { buffer += stack1; }
  buffer += "\">\n    <thead>\n      <tr>\n        <th></th>\n        <th>Now</th>\n        <th>"
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.shift)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + " Ago</th>\n        <th>Delta</th>\n        <th>%</th>\n      </tr>\n    </thead>\n    <tbody/>\n  </table>\n</div>\n";
  return buffer;
  });

this["ds"]["templates"]["models"]["timeshift_summation_table_body"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function";


  buffer += "<!-- TODO: configure which rows to include. Sum doesnt make sense for rates -->\n<tr>\n  <th>Average</th>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.mean), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.mean), options)))
    + "</td>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.mean), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.mean), options)))
    + "</td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.mean_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.mean), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.mean), options)))
    + "\n  </td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.mean_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.mean_pct)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\n  </td>\n</tr>\n<tr>\n  <th>Median</th>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.median), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.median), options)))
    + "</td>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.median), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.median), options)))
    + "</td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.median_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.median), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.median), options)))
    + "\n  </td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.median_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.median_pct)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\n  </td>\n</tr>\n<tr>\n  <th>Min</th>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.min), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.min), options)))
    + "</td>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.min), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.min), options)))
    + "</td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.min_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.min), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.min), options)))
    + "\n  </td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.min_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.min_pct)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\n  </td>\n</tr>\n<tr>\n  <th>Max</th>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.max), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.max), options)))
    + "</td>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.max), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.max), options)))
    + "</td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.max_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.max), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.max), options)))
    + "\n  </td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.max_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.max_pct)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\n  </td>\n</tr>\n<tr>\n  <th>Sum</th>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.now)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options)))
    + "</td>\n  <td>"
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.then)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options)))
    + "</td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.sum_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression((helper = helpers.format || (depth0 && depth0.format),options={hash:{},data:data},helper ? helper.call(depth0, ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options) : helperMissing.call(depth0, "format", ((stack1 = (depth0 && depth0.item)),stack1 == null || stack1 === false ? stack1 : stack1.format), ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.sum), options)))
    + "\n  </td>\n  <td class=\""
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.sum_class)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\">\n    "
    + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.diff)),stack1 == null || stack1 === false ? stack1 : stack1.sum_pct)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
    + "\n  </td>\n</tr>\n";
  return buffer;
  });;/**
 * ds.charts exposes the generic interface to rendering a chart for a
 * dashboard item. It delegates all calls to the currently assigned
 * implementation in the impl property.
 *
 * Chart providers must each implement the following functions:
 *
 *   * simple_line_chart
 *   * standard_line_chart
 *   * simple_area_chart
 *   * stacked_area_chart
 *   * donut_chart
 *   * process_series
 */
ds.charts =
  (function () {

    var self = limivorous.observable()
                         .property('provider')
                         .property('interactive', {init: false})
                         .build()

    self.DEFAULT_PALETTE = 'spectrum6'

    function get_renderer(fn_name, item) {
      var interactive = self.interactive
      if ((typeof(item) !== 'undefined') && (typeof(item.interactive) !== 'undefined'))
        interactive = item.interactive
      return interactive ? self.provider[fn_name] : ds.charts.graphite[fn_name]
    }

    /**
     * Render a minimal line chart into element.
     */
    self.simple_line_chart = function(element, item, query) {
      return get_renderer('simple_line_chart', item)(element, item, query)
    }

    /**
     * Render a complete line chart into element.
     */
    self.standard_line_chart = function(element, item, query) {
      return get_renderer('standard_line_chart', item)(element, item, query)
    }

    /**
     * Render a minimal area chart into element.
     */
    self.simple_area_chart = function(element, item, query) {
      return get_renderer('simple_area_chart', item)(element, item, query)
    }

    /**
     * Render a complete area chart into element.
     */
    self.stacked_area_chart = function(element, item, query) {
      return get_renderer('stacked_area_chart', item)(element, item, query)
    }

    /**
     * Render a donut/pie chart into element.
     */
    self.donut_chart = function(element, item, query) {
      return get_renderer('donut_chart', item)(element, item, query)
    }

    /**
     * Convert the JSON data series returned from Graphite into the
     * format used by the current chart provider.
     *
     * @param type {string} Override the currently configured chart
     *                      provider.
     */
    self.process_series = function(series, type) {
      var processed = null
      if (type) {
        processed = ds.charts[type].process_series(series)
      } else {
        processed = self.provider.process_series(series)
      }
      /* Ensure that a common target variable is always there for non-chart presentations to use */
      processed.target = series.target
      return processed
    }

    /**
     * Process a data series or array of data series from Graphite's
     * JSON format to the format expected by the charting provider.
     *
     * @param type {string} Override the currently configured chart
     *                      provider.
     */
    self.process_data = function(data, type) {
      if (!data)
        return []
      if (data instanceof Array) {
        return data.map(function(series) {
                 return self.process_series(series, type)
               })
      } else {
        return [ self.process_series(data) ]
      }
    }

    return self

  })()

/**
 * Utils relating to a collection of bundled color palettes for use in
 * rendering charts.
 */
ds.charts.util =
  (function () {
    var self = {}

    self.get_palette = function(name) {
      if (name instanceof Array)
        return name
      var palette = self.colors[name];
      return palette || self.colors[ds.charts.DEFAULT_PALETTE];
    }

    /**
     * Return a set of colors for rendering graphs that are tuned to
     * the current UI color theme. Colors are derived from the
     * background color of the 'body' element.
     *
     * TODO: cache the results keyed by background color.
     *
     * TODO: if the model had back links to containers, we could walk
     * up the containment hierarchy to see if the chart is contained
     * in something that has a background style set (i.e. well, alert,
     * etc...) and get the colors based on that.
     *
     * Or we could just pre-compute them all based on the background
     * colors from the CSS.
     */
    self.get_colors = function() {
      var color = Color(window.getComputedStyle($('body')[0]).backgroundColor)
      if (color.dark()) {
        return {
          majorGridLineColor: color.clone().lighten(0.75).hexString(),
          minorGridLineColor: color.clone().lighten(0.5).hexString(),
          fgcolor: color.clone().lighten(3.0).hexString()
        }
      } else {
        return {
          majorGridLineColor: color.clone().darken(0.15).hexString(),
          minorGridLineColor: color.clone().darken(0.05).hexString(),
          fgcolor: color.clone().darken(0.75).hexString()
        }
      }
    }

    /**
     * Return a low contrast monochromatic color palette for
     * transforms like HighlightAverage, which de-emphasize a mass of
     * raw metrics in order to highlight computed series.
     */
    self.get_low_contrast_palette = function() {
      /* TODO: get from options parameter */
      var light_step = 0.1
        , dark_step  = 0.05
        , count      = 6
        , bg    = Color(window.getComputedStyle($('body')[0]).backgroundColor)
        , color = bg.dark() ? bg.clone().lighten(0.25) : bg.clone().darken(0.1)

      var palette = []
      for (var i = 0; i < count; i++) {
        palette.push(color.hexString())
        bg.dark() ? color.lighten(light_step) : color.darken(dark_step)
      }

      return palette
    }

    self._color_function = function(palette_name) {
        var palette = self.colors[palette_name];
        if (!palette) {
            palette = self.colors[ds.charts.DEFAULT_PALETTE];
        }
        return function(d, i) {
            return palette[i % palette.length];
        }
    }

    /**
     * Some color palettes", handily compiled by the Stanford Vis
     * Group for their Color Palette Analyzer project.
     * http://vis.stanford.edu/color-names/analyzer/
     */
    self.colors = {
      applespectrum:[ "#2d588a", "#58954c", "#e9a044", "#c12f32", "#723e77", "#7d807f" ],
        appleblue:    [ "#4972a8", "#92b9d8", "#002d64", "#599bcf", "#134d8d" ],
        applebrown:   [ "#8b6c4f", "#c8b68e", "#3b291d", "#ae8e5d", "#713f24" ],
        applegrey:    [ "#717372", "#c0c2c1", "#2d2f2e", "#8c8e8d", "#484a49" ],
        applegreen:   [ "#2d632f", "#90b879", "#0d2d16", "#599a48", "#00431a" ],
        tableau10:    [ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b",
                        "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" ],
        tableau20:    [ "#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a",
                        "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94",
                        "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d",
                        "#17becf", "#9edae5" ],
        manyeyes:     [ "#9c9ede", "#7375b5", "#4a5584", "#cedb9c", "#b5cf6b", "#8ca252",
                        "#637939", "#e7cb94", "#e7ba52", "#bd9e39", "#8c6d31", "#e7969c",
                        "#d6616b", "#ad494a", "#843c39", "#de9ed6", "#ce6dbd", "#a55194",
                        "#7b4173" ],
        numbers6:     [ "#2d588a", "#58954c", "#e9a044", "#c12f32", "#723e77", "#7d807f" ],
        excel10:      [ "#365e96", "#983334", "#77973d", "#5d437c", "#36869f", "#d1702f",
                        "#8197c5", "#c47f80", "#acc484", "#9887b0" ],
        economist:    [ "#621e15", "#e59076", "#128dcd", "#083c52", "#64c5f2", "#61afaf",
                        "#0f7369", "#9c9da1" ],
        brewerq9:     [ "#e41a1c", "#377eb8", "#4daf4a", "#984ea3", "#ff7f00", "#ffff33",
                        "#a65628", "#f781bf", "#999999" ],
        brewerq10:    [ "#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99", "#e31a1c",
                        "#fdbf6f", "#ff7f00", "#cab2d6", "#6a3d9a" ],
        brewerq12:    [ "#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462",
                        "#b3de69", "#fccde5", "#d9d9d9", "#bc80bd", "#ccebc5", "#ffed6f" ],
        brewerdiv1:   [ "#8c510a", "#d8b365", "#f6e8c3", "#f5f5f5", "#c7eae5", "#5ab4ac",
                        "#01665e" ],
        brewerdiv2:   [ "#b2182b", "#ef8a62", "#fddbc7", "#f7f7f7", "#d1e5f0", "#67a9cf",
                        "#2166ac" ],
        brewerdiv3:   [ "#762a83", "#af8dc3", "#e7d4e8", "#f7f7f7", "#d9f0d3", "#7fbf7b",
                        "#1b7837" ],
        brewerdiv4:   [ "#d73027", "#fc8d59", "#fee090", "#ffffbf", "#e0f3f8", "#91bfdb",
                        "#4575b4" ],
        marketmap :   [ "#fa0007", "#ac0000", "#4e0300", "#000000", "#005101", "#06a200",
                        "#07ff00" ],


        // some more color palettes from rickshaw
        spectrum6:  ["#e7cbe6", "#d8aad6", "#a888c2", "#9dc2d3",
                     "#649eb9", "#387aa3"].reverse(),
        spectrum14:  ["#ecb796", "#dc8f70", "#b2a470", "#92875a", "#716c49", "#d2ed82",
                      "#bbe468", "#a1d05d", "#e7cbe6", "#d8aad6", "#a888c2", "#9dc2d3",
                      "#649eb9", "#387aa3"].reverse(),
        spectrum2000:["#57306f", "#514c76", "#646583", "#738394", "#6b9c7d", "#84b665",
                      "#a7ca50", "#bfe746", "#e2f528", "#fff726", "#ecdd00", "#d4b11d",
                      "#de8800", "#de4800", "#c91515", "#9a0000", "#7b0429", "#580839",
                      "#31082b"],
        spectrum2001:["#2f243f", "#3c2c55", "#4a3768", "#565270", "#6b6b7c", "#72957f",
                      "#86ad6e", "#a1bc5e", "#b8d954", "#d3e04e", "#ccad2a", "#cc8412",
                      "#c1521d", "#ad3821", "#8a1010", "#681717", "#531e1e", "#3d1818",
                      "#320a1b"],
        classic9:    ["#423d4f", "#4a6860", "#848f39", "#a2b73c", "#ddcb53", "#c5a32f",
                      "#7d5836", "#963b20", "#7c2626", "#491d37", "#2f254a"].reverse(),
        colorwheel:  ["#b5b6a9", "#858772", "#785f43", "#96557e", "#4682b4", "#65b9ac",
                      "#73c03a", "#cb513a"].reverse(),
        cool:        ["#5e9d2f", "#73c03a", "#4682b4", "#7bc3b8", "#a9884e", "#c1b266",
                      "#a47493", "#c09fb5"],
        munin:       ["#00cc00", "#0066b3", "#ff8000", "#ffcc00", "#330099", "#990099",
                      "#ccff00", "#ff0000", "#808080", "#008f00", "#00487d", "#b35a00",
                      "#b38f00", "#6b006b", "#8fb300", "#b30000", "#bebebe", "#80ff80",
                      "#80c9ff", "#ffc080", "#ffe680", "#aa80ff", "#ee00cc", "#ff8080",
                      "#666600", "#ffbfff", "#00ffcc", "#cc6699", "#999900"],

        // some more color palettes from d3.js
        d3category10: [ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b",
                        "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"],

        d3category20: ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a",
                       "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94",
                       "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d",
                       "#17becf", "#9edae5" ],

        d3category20b: [ "#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252",
                         "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94",
                         "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194",
                         "#ce6dbd", "#de9ed6" ],

        d3category20c: ["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c",
                        "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0",
                        "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696",
                        "#bdbdbd", "#d9d9d9"],

      // Here are a couple of intentionally low-contrast palettes
      // which intended to allow computed series (like
      // averageSeries(), highestAverage(), mostDeviant(), etc...) to
      // stand out over the noise of a ton of metric instances when
      // assigned their own color with the color() function.

      low_contrast_gray : [ "#BBBBBB","#C8C8C8","#D5D5D5","#E1E1E1","#EEEEEE" ],
      low_contrast_blue : [ "#AAAABB","#B7B7C8","#C4C4D5","#D0D0E1","#DDDDEE", "#EAEAFB" ]

    }

    return self

  })()
;ds.charts = ds.charts || {}

/**
 * Charts provider for Graphite's built-in static image
 * rendering. Also provides Graphite URL formatting for a number of
 * the UI's actions (Open in Graphite..., Export PNG..., etc...)
 */
ds.charts.graphite =
  (function () {

    var self = {}

    self.DEFAULT_BGCOLOR = 'ff000000'

    function img(element, url) {
      element.html($('<img/>')
                     .attr('src', url)
                     .height(element.height())
                     .width(element.width()))
    }

    self.simple_line_chart = function(element, item, query) {
      var url = self.simple_line_chart_url(item, {
        height: element.height(),
        width: element.width()
      })
      img(element, url)
    }

    self.simple_line_chart_url = function(item, opt) {
        var options = ds.extend(opt || {}, item.options, ds.charts.util.get_colors())
        var png_url = URI(item.query.url())
            .setQuery('format', options.format || 'png')
            .setQuery('height', options.height || 600)
            .setQuery('width', options.width || 1200)
            .setQuery('bgcolor', options.bgcolor || self.DEFAULT_BGCOLOR)
            .setQuery('fgcolor', options.fgcolor || 'white')
            .setQuery('hideLegend', 'true')
            .setQuery('hideAxes', 'true')
            .setQuery('margin', '0')
            .setQuery('colorList', ds.charts.util.get_palette(options.palette).join())
            .setQuery('title', options.showTitle ? item.title : '')

        if (options.y1 && options.y1.min)
            png_url.setQuery('yMin', options.y1.min )
        if (options.y1 && options.y1.max)
            png_url.setQuery('yMax', options.y1.max )

        return png_url.href()
    }

    self.standard_line_chart = function(element, item, query) {
      var url = self.standard_line_chart_url(item, {
        height: element.height(),
        width: element.width()
      })
      img(element, url)
    }

    self.standard_line_chart_url = function(item, opt) {
        var options = ds.extend(opt || {}, item.options, ds.charts.util.get_colors())
        var png_url = URI(item.query.url())
            .setQuery('format', options.format || 'png')
            .setQuery('height', options.height || 600)
            .setQuery('width', options.width || 1200)
            .setQuery('bgcolor', options.bgcolor || self.DEFAULT_BGCOLOR)
            .setQuery('fgcolor', options.fgcolor || 'black')
            .setQuery('majorGridLineColor', options.majorGridLineColor || '#dddddd')
            .setQuery('minorGridLineColor', options.minorGridLineColor || '#eeeeee')
            .setQuery('hideLegend', options.hideLegend || 'false')
            .setQuery('hideAxes', options.hideAxes || 'false')
            .setQuery('colorList', ds.charts.util.get_palette(options.palette).join())
            .setQuery('vtitle', options.y1 ? options.y1.label : options.yAxisLabel)
            .setQuery('title', options.showTitle ? item.title : '')

        if (options.y1 && options.y1.min)
            png_url.setQuery('yMin', options.y1.min )
        if (options.y1 && options.y1.max)
            png_url.setQuery('yMax', options.y1.max )

        return png_url.href()
    }

    self.simple_area_chart = function(element, item, query) {
      var url = self.simple_area_chart_url(item, {
        height: element.height(),
        width: element.width()
      })
      img(element, url)
    }

    self.simple_area_chart_url = function(item, opt) {
        var options = ds.extend(opt || {}, item.options, ds.charts.util.get_colors())
        var png_url = URI(item.query.url())
            .setQuery('format', options.format || 'png')
            .setQuery('height', options.height || 600)
            .setQuery('width', options.width || 1200)
            .setQuery('bgcolor', options.bgcolor || self.DEFAULT_BGCOLOR)
            .setQuery('fgcolor', options.fgcolor || 'black')
            .setQuery('majorGridLineColor', options.majorGridLineColor || '#dddddd')
            .setQuery('minorGridLineColor', options.minorGridLineColor || '#eeeeee')
            .setQuery('hideLegend', 'true')
            .setQuery('hideAxes', 'true')
            .setQuery('margin', '0')
            .setQuery('colorList', ds.charts.util.get_palette(options.palette).join())

        if (!item.query.is_stacked())
            png_url.setQuery('areaMode', 'stacked')

        if (options.y1 && options.y1.min)
            png_url.setQuery('yMin', options.y1.min )
        if (options.y1 && options.y1.max)
            png_url.setQuery('yMax', options.y1.max )

        return png_url.href()
    }

    self.stacked_area_chart = function(element, item, query) {
      var url = self.stacked_area_chart_url(item, {
        height: element.height(),
        width: element.width()
      })
      img(element, url)
    }

    self.stacked_area_chart_url = function(item, opt) {
        var options = ds.extend(opt || {}, item.options, ds.charts.util.get_colors())
        var png_url = URI(item.query.url())
            .setQuery('format', options.format || 'png')
            .setQuery('height', options.height || 600)
            .setQuery('width', options.width || 1200)
            .setQuery('bgcolor', options.bgcolor || self.DEFAULT_BGCOLOR)
            .setQuery('fgcolor', options.fgcolor || 'black')
            .setQuery('majorGridLineColor', options.majorGridLineColor || '#dddddd')
            .setQuery('minorGridLineColor', options.minorGridLineColor || '#eeeeee')
            .setQuery('hideLegend', options.hideLegend || 'false')
            .setQuery('hideAxes', options.hideAxes || 'false')
            .setQuery('colorList', ds.charts.util.get_palette(options.palette).join())
            .setQuery('vtitle', options.y1 ? options.y1.label : options.yAxisLabel)
            .setQuery('title', options.showTitle ? item.title : '')

        if (!item.query.is_stacked())
            png_url.setQuery('areaMode', 'stacked')

        if (options.y1 && options.y1.min)
            png_url.setQuery('yMin', options.y1.min )
        if (options.y1 && options.y1.max)
            png_url.setQuery('yMax', options.y1.max )

        return png_url.href()
    }

    self.donut_chart_url = function(item, opt) {
      var png_url = URI(self.standard_line_chart_url(item, opt))
            .setQuery('graphType', 'pie')

        return png_url.href()
    }

    self.donut_chart = function(element, item, query) {
      var url = self.donut_chart_url(item, {
        height: element.height(),
        width: element.width()
      })
      img(element, url)
    }

    self.chart_url = function(item, options) {
      switch (item.item_type) {
        case 'simple_time_series':
            return item.filled
                ? self.simple_area_chart_url(item, options)
                : self.simple_line_chart_url(item, options)
        case 'standard_time_series':
            return self.standard_line_chart_url(item, options)
        case 'stacked_area_chart':
            return self.stacked_area_chart_url(item, options)
        case 'singlegraph':
            return self.simple_area_chart_url(item, options)
        case 'donut_chart':
            return self.donut_chart_url(item, options)
        }
        return undefined
    }

    self.composer_url = function(item, options) {
        options = options || {}
        var composer_url = URI(item.query.url())
            .filename('composer')
            .removeQuery('format')
            .setQuery('colorList', ds.charts.util.get_palette(options.palette).join())
            .setQuery('vtitle', options.yAxisLabel)
            .setQuery('title', options.showTitle ? item.title : '')
        if (item.item_type === 'stacked_area_chart' && !(item.query.is_stacked())) {
            composer_url.setQuery('areaMode', 'stacked')
        }
        return composer_url.href()
    }

    return self

  })()
;ds.charts = ds.charts || {}

/**
 * Chart provider for rendering dashboard charts with NVD3
 */
ds.charts.nvd3 =
  (function() {

    var self = {}

    self.CHART_IMPL_TYPE = 'nvd3'
    self.DEFAULT_AUTO_HIDE_LEGEND_THRESHOLD = 6

    self.simple_line_chart = function(e, item, query) {
      var options = item.options || {}
      var data = [query.chart_data('nvd3')[0]]
        nv.addGraph(function() {
            var width = e.width()
            var height = e.height()
            var chart = nv.models.lineChart()
                .options({
                    showXAxis: false,
                    showYAxis: false,
                    showLegend: false,
                    useInteractiveGuideline: true,
                    x: function(d) { return d[1] },
                    y: function(d) { return d[0] }
                })
                .color(ds.charts.util._color_function(options.palette || ds.charts.DEFAULT_PALETTE))
                .margin(options.margin || { top: 0, right: 16, bottom: 0, left: 40 })
                .width(width)
                .height(height)
            chart.yAxis.tickFormat(d3.format((options.y1 ? options.y1.format : options.yAxisFormat) || ',.3s'))
            chart.xAxis.tickFormat(function(d) { return moment.unix(d).tz(ds.config.DISPLAY_TIMEZONE).fromNow() })
            d3.select(e.selector + ' svg')
                .attr('width', width)
                .attr('height', height)
                .datum(data)
                .call(chart)
            return chart
        })
    }

    self.standard_line_chart = function(e, item, query) {
      var options = item.options || {}
      var data = query.chart_data('nvd3')
        var showLegend = options.showLegend !== false
        if (data.length > self.DEFAULT_AUTO_HIDE_LEGEND_THRESHOLD) {
            showLegend = false
        }
        nv.addGraph(function() {
            var width = e.width()
            var height = e.height()
            var chart = nv.models.lineChart()
                .options({
                    showXAxis: options.showXAxis !== false,
                    showYAxis: options.showYAxis !== false,
                    showLegend: showLegend,
                    useInteractiveGuideline: options.useInteractiveGuideline !== false,
                    x: function(d) { return d[1] },
                    y: function(d) { return d[0] }
                })
                .color(ds.charts.util._color_function(options.palette || ds.charts.DEFAULT_PALETTE))
                //.margin(options.margin || { top: 12, right: 16, bottom: 16, left: 40 })
                .width(width)
                .height(height)
            chart.yAxis
                .axisLabelDistance((options.y1 ? options.y1.label_distance : options.yAxisLabelDistance) || 30)
                .axisLabel(options.y1 ? options.y1.label : options.yAxisLabel)
                .tickFormat(d3.format((options.y1 ? options.y1.format : options.yAxisFormat) || ',.3s'))
            chart.xAxis
                .tickFormat(function(d) { return moment.unix(d).tz(ds.config.DISPLAY_TIMEZONE).format('h:mm A') })
                .axisLabel(options.x ? options.x.label : options.xAxisLabel)
            d3.select(e.selector + ' svg')
                .attr('width', width)
                .attr('height', height)
                .datum(data)
                .call(chart)
            return chart
        })
    }

    self.simple_area_chart = function(e, item, query) {
      var options = item.options || {}
      var data = [query.chart_data('nvd3')[0]]
        nv.addGraph(function() {
            var width  = e.width()
            var height = e.height()
            var chart  = nv.models.stackedAreaChart()
                .options({
                    showLegend: false,
                    showControls: false,
                    showXAxis: false,
                    showYAxis: false,
                    useInteractiveGuideline: true,
                    x: function(d) { return d[1] },
                    y: function(d) { return d[0] }
                })
                .color(ds.charts.util._color_function(options.palette || ds.charts.DEFAULT_PALETTE))
                .style('stack')
                .width(width)
                .height(height)
                .margin(options.margin || { top: 0, right: 0, bottom: 0, left: 0 })
            chart.yAxis
                .tickFormat(d3.format((options.y1 ? options.y1.format : options.yAxisFormat) || ',.3s'))
            chart.xAxis
                .tickFormat(function(d) { return moment.unix(d).tz(ds.config.DISPLAY_TIMEZONE).fromNow() })
            d3.select(e.selector + ' svg')
                .attr('width', width)
                .attr('height', height)
                .datum(data)
                .transition()
                .call(chart)
            return chart
        })
    }

    self.stacked_area_chart = function(e, item, query) {
        var options = item.options || {}
        var showLegend = options.showLegend !== false
      var data = query.chart_data('nvd3')
        if (data.length > self.DEFAULT_AUTO_HIDE_LEGEND_THRESHOLD) {
            showLegend = false
        }
        nv.addGraph(function() {
            var width  = e.width()
            var height = e.height()
            var chart  = nv.models.stackedAreaChart()
                .options({
                    showLegend: showLegend,
                    useInteractiveGuideline: options.useInteractiveGuideline !== false,
                    showXAxis: options.showXAxis !== false,
                    showYAxis: options.showYAxis !== false,
                    x: function(d) { return d[1] },
                    y: function(d) { return d[0] }
                })
                .color(ds.charts.util._color_function(options.palette || ds.charts.DEFAULT_PALETTE))
                .style(options.style || 'stack')
                .width(width)
                .height(height)
            chart.yAxis
                .axisLabel(options.y1 ? options.y1.label : options.yAxisLabel)
                .axisLabelDistance((options.y1 ? options.y1.label_distance : options.yAxisLabelDistance) || 30)
                .tickFormat(d3.format((options.y1 ? options.y1.format : options.yAxisFormat) || ',.3s'))
            chart.xAxis
                .axisLabel(options.x ? options.x.label : options.xAxisLabel)
                .tickFormat(function(d) { return moment.unix(d).tz(ds.config.DISPLAY_TIMEZONE).format('h:mm A') })
            d3.select(e.selector + ' svg')
                .attr('width', width)
                .attr('height', height)
                .datum(data)
                .transition()
                .call(chart)
            return chart
        })
    }

    self.donut_chart = function(e, item, query) {
      var options = item.options || {}
      var transform = item.transform || 'sum'
      var series = query.chart_data('nvd3')
        /* var showLegend = options.showLegend !== false
           if (list_of_series.length > self.DEFAULT_AUTO_HIDE_LEGEND_THRESHOLD) {
           showLegend = false
           } */
        var data = series.map(function(series) {
            return {
                label: series.key,
                y: series.summation[transform]
            }
        })
        nv.addGraph(function() {
            var width  = e.width()
            var height = e.height()
            var chart  = nv.models.pieChart()
            /* .options({
               showLegend: showLegend,
               useInteractiveGuideline: options.useInteractiveGuideline !== false,
               x: function(d) { return d.key },
               y: function(d) { return d.y }
               }) */
                .color(ds.charts.util._color_function(options.palette || ds.charts.DEFAULT_PALETTE))
                .labelType(options.labelType || "percent")
                .donut(options.donut !== false)
                .donutRatio(options.donutRatio || 0.3)
                .showLabels(options.showLabels !== false)
                .donutLabelsOutside(options.donutLabelsOutside !== false)
                .width(width)
                .height(height)
                .margin(options.margin || { top: 0, right: 0, bottom: 0, left: 0 })
            d3.select(e.selector + ' svg')
                .attr('width', width)
                .attr('height', height)
                .datum(data)
                .transition()
                .call(chart)
            return chart
        })
    }

    self.process_series = function(series) {
      var result = {}
      if (series.summation) {
        result.summation = series.summation
      }
      result.key = series.target
      result.values = series.datapoints
      return result
    }

    return self
})()
;ds.charts = ds.charts || {}

/**
 * Chart provider for rendering dashboard chart with flot, which
 * provides Canvas-based interactivity.
 */
ds.charts.flot =
  (function() {

    var self = {}

    self.CHART_IMPL_TYPE = 'flot'

    function get_default_options() {
      var theme_colors = ds.charts.util.get_colors()
      var default_options = {
        colors: ds.charts.util.get_palette(),
        series: {
          lines: {
            show: true,
            lineWidth: 1,
            steps: false,
            fill: false
          },
          stack: null,
          points: { show: false },
          bars: { show: false }
        },
        xaxis: {
          mode: "time",
          tickFormatter: function(value) {
            return moment(value).tz(ds.config.DISPLAY_TIMEZONE).format('h:mm A')
          },
          tickColor: theme_colors.minorGridLineColor,
          font: {
            color: theme_colors.fgcolor,
            size: 12
          }
        },
        yaxes: [
          {
            position: 'left',
            tickFormatter: d3.format(',.3s'),
            reserveSpace: 30,
            labelWidth: 30,
            tickColor: theme_colors.minorGridLineColor,
            font: {
              color: theme_colors.fgcolor,
              size: 12
            }
          },
          {
            position: 'right',
            tickFormatter: d3.format(',.3s'),
            font: {
              color: theme_colors.fgcolor,
              size: 12
            }
          }
        ],
        points: {
          show: false,
          radius: 2,
          symbol: "circle"
        },
        shadowSize: 0,
        legend: {
          container: null,
          noColumns: 2,
          position: 'sw',
          backgroundColor: 'transparent',
          labelBoxBorderColor: 'transparent'
        },
        grid: {
          borderWidth: 1,
          hoverable: true,
          clickable: true,
          autoHighlight: false,
          /* grid.color actually sets the color of the legend
           * text. WTH? */
          color: theme_colors.fgcolor,
          borderColor: theme_colors.minorGridLineColor
        },
        selection: {
          mode: "x",
          color: "red"
        },
        multihighlight: {
          mode: "x"
        },
        crosshair: {
          mode: "x",
          color: "#BBB",
          lineWidth: 1
        }
      }
      return default_options
    }

    function get_flot_options(item, base) {
      var options      = item.options || {}
      var flot_options = ds.extend(get_default_options(), base)

      flot_options.colors = ds.charts.util.get_palette(options.palette)

      if (options.y1 && options.y1.min)
        flot_options.yaxes[0].min = options.y1.min
      if (options.y2 && options.y2.min)
        flot_options.yaxes[1].min = options.y2.min

      if (options.y1 && options.y1.max)
        flot_options.yaxes[0].max = options.y1.max
      if (options.y2 && options.y2.max)
        flot_options.yaxes[1].max = options.y2.max

      if (options.y1 && options.y1.label)
        flot_options.yaxes[0].axisLabel = options.y1.label
      if (options.y2 && options.y2.label)
        flot_options.yaxes[1].axisLabel = options.y2.label

      return flot_options
    }

    function show_tooltip(x, y, contents) {
      $('<div id="ds-tooltip">' + contents + '</div>').css( {
        position: 'absolute',
        display: 'none',
        top: y + 5,
        left: x + 5
      }).appendTo("body").show()
    }

    function setup_plugins(container, context) {
      $(container).bind("multihighlighted", function(event, pos, items) {
        if ( !items )
          return
        var series = context.plot.getData()
        var item = items[0]
        var point = series[item.serieIndex].data[item.dataIndex]

        var contents = ds.templates.flot.tooltip({
          time: point[0],
          items: items.map(function(item) {
                   return {
                     series: series[item.serieIndex],
                     value: series[item.serieIndex].data[item.dataIndex][1]
                   }
                 })
        })

        $("#ds-tooltip").remove()
        show_tooltip(pos.pageX, pos.pageY, contents)
      })

      $(container).bind("unmultihighlighted", function(event) {
        $("#ds-tooltip").remove()
      })
    }

    function render_legend(item, query, flot_options) {
      var legend_id = '#ds-legend-' + item.item_id
      var legend = ''
      var data = query.chart_data('flot')
      for (var i = 0; i < data.length; i++) {
        var series = data[i]
        var label = series.label
        var color = flot_options.colors[i % flot_options.colors.length]

        var cell = '<div class="ds-legend-cell">'
                 + '<span class="color" style="background-color:' + color + '"></span>'
                 + '<span class="label" style="color:' + flot_options.xaxis.font.color +  '">' + label + '</span>'
                 + '</div>'
        legend += cell
      }
      var elt = $(legend_id)
      elt.html(legend)
      elt.equalize({equalize: 'outerWidth', reset: true })
    }


    self.simple_line_chart = function(e, item, query) {
      var context = {
          plot: null
      }
      setup_plugins(e, context)

      var flot_options = get_flot_options(item, {
        grid: {
          show: false
        },
        legend: {
          show: false
        }
      })

      context.plot = $.plot($(e), [query.chart_data('flot')[0]],
                            flot_options)
      return self
    }

    self.standard_line_chart = function(e, item, query) {
      var context = {
          plot: null
      }
      setup_plugins(e, context)
      var flot_options = get_flot_options(item, {
        grid: ds.extend(get_default_options().grid, {
          hoverable: true,
          clickable: true,
          autoHighlight: false
        }),
        legend: {
          show: false
        }
      })

      context.plot = $.plot($(e), query.chart_data('flot'), flot_options)
      render_legend(item, query, flot_options)

      return self
    }

    self.simple_area_chart = function(e, item, query) {
      var options = item.options || {}
      var context = {
          plot: null
      }
      setup_plugins(e, context)
      var flot_options = ds.extend(get_default_options(), {
        colors: ds.charts.util.get_palette(options.palette),
        grid: {
          show: false
        },
        legend: {
          show: false
        },
        series: {
          lines: {
            show: true,
            fill: 1
          }
        }
      })

      context.plot = $.plot($(e), [query.chart_data('flot')[0]],
                            flot_options)
      return self
    }


    self.stacked_area_chart = function(e, item, query) {
      var context = {
          plot: null
      }
      var legend_id = '#ds-legend-' + item.item_id
      var flot_options = get_flot_options(item, {
        legend: {
          container: legend_id,
          labelBoxBorderColor: 'transparent',
          show: true,
          noColumns: 4
        },
        series: {
          lines: { show: true, lineWidth: 1, fill: 1},
          stack: true,
          points: { show: false },
          bars: { show: false }
        }
      })

      setup_plugins(e, context)
      context.plot = $.plot($(e), query.chart_data('flot'), flot_options)

      render_legend(item, query, flot_options)
      return self
    }

    self.donut_chart = function(e, item, query) {
      ds.charts.nvd3.donut_chart(e, item, query)
      return self
    }

    self.process_series = function(series) {
      var result = {}
      if (series.summation) {
        result.summation = series.summation
      }
      result.label = series.target
      result.data = series.datapoints.map(function(point) {
                      return [point[1] * 1000, point[0]]
                    })
      return result
    }

    return self
  })()

/**
 * Set flot as the default interactive chart provider.
 */
ds.charts.provider = ds.charts.flot
;/**
 * A property descriptor for building editor property sheets.
 */
ds.property = function(data) {

  var self = limivorous.observable()
                       .property('id')
                       .property('name')
                       .property('category')
                       .property('type')
                       .property('template')
                       .property('edit_options')
                       .build()
    , default_options = { type: 'text' }

  Object.defineProperty(self, 'is_property', {value: true})

  if (data) {
    self.id = data.id
    self.name = data.name || self.id
    self.category = data.category
    self.type = data.type
    self.edit_options = data.edit_options
    if (typeof(data.template) === 'string') {
      self.template = Handlebars.compile(data.template)
    } else if (data.template && (data.template instanceof Function)) {
      self.template = data.template
    }
  }

  /**
   * Render the basic display of the property's value.
   */
  self.render = function(item) {
    var inner = undefined
    if (self.template && (self.template instanceof Function)) {
      inner = self.template({property: self, item: item})
    } else {
      inner = item[self.name] || ''
    }
    return '<span id="' + item.item_id + self.name + '">'
         + inner
         + '</span>'
  }

  /**
   * Make the property editable by transforming its value display into
   * an in-line edit widget.
   */
  self.edit = function(item) {
    var default_options = {
        type: 'text',
        value: item[self.name] || '',
        success: function(ignore, newValue) {
          item[self.name] = newValue
          ds.manager.update_item_view(item)
        }
    }
    var options = ds.extend(default_options, self.edit_options)

    if (self.type === 'boolean') {
      options.type = 'checklist'
      options.source = [
        { value: true, text: self.name }
      ]
      options.success = function(ignore, newValue) {
        item[self.name] = newValue.length > 0
        ds.manager.update_item_view(item)
      }
    } else if (self.type) {
      options.type = self.type
    }

    if (self.type === 'select' && (options.source instanceof Array)) {
      options.source = options.source.map(function(value) {
                         if ( value instanceof String ) {
                           return { value: value, text: value }
                         } else if (typeof(value) === 'undefined') {
                           return { value: undefined, text: 'none' }
                         } else {
                           return value
                         }
                       })
    }

    if (options.source && (options.source instanceof Function)) {
      options.source = options.source()
    }

    if (options.value && (options.value instanceof Function)) {
      options.value = options.value(item)
    }

    if (options.update && (options.update instanceof Function)) {
      options.success = function(ignore, newValue) {
        options.update(item, newValue)
        ds.manager.update_item_view(item)
      }
    }

    $('#' + item.item_id + self.name).editable(options)

    return self
  }

  return self
}

/**
 * An object to allow lookup of properties by id, for easy sharing of
 * property definitions.
 */
ds.property.registry = {}

/**
 * Registry a property or array of properties in the global registry.
 */
ds.property.register = function(property) {
   if (property instanceof Array) {
     for (var i = 0; i < property.length; i++) {
       ds.property.register(property[i])
     }
   } else {
     ds.property.registry[property.id] = property.is_property
                                       ? property
                                       : ds.property(property)
   }
}

/**
 * Normalize a property object or string ID.
 *
 * @param property_or_id A string ID of a property, or a property
 *                       object
 * @return A ds.property object. If the argument was already a
 *         property instance, it is returned unmodified.
 */
ds.property.get = function(property_or_id) {
  if (property_or_id.is_property)
    return property_or_id
  return ds.property.registry[property_or_id]
      || ds.property({id: property_or_id})
}
;/**
 * Descriptor may have the following attributes:
 *   - constructor
 *   - template
 *   - interactive_properties
 *   - data_handler
 *   - icon
 *   - display_name
 *   - category
 */
ds.register_dashboard_item = function(item_type, descriptor) {
  ds.models[item_type] = descriptor
  if (descriptor.template && (typeof(descriptor.template) === 'string')) {
    descriptor.template = Handlebars.compile(descriptor.template)
  }

  if (descriptor.actions && descriptor.actions.length) {
    ds.actions.register(item_type, descriptor.actions)
  }

  var props = (descriptor.interactive_properties || []).map(function(p) {
                if (typeof(p) === 'string') {
                  var prop = ds.property.get(p)
                  return prop || ds.property({id: p})
                } else if (p.is_property) {
                  return p
                } else {
                  return ds.property(p)
                }
              })

  props.sort(function(p1, p2) {
    if (p1.category === p2.category) {
      return p1.name.localeCompare(p2.name)
    } else {
      return (p1.category || '').localeCompare(p2.category || '')
    }
  })

  descriptor.interactive_properties = props

  var category = descriptor.category ? 'new-item-' + descriptor.category : 'new-item'

  ds.actions.register(category, {
    name:     item_type,
    display:  'Add new ' + (descriptor.display_name || item_type),
    icon:     descriptor.icon || '',
    category: category,
    class:   'new-item',
    handler:  function(action, container) {
      container.add(item_type)
    }
  })

  return ds
}

ds.models.factory = function(data) {
  var item_type = null
    , item = null

  if (typeof(data) === 'string' && ds.models[data]) {
    item_type = ds.models[data]
    item = ( item_type.constructor ) ? item_type.constructor() : item_type()
  } else if (data.is_dashboard_item) {
    item = data;
  } else if (data.item_type && ds.models[data.item_type]) {
    item_type = ds.models[data.item_type]
    if (item_type instanceof Function) {
      item = item_type(data)
    } else if (item_type.constructor) {
      item = item_type.constructor(data)
    }
  }
  if (item) {
    if (item_type) {
      item.interactive_properties = item_type.interactive_properties
    }
    return item
  }
  console.log('Unknown type');
  console.log(data);
  return null;
}

ds.models.make = ds.models.factory
;ds.property.register([
  {
    id: 'query',
    category: 'base',
    template: '{{item.query.name}}',
    edit_options: {
      type: 'select',
      source: function() {
        var queries = ds.manager.current.dashboard.definition.queries
        return Object.keys(queries).map(function(k) {
                 return { value: k, text: k }
               })
      },
      value: function(item) {
        return item.query ? item.query.name : undefined
      }
    }
  },
  {
    id: 'style',
    type: 'select',
    edit_options: {
      source: [
        undefined,
        'well',
        'callout_neutral',
        'callout_info',
        'callout_success',
        'callout_warning',
        'callout_danger',
        'alert_neutral',
        'alert_info',
        'alert_success',
        'alert_warning',
        'alert_danger'
      ]
    }
  },
  {
    id: 'transform',
    type: 'select',
    edit_options: {
      source: [
        undefined,
        'sum',
        'min',
        'max',
        'mean',
        'median',
        'first',
        'last',
        'count'
      ]
    }
  }
])
;/**
 * Mixin for all chart presentation types
 */
ds.models.chart =
  (function() {
    'use strict'

    function extend(builder) {
      Object.defineProperty(builder.target, 'is_chart', {value: true})
      return builder.property('title')
                    .property('options', { init: {} })
    }

    function init(target, data) {
      if (data) {
        target.title = data.title
        target.options = data.options || {}
        if (data.options.y1) {
          target.options.y1 = ds.models.Axis(data.options.y1)
        }
        if (data.options.y2) {
          target.options.y2 = ds.models.Axis(data.options.y2)
        }
        if (data.options.x) {
          target.options.x = ds.models.Axis(data.options.x)
        }
      }
      return target
    }

    function json(target, data) {
      data = data || {}
      if (target.title)
        data.title = target.title
      if (target.options) {
        data.options = ds.extend(target.options)
        if (target.options.y1) {
          data.options.y1 = ds.json(target.options.y1)
        }
        if (target.options.y2) {
          data.options.y2 = ds.json(target.options.y2)
        }
        if (target.options.x) {
          data.options.x = ds.json(target.options.x)
        }
      }
      return data
    }

    return {
      extend: extend,
      init: init,
      json: json
    }
  })()

/**
 * Clearly, a bunch of this should be refactored into a common
 * model. Should probably make chart.options a property model object.
 */
ds.models.chart.interactive_properties = [
  'title',
  {
    id: 'chart.y-axis-label',
    name: 'y-axis-label',
    category: 'chart',
    edit_options: {
      type: 'text',
      value: function(item) {
        if (item.options && item.options.y1) {
          return item.options.y1.label
        } else if (item.options) {
          /* legacy */
          return item.options.yAxisLabel
        } else {
          return undefined
        }
      },
      update: function(item, newValue) {
        if (!item.options) {
          item.options = {}
        }
        if (!item.options.y1) {
          item.options.y1 = ds.models.Axis()
        }
        item.options.y1.label = newValue
      }
    }
  },
  {
    id: 'chart.y-axis-min',
    name: 'y-axis-min',
    category: 'chart',
    edit_options: {
      type: 'text',
      value: function(item) {
        return item.options && item.options.y1
             ? item.options.y1.min
             : undefined
      },
      update: function(item, newValue) {
        if (!item.options) {
          item.options = {}
        }
        if (!item.options.y1) {
          item.options.y1 = ds.models.Axis()
        }
        item.options.y1.min = newValue
      }
    }
  },
  {
    id: 'chart.y-axis-max',
    name: 'y-axis-max',
    category: 'chart',
    edit_options: {
      type: 'text',
      value: function(item) {
        return item.options && item.options.y1
             ? item.options.y1.max
             : undefined
      },
      update: function(item, newValue) {
        if (!item.options) {
          item.options = {}
        }
        if (!item.options.y1) {
          item.options.y1 = ds.models.Axis()
        }
        item.options.y1.max = newValue
      }
    }
  },
  {
    id: 'chart.palette',
    name: 'palette',
    category: 'chart',

    edit_options: {
      type: 'select',

      value: function(item) {
        if (item.options && item.options.palette) {
          return item.options.palette
        } else {
          return undefined
        }
      },

      source: [ { text: 'None', value: undefined } ]
                .concat(Object.keys(ds.charts.util.colors).map(function(value, index) {
                          return { text: value, value: value }
                        })),

      update: function(item, newValue) {
        if (!item.options) {
          item.options = {}
        }
        item.options.palette = newValue
      }
    }
  }
]
;/**
 * Mixin for all dashboard items that contain other items.
 */
ds.models.container =
  (function() {
    'use strict'

    function init(target, data) {
      if (data && data.items) {
        target.items = data.items.map(function(i) {
                         return ds.models.factory(i)
                       })
      }
    }

    function extend(builder, options) {
      Object.defineProperty(builder.target, 'is_container', {value: true})
      builder.property('items', {init: []})
             .property('length', {
               get: function(context) {
                 return context.items.length
               }
             })

      var self = builder.target

      self.find = function(item_or_id) {
        var id = item_or_id
        if (item_or_id instanceof Object && item_or_id.item_id) {
          id = item_or_id.item_id
        }
        for (var i = 0; i < self.items.length; i++) {
          if (self.items[i].item_id === id) {
            return Number(i)
          }
        }
        return -1
      }

      self.contains = function(item_or_id) {
        return self.find(item_or_id) > -1
      }

      self.visit = function(visitor) {
        visitor(self)
        self.items.forEach(function(item) {
          if (item.visit && typeof(item.visit) === 'function') {
            item.visit(visitor)
          } else {
            visitor(item)
          }
        })
        return self
      }

      self.add = function(item) {
        if (typeof(item) === 'string') {
          item = ds.models.factory(item)
        }
        self.items.push(item)
        self.notify('items')
        /* This should go in an event handler */
        ds.manager.current.dashboard.update_index()
        ds.manager.update_item_view(self)
        return self
      }

      self.add_after = function(item, new_item) {
        var index = self.find(item)
        if ((index === -1) || index === (self.length - 1)) {
          self.items.push(new_item)
        } else {
          self.items.splice(index + 1, 0, new_item)
        }
        self.notify('items')
        return self
      }

      self.remove = function(item) {
        var index = self.find(item)
        if (index < 0) {
          return false
        }
        self.items.splice(index, 1)
        self.notify('items')
        return true
      }

      /**
       * Move the position of a child item in the list, either up or
       * down one position. The position will not wrap - if the child
       * is a the beginning or end of the list, it will not be moved.
       *
       * @param item {object} A dashboard item. Must be a child of
       *                      this container.
       * @param increment {number} Either 1 to move the element up one
       *                           place, or -1 to move it back one
       *                           element.
       */
      self.move = function(item, increment) {
        var index = self.find(item)
        if (index < 0) {
          return false
        }
        if (index == 0 && increment < 0) {
          return false
        }
        if (index == (self.length - 1) && increment > 0) {
          return false
        }
        var target_index = index + increment
        var tmp = self.items[target_index]
        self.items[target_index] = item
        self.items[index] = tmp
        self.notify('items')
        return true
      }

      return builder
    }

    function json(target, data) {
      data = data || {}
      data.items = target.items.map(function(i) {
                     return i.toJSON()
                   })
      return data
    }

    return {
      extend: extend,
      init: init,
      json: json
    }
  })()
;/**
 * Base mixin for all dashboard items
 */
ds.models.item =
  (function() {
    'use strict'

    function init(target, data) {
      if (data) {
        if (data.item_type)
          target.item_type = data.item_type
        if (data.item_id)
          target.item_id = data.item_id
        target.query = data.query
        target.css_class = data.css_class
        target.height = data.height
        target.style = data.style
        if (data.thresholds)
          target.thresholds = ds.models.thresholds(data.thresholds)
      }
      return target
    }

    function extend(builder, options) {
      Object.defineProperty(builder.target, 'is_dashboard_item', {value: true})
      options = options || {}
      builder.property('item_type', {init: options.item_type })
             .property('item_id')
             .property('css_class')
             .property('height')
             .property('style')
             .property('thresholds') // moving to Query
             .property('interactive')
             .property('dashboard')
             .property('query', {
               get: function(context) {
                 if (typeof(context.query) === 'string' && context.dashboard) {
                   return context.dashboard.definition.queries[context.query]
                 } else {
                   return context.query
                 }
               }
             })
             .property('query_override', {
               get: function(context) {
                 if (typeof(context.query_override) === 'string' && context.dashboard) {
                   return context.dashboard.definition.queries[context.query_override]
                 } else {
                   return context.query_override
                 }
               }
             })
             .property('is_immediate_query', {
               get: function(context) {
                 return typeof(context.query) !== 'string'
               }
             })

      /**
       * Operations
       */

      var self = builder.target

      self.render = function() {
        var item_type = ds.models[self.item_type]
        if (item_type.template) {
          if (item_type.data_handler && (self.query || self.query_override)) {
            var query = self.query_override || self.query
            if (typeof(query) === 'string') {
              console.log('ERROR: unresolved query ' + query + ' for item ' + self.item_id)
              return
            }
            query.on_load(function(q) {
              item_type.data_handler(q, self)
            })
          }
          return item_type.template({item: self})
        } else {
          return "<p>Unknown item type <code>" + self.item_type + "</code></p>"
        }
      }

      self.visit = function(visitor) {
        visitor(self)
      }

      self.clone = function() {
        return ds.models.make(self.toJSON()).set_item_id(null)
      }

      /**
       * Various visitors for convenience.
       */

      self.flatten = function() {
        var flat = []
        self.visit(function(item) {
          if (item.item_type) {
            flat.push(item)
          }
        })
         return flat
      }

      self.get_queries = function() {
        var queries = {}
        self.visit(function(i) {
          var query = i.query || i.query_override
          if (query && query.is_query) {
            queries[query.name] = query
          }
        })
        return queries
      }

      return builder
    }

    function json(target, data) {
      data = data || {}
      data.item_type = target.item_type
      if (target.item_id)
        data.item_id = target.item_id
      if (target.query) {
        data.query = target.is_immediate_query ? target.query.toJSON() : target.query.name
      }
      if (target.css_class)
        data.css_class = target.css_class
      if (target.height)
        data.height = target.height
      if (target.style)
        data.style = target.style
      if (target.thresholds) {
        data.thresholds = target.thresholds.toJSON()
      }
      return data
    }

    return {
      extend: extend,
      init: init,
      json: json
    }
  })()

ds.models.item.Style = {
  WELL:            'well',
  CALLOUT_NEUTRAL: 'callout_neutral',
  CALLOUT_INFO:    'callout_info',
  CALLOUT_SUCCESS: 'callout_success',
  CALLOUT_WARNING: 'callout_warning',
  CALLOUT_DANGER:  'callout_danger',
  ALERT_NEUTRAL:   'alert_neutral',
  ALERT_INFO:      'alert_info',
  ALERT_SUCCESS:   'alert_success',
  ALERT_WARNING:   'alert_warning',
  ALERT_DANGER:    'alert_danger',
}

ds.models.item.Transform = {
  SUM: 'sum',
  MIN: 'min',
  MAX: 'max',
  MEAN: 'mean',
  MEDIAN: 'median'
}

ds.models.item.interactive_properties = [
  'query',
  { id: 'css_class', category: 'base'} ,
  { id: 'height', type: 'number', category: 'base' }
]
;ds.models.Axis = function(data) {
  'use strict'

  var self = limivorous.observable()
                       .property('visible')
                       .property('label')
                       .property('label_distance')
                       .property('format')
                       .property('min')
                       .property('max')
                       .build()

  Object.defineProperty(self, 'is_axis', {value: true})

  if (data) {
    self.value = data.visible
    self.label = data.label
    self.label_distance = data.label_distance
    self.format = data.format
    self.min = data.min
    self.max = data.max
  }

  self.toJSON = function() {
    return {
      visible: self.visible,
      label: self.label,
      label_distance: self.label_distance,
      format: self.format,
      min: self.min,
      max: self.max
    }
  }

  return self
}
;ds.register_dashboard_item('cell', {

  display_name: 'Cell',
  category: 'structural',

  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .extend(ds.models.item, {item_type: 'cell'})
                         .extend(ds.models.container)
                         .property('span', { init: 3 })
                         .property('offset')
                         .property('align')
                         .build()

    if (data) {
      self.span = data.span || self.span
      self.offset = data.offset
      self.align = data.align
    }
    ds.models.item.init(self, data)
    ds.models.container.init(self, data)

    self.toJSON = function() {
      var data = ds.models.container.json(self, ds.models.item.json(self))
      if (self.span)
        data.span = self.span
      if (self.offset)
        data.offset = self.offset
      if (self.align)
      data.align = self.align
      return data
    }

    return self
  },

  template: ds.templates.models.cell,

  interactive_properties: [
    'style',
    'css_class',
    { id: 'span', type: 'number' },
    { id: 'offset', type: 'number' },
    {
      id: 'align',
      type: 'select',
      edit_options: {
        source: [
          undefined,
          'left',
          'center',
          'right'
        ]
      }
    }
  ]

})
;/**
 * A summation table that compares two arbitrary queries.
 */
ds.register_dashboard_item('comparison_summation_table', {

  display_name: 'Comparison Summation Table',
  icon: 'fa fa-table',
  category: 'data-table',

  /**
   * Definition of the model object.
   */
  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .property('striped', {init: false})
                         .property('format', {init: ',.3s'})
                         .property('title')
                         .property('query_other', {
                           get: function(context) {
                             if (typeof(context.query_other) === 'string' && context.dashboard) {
                               return context.dashboard.definition.queries[context.query_other]
                             } else {
                               return context.query_other
                             }
                           }
                         })
                         .extend(ds.models.item, {item_type: 'comparison_summation_table'})
                         .build()

    Object.defineProperty(self, 'requires_data', {value: true})

    function update_query() {
      if ((self.query && self.query.is_query) && (self.query_other && self.query_other.is_query)) {
        self.query_override =
          self.query.join(self.query_other).set_name(self.item_id + '_joined')
        self.query_override.render_templates(ds.context().variables)
      }
    }

    if (data) {
      if (typeof(data.striped) !== 'undefined')
        self.striped = data.striped
      self.title = data.title
      self.format = data.format || self.format
      self.query_other = data.query_other
    }
    self.on('change:query', function(e) {
      update_query()
    }).on('change:query_other', function(e) {
      update_query()
    }).on('change:dashboard', function(e) {
      update_query()
    })
    ds.models.item.init(self, data)
    update_query()

    self.toJSON = function() {
      var data = ds.models.item.json(self)
      data.format = self.format
      data.striped = self.striped
      data.title = self.title
      if (self.query_other) {
        data.query_other = self.query_other.name
      }
      return data
    }

    return self
  },

  /**
   * Handler to update the rendered DOM once the data query has been
   * evaluated.
   */
  data_handler: function(query, item) {
    var body = $('#' + item.item_id + ' tbody')
    var now  = query.data[0].summation
    var then = query.data[1].summation
    var diff = ds.models.data.Summation(now).subtract(then);
    var properties = ['mean', 'min', 'max', 'sum']
    var float_margin = 0.000001
    properties.forEach(function(prop) {
      var value = diff[prop]

      if (value > float_margin)
        diff[prop + '_class'] = 'ds-diff-plus'
      else if (value < -float_margin)
        diff[prop + '_class'] = 'ds-diff-minus'

      if ((float_margin > value) && (value > -float_margin))
        value = 0.0

      var pct = (now[prop] / then[prop]) - 1
      pct = isNaN(pct) ? 0.0 : pct
      diff[prop + '_pct'] = d3.format(',.2%')(Math.abs(pct))
      diff[prop] = Math.abs(value)
    })
    body.empty()
    body.append(ds.templates.models.comparison_summation_table_body({
      now:  now,
      then: then,
      diff: diff,
      item: item
    }))
  },

  template: ds.templates.models.comparison_summation_table,

  interactive_properties: [
    {
      id: 'striped', type: 'boolean'
    },
    'format',
    'title',
    'query',
    {
      id: 'query_other',
      category: 'base',
      template: '{{item.query_other.name}}',
      edit_options: {
        type: 'select',
        source: function() {
          var queries = ds.manager.current.dashboard.definition.queries
          return Object.keys(queries).map(function(k) {
                   return { value: k, text: k }
                 })
        },
        value: function(item) {
          return item.query_other ? item.query_other.name : undefined
        }
      }
    },
    {
      id: 'css_class', category: 'base'
    }
  ]
})
;ds.register_dashboard_item('dashboard_definition', {

  category: false,

  constructor: function(data) {
    "use strict"

    var self = limivorous.observable()
                         .property('queries', {init: {}})
                         .property('options', {init: {}})
                         .extend(ds.models.item, {item_type: 'dashboard_definition'})
                         .extend(ds.models.container)
                         .build()
      , log = ds.log.logger('tessera.item.definition')

    if (data && data.queries) {
      for (var key in data.queries) {
        var query = data.queries[key]
        self.queries[key] = (typeof(query) === 'string' || query instanceof Array)
                          ? ds.models.data.Query({name: key, targets: query})
                          : ds.models.data.Query(query)
      }
    }
    ds.models.item.init(self, data)
    ds.models.container.init(self, data)

    /**
     * Operations
     */

    self.summarize = function() {
      var counts = {}
      self.visit(function(item) {
        if (typeof(counts[item.item_type]) === 'undefined') {
          counts[item.item_type] = 1
        } else {
          counts[item.item_type] = counts[item.item_type] + 1
        }
      })
      return counts
    }

    self.render_templates = function(context) {
      for (var key in self.queries) {
        self.queries[key].render_templates(context)
      }
      self.visit(function(item) {
        if (item.query_override) {
          item.query_override.render_templates(context)
        }
        if ((item !== self) && item.render_templates) {
          item.render_templates(context)
        }
      })
      return self
    }

    self.cleanup = function() {
      log.debug('cleanup()')
      for (var key in self.queries) {
        self.queries[key].off()
      }
    }

    self.load_all = function(options, fire_only) {
      log.debug('load_all()')
      self.options = options || self.options
      for (var key in self.queries) {
        self.queries[key].load(self.options, fire_only)
      }
      self.visit(function(item) {
        if (item.query_override) {
          log.debug('load_all(): loading query override ' + item.query_override.name)
          item.query_override.load(self.options, fire_only)
        }
      })
      return self
    }

    self.add_query = function(query) {
      self.queries[query.name] = query
      query.options = self.options
      return self
    }

    /**
     * Delete a query and null out any references to it.
     */
    self.delete_query = function(query_name) {
      self.visit(function(item) {
        if (item.query && (item.query.name === query_name)) {
          item.query = undefined
        }
      })
      delete self.queries[query_name]
      return self
    }

    /**
     * Rename a query and update any references to it.
     */
    self.rename_query = function(old_name, new_name) {
      var query = self.queries[old_name]
      if (!query)
        return self
      var updated = []
      self.visit(function(item) {
        if (item.query && (item.query.name == old_name)) {
          item.query = new_name
          updated.push(item)
        }
      })
      query.name = new_name
      self.add_query(query)
      delete self.queries[old_name]
      return updated
    }

    self.toJSON = function() {
      var q = {}
      for (var key in self.queries) {
        q[key] = self.queries[key].toJSON()
      }
      return ds.models.container.json(self, ds.models.item.json(self, {queries: q}))
    }

    return self
  },

  template: ds.templates.models.definition

})
;ds.register_dashboard_item('donut_chart', {

  display_name: 'Donut Chart',
  icon: 'fa fa-pie-chart',
  category: 'chart',

  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .extend(ds.models.item, {item_type: 'donut_chart'})
                         .extend(ds.models.chart)
                         .build()
    Object.defineProperty(self, 'requires_data', {value: true})

    ds.models.chart.init(self, data)
    ds.models.item.init(self, data)

    self.toJSON = function() {
      return ds.models.chart.json(self, ds.models.item.json(self))
    }

    return self
  },

  data_handler: function(query, item) {
    ds.charts.donut_chart($("#" + item.item_id + ' .ds-graph-holder'), item, query)
  },

  template: ds.templates.models.donut_chart,

  interactive_properties: ds.models.chart.interactive_properties
                            .concat(ds.models.item.interactive_properties)

})
;ds.register_dashboard_item('heading', {

  display_name: 'Heading',
  icon: 'fa fa-header',
  category: 'display',

  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .property('text')
                         .property('level', {init: 1})
                         .property('description')
                         .extend(ds.models.item, {item_type: 'heading'})
                         .build()
    if (data) {
      self.text = data.text
      self.level = data.level || self.level
      self.description = data.description
    }
    ds.models.item.init(self, data)

    self.toJSON = function() {
      var data = ds.models.item.json(self)
      if (self.text)
        data.text = self.text
      if (self.level)
        data.level = self.level
      if (self.description)
        data.description = self.description
      return data
    }

    return self
  },

  template: ds.templates.models.heading,

  interactive_properties: [
    'text',
    { id: 'level', type: 'number' },
    'description'
  ]

})
;ds.register_dashboard_item('jumbotron_singlestat', {

  display_name: 'Jumbotron Singlestat',
  icon: 'fa fa-subscript',
  category: 'data-table',

  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .property('title')
                         .property('units')
                         .property('format', {init: ',.3s'})
                         .property('index')
                         .property('transform', {init: 'mean'})
                         .extend(ds.models.item, {item_type: 'jumbotron_singlestat'})
                         .build()
    Object.defineProperty(self, 'requires_data', {value: true})

    if (data) {
      self.title = data.title
      self.units = data.units
      self.format = data.format || self.format
      self.index = data.index
      self.transform = data.transform || self.transform
    }
    ds.models.item.init(self, data)

    self.toJSON = function() {
      var data = ds.models.item.json(self)
      if (self.title)
        data.title = self.title
      if (self.format)
        data.format = self.format
      if (self.transform)
        data.transform = self.transform
      if (self.units)
        data.units = self.units
      if (self.index)
        data.index = self.index
      return data
    }

    return self
  },

  data_handler: function(query, item) {
    var element = $('#' + item.item_id + ' span.value')
    var value = query.summation[item.transform]
    if (item.index) {
      value = query.data[item.index].summation[item.transform]
    }
    $(element).text(d3.format(item.format)(value))
  },

  template: ds.templates.models.jumbotron_singlestat,

  interactive_properties: [ 'title',
                            'units',
                            'format',
                            { id: 'index', type: 'number' },
                            'transform'
                          ].concat(ds.models.item.interactive_properties)

})
;ds.register_dashboard_item('markdown', {

  display_name: 'Markdown',
  icon: 'fa fa-code',
  category: 'display',

  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .property('text')
                         .property('expanded_text')
                         .property('raw', {init: false})
                         .extend(ds.models.item, {item_type: 'markdown'})
                         .build()

    if (data) {
      self.text = data.text
      if (data.raw !== undefined) {
        self.raw = data.raw
      }
    }
    ds.models.item.init(self, data)

    self.render_templates = function(context) {
      try {
        self.expanded_text = ds.render_template(self.text, context)
      } catch (e) {
        self.expanded_text = e.toString()
      }
    }

    self.toJSON = function() {
      return ds.models.item.json(self, {
        text: self.text,
        raw: self.raw
      })
    }

    return self
  },

  template: ds.templates.models.markdown,

  interactive_properties: [
    {
      id: 'markdown.text',
      type: 'textarea',
      name: 'text'
    },
    { id: 'height', type: 'number' },
    'css_class'
  ]

})
;ds.register_dashboard_item('percentage_table', {

  display_name: 'Percentage Table',
  icon: 'fa fa-table',
  category: 'data-table',

  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .property('format', {init: ',.3s'})
                         .property('title')
                         .property('include_sums', {init: false})
                         .extend(ds.models.item, {item_type: 'percentage_table'})
                         .build()
    Object.defineProperty(self, 'requires_data', {value: true})

    if (data) {
      self.include_sums = data.include_sums
      self.title = data.title
      self.format = data.format || self.format
    }
    ds.models.item.init(self, data)

    self.toJSON = function() {
      var data = ds.models.item.json(self)
      if (self.format)
        data.format = self.format
      if (self.title)
        data.title = self.title
      data.include_sums = self.include_sums
      return data
    }

    return self
  },

  data_handler: function(query, item) {

    query.data.forEach(function(series) {
      series.summation.percent = 1 / (query.summation.sum / series.summation.sum)
    })

    var holder = $('#' + item.item_id + ' .ds-percentage-table-holder')
    holder.empty()
    holder.append(ds.templates.models.percentage_table_data({item:item, query:query}))
  },

  template: ds.templates.models.percentage_table,

  interactive_properties: [
    'format',
    'title',
    {
      id: 'include_sums',
      type: 'boolean'
    }
  ].concat(ds.models.item.interactive_properties)
})
;ds.register_dashboard_item('row', {

  display_name: 'Row',
  category: 'structural',

  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .extend(ds.models.item, {item_type: 'row'})
                         .extend(ds.models.container)
                         .build()

    ds.models.item.init(self, data)
    ds.models.container.init(self, data)

    self.toJSON = function() {
      return ds.models.container.json(self, ds.models.item.json(self))
    }

    return self
  },

  template: ds.templates.models.row,

  interactive_properties: [
    'style', 'css_class'
  ]

})
;ds.register_dashboard_item('section', {

  display_name: 'Section',
  category: 'structural',

  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .property('title')
                         .property('layout', {init: 'fixed'})
                         .extend(ds.models.item, {item_type: 'section'})
                         .extend(ds.models.container)
                       .build()

    if (data) {
      self.layout = data.layout || layout
      self.title = data.title
    }
    ds.models.item.init(self, data)
    ds.models.container.init(self, data)

    self.toJSON = function() {
      var data = ds.models.container.json(self, ds.models.item.json(self))
      if (self.layout)
        data.layout = self.layout
      if (self.title)
        data.title = self.title
      return data
    }

    return self
  },

  template: ds.templates.models.section,

  interactive_properties: [ { id: 'layout',
                              type: 'select',
                              edit_options: {
                                source: [
                                  'fixed',
                                  'fluid',
                                  'none'
                                ]
                              }
                            },
                            'style',
                            'css_class',
                            'title'
                          ]
})
;ds.register_dashboard_item('separator', {

  display_name: 'Separator',
  icon: 'fa fa-arrows-h',
  category: 'display',

  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .extend(ds.models.item, {item_type: 'separator'})
                         .build()

    ds.models.item.init(self, data)

    self.toJSON = function() {
      return ds.models.item.json(self)
    }

    return self
  },

  template: ds.templates.models.separator,

  interactive_properties: [ 'css_class' ]

})
;ds.register_dashboard_item('simple_time_series', {

  display_name: 'Simple Time Series',
  icon: 'fa fa-line-chart',
  category: 'chart',

  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .extend(ds.models.item, {item_type: 'simple_time_series'})
                         .extend(ds.models.chart)
                         .property('filled', {init: false})
                         .build()

    if (data) {
      self.filled = data.filled !== false
    }
    ds.models.chart.init(self, data)
    ds.models.item.init(self, ds.extend({ height: 1 }, data))

    self.toJSON = function() {
      return ds.models.chart.json(self, ds.models.item.json(self, {
        filled: self.filled
      }))
    }

    return self
  },

  data_handler: function(query, item) {
    if (item.filled) {
      ds.charts.simple_area_chart($('#' + item.item_id + ' .ds-graph-holder'), item, query)
    } else {
      ds.charts.simple_line_chart($('#' + item.item_id + ' .ds-graph-holder'), item, query)
    }
  },

  template: ds.templates.models.simple_time_series,

  interactive_properties: [ { id: 'filled', type: 'boolean' } ]
                            .concat(ds.models.chart.interactive_properties,
                                    ds.models.item.interactive_properties)
})
;ds.register_dashboard_item('singlegraph', {

  display_name: 'Singlegraph',
  icon: 'fa fa-image',
  category: 'chart',

  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .property('format', {init: ',.1s'})
                         .property('transform', {init: 'mean'})
                         .extend(ds.models.item, {item_type: 'singlegraph'})
                         .extend(ds.models.chart)
                         .build()

    if (data) {
      self.format = data.format || self.format
      self.transform = data.transform || self.transform
    }
    ds.models.chart.init(self, data)
    ds.models.item.init(self, data)
    if (!self.height) {
      self.height = 1
    }

    self.toJSON = function() {
      return ds.models.chart.json(self, ds.models.item.json(self, {
        format: self.format,
        transform: self.transform
      }))
    }
    return self
  },

  data_handler: function(query, item) {
    ds.charts.simple_area_chart($("#" + item.item_id + ' .ds-graph-holder'), item, query)
    item.options.margin = { top: 0, left: 0, bottom: 0, right: 0 }
    var label = query.data[item.index || 0].key
    var value = query.summation[item.transform]
    if (item.index) {
      value = query.data[item.index].summation[item.transform]
    }
    $('#' + item.item_id + ' span.value').text(d3.format(item.format)(value))
    $('#' + item.item_id + ' span.ds-label').text(label)
  },

  template: ds.templates.models.singlegraph,

  interactive_properties: [ 'format', 'transform' ]
                            .concat(ds.models.chart.interactive_properties,
                                    ds.models.item.interactive_properties)

})
;ds.register_dashboard_item('singlestat',  {

  display_name: 'Singlestat',
  icon: 'fa fa-subscript',
  category: 'data-table',

  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .property('title')
                         .property('units')
                         .property('format', {init: ',.3s'})
                         .property('index')
                         .property('transform', {init: 'mean'})
                         .extend(ds.models.item, {item_type: 'singlestat'})
                         .build()
    Object.defineProperty(self, 'requires_data', {value: true})

    if (data) {
      self.title = data.title
      self.units = data.units
      self.format = data.format || self.format
      self.index = data.index
      self.transform = data.transform || self.transform
    }
    ds.models.item.init(self, data)

    self.toJSON = function() {
      var data = ds.models.item.json(self)
      if (self.title)
        data.title = self.title
      if (self.format)
        data.format = self.format
      if (self.transform)
        data.transform = self.transform
      if (self.units)
        data.units = self.units
      if (self.index)
        data.index = self.index
      return data
    }

    return self
  },

  data_handler: function(query, item) {
    if (!query.summation)
      return
    var element = $('#' + item.item_id + ' span.value')
    var value = query.summation[item.transform]
    if (item.index) {
      value = query.data[item.index].summation[item.transform]
    }
    element.text(d3.format(item.format)(value))
  },

  template: ds.templates.models.singlestat,

  interactive_properties: [ 'title',
                            'units',
                            'format',
                            { id: 'index', type: 'number' },
                            'transform'
                          ].concat(ds.models.item.interactive_properties)

})
;ds.register_dashboard_item('stacked_area_chart', {

  display_name: 'Stacked Area Chart',
  icon: 'fa fa-area-chart',
  category: 'chart',

  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .extend(ds.models.item, {item_type: 'stacked_area_chart'})
                         .extend(ds.models.chart)
                         .build()

    ds.models.chart.init(self, data)
    ds.models.item.init(self, data)

    self.toJSON = function() {
      return ds.models.chart.json(self, ds.models.item.json(self))
    }

    return self
  },

  data_handler: function(query, item) {
    ds.log.logger('tessera.items.stacked_area_chart').debug('data_handler(): '
                                                           + query.name + '/' + item.item_id)
    ds.charts.stacked_area_chart($('#' + item.item_id + ' .ds-graph-holder'), item, query)
  },

  template: ds.templates.models.stacked_area_chart,

  interactive_properties: ds.models.chart.interactive_properties
                            .concat(ds.models.item.interactive_properties)

})
;ds.register_dashboard_item('standard_time_series', {

  display_name: 'Standard Time Series',
  icon: 'fa fa-line-chart',
  category: 'chart',

  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .extend(ds.models.item, {item_type: 'standard_time_series'})
                         .extend(ds.models.chart)
                         .build()

    ds.models.chart.init(self, data)
    ds.models.item.init(self, data)

    self.toJSON = function() {
      return ds.models.chart.json(self, ds.models.item.json(self))
    }

    return self
  },

  data_handler: function(query, item) {
    ds.charts.standard_line_chart($('#' + item.item_id + ' .ds-graph-holder'), item, query)
  },

  template: ds.templates.models.standard_time_series,

  interactive_properties: ds.models.chart.interactive_properties
                            .concat(ds.models.item.interactive_properties)

})
;ds.register_dashboard_item('summation_table', {

  display_name: 'Summation Table',
  icon: 'fa fa-table',
  category: 'data-table',

  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .property('striped', {init: false})
                         .property('show_color', {init: false})
                         .property('sortable', {init: false})
                         .property('format', {init: ',.3s'})
                         .property('title')
                         .property('options')
                         .extend(ds.models.item, {item_type: 'summation_table'})
                         .build()

    Object.defineProperty(self, 'requires_data', {value: true})

    if (data) {
      self.striped = Boolean(data.striped)
      self.show_color = Boolean(data.show_color)
      self.sortable = Boolean(data.sortable)
      self.title = data.title
      self.format = data.format || self.format
      self.options = data.options
    }
    ds.models.item.init(self, data)

    self.toJSON = function() {
      var data = ds.models.item.json(self)
      if (self.format)
        data.format = self.format
      if (self.striped)
        data.striped = self.striped
      if (self.show_color)
        data.show_color = self.show_color
      if (self.sortable)
        data.sortable = self.sortable
      if (self.title)
        data.title = self.title
      if (self.options)
        data.options = self.options
      return data
    }

    return self
  },

  data_handler: function(query, item) {
    ds.log.logger('tessera.items.summation_table').debug('data_handler(): '
                                                        + query.name + '/' + item.item_id)
    var options = item.options || {}
    var palette = ds.charts.util.get_palette(options.palette)
    var body = $('#' + item.item_id + ' tbody')
    body.empty()
    query.data.forEach(function(series, i) {
      var color = palette[i % palette.length]
      body.append(ds.templates.models.summation_table_row({series:series, item:item, color: color}))
    })
    if (item.sortable) {
      body.parent().DataTable({
        paging: false,
        searching: false,
        info: false
      })
    }
  },

  template: ds.templates.models.summation_table,

  interactive_properties: [
    { id: 'striped', type: 'boolean' },
    { id: 'show_color', type: 'boolean' },
    { id: 'sortable', type: 'boolean' },
    'format',
    'title',
    'chart.palette',
  ].concat(ds.models.item.interactive_properties)
})
;/**
 * This iteration of timeshift_summation_table takes a single query
 * and applies a timeShift() value to it. Another approach is to take
 * two arbitrary queries and compare them (so it's not strictly a
 * time-based comparison).
 *
 * This version is slightly simpler to implement, because we don't
 * need to join on two asynchronously fetched queries.
 */
ds.register_dashboard_item('timeshift_summation_table', {

  display_name: 'Timeshift Summation Table',
  icon: 'fa fa-table',
  category: 'data-table',

  /**
   * Definition of the model object.
   */
  constructor: function(data) {
    'use strict'

    var self = limivorous.observable()
                         .property('striped', {init: false})
                         .property('format', {init: ',.3s'})
                         .property('title')
                         .property('shift', {init: "1d"})
                         .extend(ds.models.item, {item_type: 'timeshift_summation_table'})
                         .build()

    Object.defineProperty(self, 'requires_data', {value: true})

    function update_query() {
      if (self.query && self.query.is_query) {
        self.query_override =
          self.query.join(self.query.shift(self.shift)).set_name(self.item_id + '_shifted')
        self.query_override.render_templates(ds.context().variables)
      }
    }

    if (data) {
      if (typeof(data.striped) !== 'undefined')
        self.striped = data.striped
      self.title = data.title
      self.format = data.format || self.format
      self.shift = data.shift || self.shift
    }
    self.on('change:query', function(e) {
      update_query()
    }).on('change:dashboard', function(e) {
      update_query()
    }).on('change:shift', function(e) {
      update_query()
    })
    ds.models.item.init(self, data)

    self.toJSON = function() {
      var data = ds.models.item.json(self)
      data.format = self.format
      data.striped = self.striped
      data.title = self.title
      data.shift = self.shift
      return data
    }

    return self
  },

  /**
   * Handler to update the rendered DOM once the data query has been
   * evaluated.
   */
  data_handler: function(query, item) {
    var body = $('#' + item.item_id + ' tbody')
    var now  = query.data[0].summation
    var then = query.data[1].summation
    var diff = ds.models.data.Summation(now).subtract(then);
    var properties = ['mean', 'median', 'min', 'max', 'sum']
    var float_margin = 0.000001
    properties.forEach(function(prop) {
      var value = diff[prop]

      if (value > float_margin)
        diff[prop + '_class'] = 'ds-diff-plus'
      else if (value < -float_margin)
        diff[prop + '_class'] = 'ds-diff-minus'

      if ((float_margin > value) && (value > -float_margin))
        value = 0.0

      var pct = (now[prop] / then[prop]) - 1
      pct = isNaN(pct) ? 0.0 : pct
      diff[prop + '_pct'] = d3.format(',.2%')(Math.abs(pct))
      diff[prop] = Math.abs(value)
    })
    body.empty()
    body.append(ds.templates.models.timeshift_summation_table_body({
      now:  now,
      then: then,
      diff: diff,
      item: item
    }))
  },

  template: ds.templates.models.timeshift_summation_table,

  interactive_properties: [
    { id: 'striped', type: 'boolean' },
    'format',
    'title',
    'query',
    'shift',
    { id: 'css_class', category: 'base' }
  ],

  /**
   * Additional actions specific to this presentation type to be added
   * to the presentation actions menu.
   */
  actions: [
    ds.action({
      name:    'timeshift_1h',
      display: '1 Hour Ago',
      icon:    'fa fa-clock-o',
      handler: function(action, item) {
        item.shift = '1h'
        ds.manager.update_item_view(item)
      }
    }),
    ds.action({
      name:    'timeshift_1d',
      display: '1 Day Ago',
      icon:    'fa fa-clock-o',
      handler: function(action, item) {
        item.shift = '1d'
        ds.manager.update_item_view(item)
      }
    }),
    ds.action({
      name:    'timeshift_1w',
      display: '1 Week Ago',
      icon:    'fa fa-clock-o',
      handler: function(action, item) {
        item.shift = '1w'
        ds.manager.update_item_view(item)
      }
    }),
    ds.action({
      name:    'timeshift_user_input',
      display: 'Pick interval...',
      icon:    'fa fa-clock-o',
      handler: function(action, item) {
        bootbox.prompt("Enter a time shift interval", function(result) {
          if (result) {
            item.shift = result
            ds.manager.update_item_view(item)
          }
        })
      }
    })
  ]
})
;ds.models.dashboard = function(data) {
  "use strict"

  var self = limivorous.observable()
                       .property('id')
                       .property('href')
                       .property('view_href')
                       .property('definition_href')
                       .property('creation_date')
                       .property('last_modified_date')
                       .property('imported_from')
                       .property('index')
                       .property('title')
                       .property('category')
                       .property('summary')
                       .property('description')
                       .property('definition', {
                         update: function() {
                           self.update_index()
                         }
                       })
                       .property('tags', {
                         init: [],
                         transform: function(tags) {
                           return tags.map(function(t) {
                                    return ds.models.tag(t)
                                  })
                         }
                       })
                       .build()
    , next_id = 0

  self.index = {}

  self.visit = function(visitor) {
    visitor(self)
    if (self.definition) {
      self.definition.visit(visitor)
    }
    return self
  }

  self.next_id = function() {
    while (true) {
      var id = 'd' + next_id++
      if (typeof(self.index[id]) === 'undefined') {
        return id
      }
    }
  }

  self.reindex = function() {
    next_id = 0
    self.visit(function(item) {
      item.item_id = self.next_id()
    })
  }

  self.update_index = function() {
    var index = {}
    self.visit(function(item) {
      if (item.is_dashboard_item) {
        if ( !item.item_id ) {
          item.item_id = self.next_id()
        }
        if (index[item.item_id]) {
          console.log('ERROR: item_id + ' + item.item_id + ' is already indexed.')
        }
        index[item.item_id] = item
        item.set_dashboard(self)
      }
    })
    self.index = index
    return self
  }

  if (data) {
    self.set_id(data.id)
        .set_title(data.title)
        .set_category(data.category)
        .set_summary(data.summary)
        .set_description(data.description)
        .set_creation_date(data.creation_date)
        .set_last_modified_date(data.last_modified_date)
        .set_imported_from(data.imported_from)
        .set_href(data.href)
        .set_view_href(data.view_href)
        .set_definition_href(data.definition_href)
    if (data.definition) {
        self.set_definition(ds.models.factory(data.definition))
    }
    if (data.tags && data.tags.length) {
      self.tags = data.tags.map(function(t) {
                    return ds.models.tag(t)
                  })
    }
    self.visit(function(item) {
      next_id++
    })
    self.next_id()
  }

  /**
   * Operations
   */

  self.get_item = function(id) {
    return self.index[id]
  }

  self.find_parent = function(item_or_id) {
    var parent = undefined
    self.visit(function(item) {
      if (item.is_container && item.contains(item_or_id)) {
        parent = item
      }
    })
    return parent
  }

  self.set_items = function(items) {
    self.definition.items = items
    self.update_index()
    return self
  }

  self.render = function() {
    return self.definition.render()
  }

  self.load_all = function(options, fire_only) {
    self.definition.load_all(options, fire_only)
    return self
  }

  self.cleanup = function() {
    self.definition.cleanup()
  }

  self.render_templates = function(context) {
    self.description = ds.safe_render_template(self.description, context)
    self.title       = ds.safe_render_template(self.title, context)
    self.summary     = ds.safe_render_template(self.summary, context)
    if (self.definition) {
      self.definition.render_templates(context)
    }
    return self
  }

  self.flatten = function() {
    return self.definition ? self.definition.flatten() : []
  }

  /**
   * Data mutators.
   */

  self.toJSON = function() {
    return {
     id: self.id,
     title: self.title,
     category: self.category,
     summary: self.summary,
     description: self.description,
     creation_date: self.creation_date,
     last_modified_date: self.last_modified_date,
     imported_from: self.imported_from,
     tags: self.tags.map(function(t) {
             return t.toJSON()
           }),
     definition: self.definition ? self.definition.toJSON() : null,
     href: self.href,
     view_href: self.view_href,
     definition_href: self.definition_href
    }
  }
return self
}
;ds.models.data.Query = function(data) {
  "use strict"

  var self = limivorous.observable()
                       .property('targets')
                       .property('name')
                       .property('data')
                       .property('summation')
                       .property('options')
                       .property('expanded_targets')
                       .property('local_options')
                       .build()
    , log = ds.log.logger('tessera.query')

  if (data) {
    if (data instanceof Array) {
      self.targets = data
    } else if (typeof(data) === 'string') {
      self.targets = [data]
    } else if (data.targets) {
      if (data.targets instanceof Array) {
        self.targets = data.targets
      } else {
        self.targets = [data.targets]
      }
    }
    if (data.options) {
      self.options = data.options
    }
    self.name = data.name
  }

  self.DEFAULT_FROM_TIME = '-3h'

  Object.defineProperty(self, 'is_query', {value: true})

  self.render_templates = function(context) {
    self.expanded_targets = self.targets.map(function(t) {
                              try {
                                return ds.render_template(t, context)
                              } catch ( e ) {
                                ds.manager.error('Failed to expand query ' + self.name + ': ' + e)
                                return t
                              }
                            })
  }

  self.url = function(opt) {
    var options = ds.extend(self.local_options, opt, self.options)
    var url = URI(options.base_url || ds.config.GRAPHITE_URL)
              .path('/render')
              .setQuery('format', options.format || 'png')
              .setQuery('from', options.from || ds.config.DEFAULT_FROM_TIME || self.DEFAULT_FROM_TIME)
              .setQuery('tz', ds.config.DISPLAY_TIMEZONE)
    if (options.until) {
      url.setQuery('until', options.until)
    }
    var targets = self.expanded_targets || self.targets
    for (var i = 0; i < targets.length; i++) {
      url.addQuery('target', targets[i].replace(/(\r\n|\n|\r)/gm,""))
    }
    return url.href()
  }

  /**
   * Return true if the item's query has the graphite stacked()
   * function anywhere in it. If you have stacked() in the query and
   * areaMode=stack in the URL, bad shit will happen to your graph.
   */
  self.is_stacked = function() {
    var targets = self.expanded_targets || self.targets
    if (typeof(targets) === 'undefined')
      return false
    var stacked = false
    self.targets.forEach(function(target) {
      if (target.indexOf('stacked') > -1) {
        stacked = true
      }
    })
    return stacked
  }

  /**
   * Asynchronously load the data for this query from the graphite
   * server, notifying any listening consumers when the data is
   * available.
   *
   * @param {Object} options Parameters for generating the URL to
   * load. Valid properties are:
   *   * base_url (required)
   *   * from
   *   * until
   *   * ready
   * @param {boolean} fire_only Just raise the event, without fetching
   *                            data.
   */
  self.load = function(opt, fire_only) {
    log.debug('load(): ' + self.name)
    self.local_options = ds.extend(self.local_options, opt)
    var options = ds.extend(self.local_options, opt, self.options)

    if (typeof(fire_only) === 'boolean' && fire_only) {
      // This is a bit of a hack for optimization, to fire the query
      // events when if we don't need the raw data because we're
      // rendering non-interactive graphs only. Would like a more
      // elegant way to handle the case.
      var ready = options.ready
      if (ready && (ready instanceof Function)) {
        ready(self)
      }

      ds.event.fire(self, 'ds-data-ready', self)
    } else {
      options.format = 'json'
      var url = self.url(options)
      ds.event.fire(self, 'ds-data-loading')
      $.ajax({
        dataType: 'json',
        url: url
      })
       .done(function(response_data, textStatus) {
        self._process(response_data)
        if (options.ready && (options.ready instanceof Function)) {
          options.ready(self)
        }
        ds.event.fire(self, 'ds-data-ready', self)
      })
       .error(function(xhr, status, error) {
        ds.manager.error('Failed to load query ' + self.name + '. ' + error)
      })
    }
  }

  /**
   * Register an event handler to be called when the query's data is
   * loaded.
   */
  self.on_load = function(handler) {
    log.debug('on(): ' + self.name)
    ds.event.on(self, 'ds-data-ready', handler)
  }

  /**
   * Remove all registered event handlers.
   */
  self.off = function() {
    log.debug('off(): ' + self.name)
    ds.event.off(self, 'ds-data-ready')
  }

  function group_targets(query) {
    return (query.targets.length > 1)
         ? 'group(' + query.targets.join(',') + ')'
         : query.targets[0]
  }

  /**
   * Return a new query with the targets timeshifted.
   */
  self.shift = function(interval) {
    var group = group_targets(self)
    return ds.models.data.Query({
      name: self.name + '_shift_' + interval,
      targets: [
        'timeShift(' + group + ', \"' + interval + '\")'
      ]
    })
  }

  /**
   * Return a new query with the targets from this query and another
   * query joined into a 2-target array, for comparison presentations.
   */
  self.join = function(other) {
    var target_self  = group_targets(self)
    var target_other = group_targets(other)
    return ds.models.data.Query({
      name: self.name + '_join_' + other.name,
      targets: [
        target_self,
        target_other
      ]
    })
  }

  /**
   * Process the results of executing the query, transforming
   * the returned structure into something consumable by the
   * charting library, and calculating sums.
   */
  self._process = function(response_data) {
    self.summation = ds.models.data.Summation(response_data)
    self.data = response_data.map(function(series) {
                  series.summation = ds.models.data.Summation(series).toJSON()
                  return series
                })
    return self
  }

  /**
   * Fetch data processed for use by a particular chart renderer, and
   * cache it in the query object so it's not re-processed over and
   * over.
   */
  self.chart_data = function(type) {
    var attribute = 'chart_data_' + type
    if (typeof(self[attribute]) === 'undefined') {
      self[attribute] = ds.charts.process_data(self.data, type)
    }
    return self[attribute]
  }

  self.toJSON = function() {
    var json = {}
    if (self.name)
      json.name = self.name
    if (self.targets)
      json.targets = self.targets
    if (self.data)
      json.data = self.data
    if (self.summation)
      json.summation = self.summation.toJSON()
    if (self.options)
      json.options = self.options

    return json
  }

  return self
}
;/**
 * Summarized stats for a data series or set of series. When
 * constructed with a data series, computes the sum, min, max, and
 * mean.
 *
 * The input format is assumed to be the JSON representation returned
 * by graphite-web.
 */
ds.models.data.Summation = function(initial_data) {
  "use strict"

  var self = limivorous.observable()
                       .property('sum', { init: 0 })
                       .property('min', { init: Number.MAX_VALUE })
                       .property('max', { init: Number.MIN_VALUE })
                       .property('mean', { init: 0 })
                       .property('median', { init: 0 })
                       .property('first', { init: 0 })
                       .property('last', { init: 0 })
                       .property('count', { init: 0 })
                       .build()
  Object.defineProperty(self, 'is_summation', {value: true})

  function if_defined(value, default_value) {
    return typeof(value) === 'undefined'
         ? default_value
         : value
  }

  /**
   * Initialize the summation
   */
  var datapoints = []

  if (initial_data && (initial_data instanceof Array) && (initial_data.length)) {
    /* This assumes that all input series have the same number of data points */
    var length = initial_data[0].datapoints.length
    var summed_datapoints = []
    for (var i = 0; i < length; i++) {
      var x = 0
      for (var n = 0; n < initial_data.length; n++) {
        x += initial_data[n].datapoints[i][0]
      }
      datapoints.push([x, initial_data[0].datapoints[i][1]])
    }
  } else if (initial_data && initial_data.datapoints) {
    datapoints = initial_data.datapoints
  }

  if (datapoints && datapoints.length) {
    /* add simple-statistics methods */
    var values = ss.mixin(datapoints.map(function(point) {
                            return point[0]
                          }))
    self.median = values.median()
    self.first = datapoints[0][0]
    self.count = datapoints.length
    if (self.first == null) {
      self.first = 0
    }
    datapoints.forEach(function(point) {
      var value = point[0] || 0
      self.last = value
      self.sum = self.sum + value
      if (value > self.max) {
        self.max = value
      }
      if (point[0] && (value < self.min)) {
        self.min = value
      }
    })
    self.mean = self.sum / self.count
  } else if (typeof(initial_data) === 'object') {
    self.sum   = if_defined(initial_data.sum,  self.sum)
    self.min   = if_defined(initial_data.min, self.min)
    self.max   = if_defined(initial_data.max, self.max)
    self.first = if_defined(initial_data.first, self.first)
    self.last  = if_defined(initial_data.last, self.last)
    self.mean  = if_defined(initial_data.mean, self.mean)
    self.mean  = if_defined(initial_data.median, self.median)
    self.count = if_defined(initial_data.count, self.count)
  }

  /**
   * Subtract other from self
   */
  self.subtract = function(other) {
    return ds.models.data.Summation({
      sum:    self.sum    - other.sum,
      min:    self.min    - other.min,
      max:    self.max    - other.max,
      mean:   self.mean   - other.mean,
      median: self.median - other.median,
      first:  self.first  - other.first,
      last:   self.last   - other.last,
      count:  self.count
    })
  }

  self.toJSON = function() {
    return {
      sum: self.sum,
      min: self.min,
      max: self.max,
      mean: self.mean,
      median: self.median,
      first: self.first,
      last: self.last,
      count: self.count
    }
  }

  return self
}
;ds.models.tag = function(data) {
  "use strict"

  var self = limivorous.observable()
                       .property('id')
                       .property('href')
                       .property('name')
                       .property('description')
                       .property('color')
                       .property('count')
                       .build()
  Object.defineProperty(self, 'is_tag', {value: true})

  if (data) {
      if (data.is_tag) {
        return data
      } else if (typeof data === 'string') {
        self.name = data
      } else {
        self.id = data.id
        self.href = data.href
        self.name = data.name
        self.description = data.description
        self.color = data.color
        self.count = data.count
      }
  }

  self.toJSON = function() {
    var json = {}
    if (self.id)
      json.id = self.id
    if (self.href)
      json.href = self.href
    if (self.name)
      json.name = self.name
    if (self.description)
      json.description = self.description
    if (self.color)
      json.color = self.color
    if (self.count)
      json.count = self.count
    return json
  }

  return self
}
;ds.models.thresholds = function(data) {
  "use strict"

  var self = limivorous.observable()
                       .property('summation_type')
                       .property('warning')
                       .property('danger')
                       .build()

  if (data) {
    self.summation_type = data.summation_type  || 'mean'
    self.warning = data.warning
    self.danger = data.danger
  }

  self.toJSON = function() {
    return {
      summation_type: self.summation_type,
      warning: self.warning,
      danger: self.danger
    }
  }

  return self
}
;/**
 * Latency - expand a single latency metric into all the percentiles,
 * based on metrics produced by Coda Hale's Metrics library for Java.
 *
 * This currently only works for bare metrics. Going to need to parse
 * graphite function expressions to handle real world cases :(
 */
ds.transforms.register({
  name: 'ExpandLatency',
  display_name: 'Expand Latency',
  transform_type: 'presentation',

  transform: function(item) {
    'use strict'
    var make        = ds.models.make
    var root_metric = item.query.targets[0]
    root_metric = root_metric.substring(0, root_metric.lastIndexOf('.'))

    function span(width, item) {
      return make('cell').set_span(width).add(item)
    }

    var metrics = [
      'Max',
      '999thPercentile',
      '99thPercentile',
      '95thPercentile',
      '75thPercentile',
      '50thPercentile',
      'Mean',
      'Min'
    ]
    var queries = {}

    var individual_rows
          = metrics.map(function(m) {
              var metric = root_metric + '.' + m
              var query = ds.models.data.Query({
                name: item.query.name + '_' + m,
                targets: [ metric ]
              })
              queries[m] = query
              return make('row')
                     .add(span(8, make('standard_time_series')
                                    .set_title(m)
                                    .set_height(3)
                                    .set_query(query)))
                       .add(span(2, make('singlestat')
                                      .set_query(query)
                                      .set_format(',.3s')
                                      .set_transform('max')
                                      .set_title('Max of ' + m))
                              .set_align('right'))
                       .add(span(2, make('singlestat')
                                      .set_query(query)
                                      .set_format(',.3s')
                                      .set_transform('mean')
                                      .set_title('Average of ' + m)))
            })

    /* TODO: being able to configure which percentile is highlighted
     * at the top would be good */
    var section = make('section')
                    .add(make('row')
                           .add(span(8, make('standard_time_series')
                                          .set_query(queries['95thPercentile'])
                                          .set_title('95th Percentile Latency')
                                          .set_height(5))
                                  .set_style('well'))
                           .add(span(4, make('cell')
                                          .add(make('jumbotron_singlestat')
                                                 .set_title('Peak 95th Percentile')
                                                 .set_query(queries['95thPercentile'])
                                                 .set_units('ms')
                                                 .set_format(',.3s')
                                                 .set_transform('max')))))
                    .add(make('separator'))
    individual_rows.forEach(function(row) {
      section.add(row)
    })
    return section
  }
})
;/**
 * Higlight the average, max average, and most deviant series. This
 * transform works best on line graphs with lots of metrics (for
 * example, CPU or disk usage across a cluster).
 *
 * TODO: enforcing that it not be used on stacked graphs would be
 * neat. Adding an `applicable_types` attribute or something could
 * control visibily of transforms, so they only appear for
 * presentations that they make sense for.
 */
ds.transforms.register({
  name:           'HighlightAverages',
  display_name:   'Highlight Averages',
  transform_type: 'presentation',

  transform: function(item) {
    var make    = ds.models.make
    var query   = item.query
    var group   = (query.targets.length > 1) ? 'group(' + query.targets.join(',') + ')' : query.targets[0]
    var bg      = Color(window.getComputedStyle($('body')[0]).backgroundColor)
    var palette = ds.charts.util.get_low_contrast_palette()

    /* Set up the modified queries */
    var query_averages = ds.models.data.Query({
      name: query.name + '_averages',
      targets: [
        group,
        'alias(lineWidth(color(averageSeries(' + group + '), "' + (bg.dark()
                                                                  ? bg.clone().lighten(4.0)
                                                                  : bg.clone().darken(1.0)).hexString()  + '"), 2), "Avg")',
        'alias(lineWidth(color(highestAverage(' + group + ', 1), "red"), 2), "Max Avg")'
      ]
    })
    var query_deviant = ds.models.data.Query({
      name: query.name + '_deviant',
      targets: [
        group,
        'alias(lineWidth(color(mostDeviant(' + group + ', 1), "red"), 2), "Most Deviant")'
      ]
    })

    /* Clone and modify the original */
    var item_averages = item.clone()
                            .set_item_type('standard_time_series')
                            .set_height(6)
                            .set_interactive(false)
                            .set_title("Average & Max Average")
                            .set_query(query_averages)
    item_averages.options.palette = palette

    var item_deviant  = item.clone()
                            .set_item_type('standard_time_series')
                            .set_height(4)
                            .set_interactive(false)
                            .set_title('Most Deviant')
                            .set_query(query_deviant)
    item_deviant.options.palette = palette

    /* And put it all together */
    return make('section')
             .add(make('row')
                    .add(make('cell')
                           .set_span(12)
                           .set_style('well')
                           .add(item_averages))
                    .add(make('cell')
                           .set_span(12)
                           .add(item_deviant))
                    .add(make('cell')
                           .set_span(12)
                           .add(item)))

  }

})
;/**
 * Focus on a single presentation.
 */
ds.transforms.register({
  name: 'Isolate',
  display_name: 'Isolate',
  transform_type: 'presentation',

  transform: function(item) {
    var make = ds.models.make
    var options = item.options || {}
    return make('section')
             .add(make('row')
                    .add(make('cell')
                           .set_span(12)
                           .set_style('well')
                           .add(item.set_height(6)
                                    .set_interactive(true))))
             .add(make('row')
                    .add(make('cell')
                           .set_span(12)
                           .add(make({item_type: 'summation_table',
                                      options: {
                                        palette: options.palette
                                      },
                                      show_color: true,
                                      sortable: true,
                                      format: ',.3f',
                                      query: item.query}))))
  }
})
;/**
 * A transform which simply takes all presentations and arranges them in
 * a regular grid.
 */
ds.models.transform.SimpleGrid = function(data) {
  "use strict"

  var self = limivorous.observable()
                       .property('columns', { init: 1,
                                              update: function(target) {
                                                target.span = 12 / target.columns
                                              }
                                            })
                       .property('span', {init: 12})
                       .property('section_type', {init: 'fixed'})
                       .property('charts_only', {init: false})
                       .build()

  if (data) {
    self.display_name   = 'Simple Grid'
    self.name           = 'SimpleGrid'
    self.transform_type = 'dashboard'
    self.columns = data.columns || self.columns
    self.section_type = data.section_type || self.section_type
    if (data.charts_only) {
      self.charts_only = data.charts_only
    }
  }
  self.span = 12 / self.columns

  self.transform = function(item) {
    var items       = item.flatten()
    var section     = ds.models.make('section').set_layout(self.section_type)
    var current_row = ds.models.make('row')

    items.forEach( function(item) {
      if (   item.item_type === 'dashboard_definition'
          || item.item_type === 'cell'
          || item.item_type === 'row'
          || item.item_type === 'section'
          || (self.charts_only && !item.is_chart)) {
        return
      }
      var cell = ds.models.make('cell')
                   .set_span(self.span)
                   .add(item)

      if (current_row.add(cell).length == self.columns) {
        section.add(current_row)
        current_row = ds.models.make('row')
      }
    } )

    if (current_row.length > 0) {
      section.add(current_row)
    }

    return section
  }


  self.toJSON = function() {
    return {
      columns: self.columns,
      span: self.span,
      section_type: self.section_type,
      charts_only: self.charts_only,
      name: self.name
    }
  }

  return self
}
;/**
 * Transform a view of a single graph into viewing multiple of that
 * graph at the same temporal resolution but shifted by one or more
 * time periods.
 */
ds.transforms.register({
  name: 'TimeShift',
  display_name: 'Time Shift',
  transform_type: 'presentation',
  icon: 'fa fa-clock-o',

  transform: function(item) {
    var make  = ds.models.make
    var query = item.query
    var shifts = [
      { shift:  '-1h', title: 'One Hour Ago' },
      { shift: '-12h', title: '12 Hours Ago' },
      { shift:  '-1d', title: 'One Day Ago'  },
      { shift:  '-2d', title: 'Two Days Ago' },
      { shift:  '-1w', title: 'One Week Ago' }
    ]
    var make_row = function(query, item, style) {
      return make('row')
               .set_style(style)
               .add(make('cell')
                      .set_span(10)
                      .add(item))
               .add(make('cell')
                      .set_span(1)
                      .set_align('right')
                      .add(make('singlestat')
                             .set_query(query)
                             .set_transform('sum')
                             .set_format(',.2s')
                             .set_title('Total')))
               .add(make('cell')
                      .set_span(1)
                      .set_align('left')
                      .add(make('singlestat')
                             .set_query(query)
                             .set_transform('mean')
                             .set_format(',.2s')
                             .set_title('Average')))
    }


    var section = make('section')
                    .add(make({ item_type: 'heading',
                                level: 2, text: item.title
                                              ? 'Time shift - ' + item.title
                                              : 'Time shift' }))
                    .add(make('separator'))
                    .add(make_row(item.query, item, 'well'))
                    .add(make('separator'))

    for (var i = 0; i < shifts.length; i++) {
      var shift = shifts[i]
      var modified_query = query.shift(shift.shift)
      var modified_item  = make(item.toJSON())
                                    .set_item_id(undefined)
                                    .set_query(modified_query)
                                    .set_title(shift.title)
      section.add(make_row(modified_query, modified_item))
    }

    return section
  }
})
;/**
 * Transform a view of a single graph into viewing that graph over
 * multiple time spans. The item being transformed should be a single
 * presentation, not a composite (i.e. not a cell, row, section, or
 * dashboard).
 *
 * The time periods can be customized by passing an array of objects
 * with from/until/title properties
 *
 * Input => A single presentation
 *
 * Output => A section with a list of copies of the presentation with
 *           immediate query objects that override the time period
 */
ds.transforms.register({
  name:           'TimeSpans',
  display_name:   'View across time spans',
  transform_type: 'presentation',
  icon: '          fa fa-clock-o',

  transform: function(item) {
    var make  = ds.models.make
    var spans = [
      { from: '-1h', title: 'Past Hour' },
      { from: '-4h', title: 'Past 4 Hours' },
      { from: '-1d', title: 'Past Day' },
      { from: '-1w', title: 'Past Week' }
    ]
    var columns = 1
    var query   = item.query
    var colspan = 12 / self.columns
    var section = make('section')
                    .add(make('heading', {
                      level: 2, text: item.title
                                    ? 'Time Spans - ' + item.title
                                    : 'Time Spans' }))
                    .add(make('separator'))

    for (var i = 0; i < spans.length; i++) {
      var span = spans[i]
      var modified_query = ds.models.data.Query(query.toJSON())
                             .set_name(query.name + '/' + span.from + '/' + span.until)
                             .set_options(ds.extend(query.options || {},
                                                  {
                                                    from: span.from,
                                                    until: span.until
                                                  }))
      var modified_item = ds.models.factory(item.toJSON())
                            .set_item_id(undefined)
                            .set_query(modified_query)
                            .set_title(span.title)

      section.add(make('cell')
                  .set_span(colspan)
                  .add(modified_item))
    }

    return section
  }
})
;/**
 * Urban Airship template extensions for retrieving data from UA's
 * clusto extension API, and formatting graphite metrics specific to
 * UA's services.
 */

var ua = window.ua || {}

ua.clusto =
  (function() {

    var cache = {}
      , cache_data = {}
      , BASE_URL = 'http://clusto.prod.urbanairship.com:9998'
      , self = {}

    Object.defineProperty(self, 'cache', {get: function() { return cache }})

    /**
     * Return the cache key for a role.
     */
    cache.key = function(role) {
      return 'ua.clusto/' + role
    }

    /**
     * List all the keys in the immediate cache.
     */
    cache.keys = function() {
      return Object.keys(cache_data)
    }

    /**
     * Retrieve an item from the cache. Checks the in-memory cache
     * first, falling back to browser-local storage.
     */
    cache.getItem = function(role) {
      var key = cache.key(role)
      if (cache_data[key]) {
        return cache_data[key]
      } else if (localStorage[key]) {
        var data = localStorage[key]
        cache[key] = data
        return data
      }
      return null
    }

    /**
     * Store an item in the cache, in both the transient in-memory
     * cache and the browser-local storage.
     */
    cache.putItem = function(role, data) {
      var key = cache.key(role)
      cache_data[key] = data
      localStorage[key] = data
      return data
    }

    /**
     * Clear the cache. This defaults to clearing the browser-local
     * storage as well as the transient cache. Passing false as the
     * first argument will retain the local storage cache.
     */
    cache.clear = function(_) {
      var clearLocal = true
      if (arguments.length > 0)
        clearLocal = arguments[0]
      if (clearLocal) {
        var localKeys = Object.keys(localStorage)
        for (var i = 0; i < localKeys.length; i++) {
          var key = localKeys[i]
          if (key.search('ua.clusto') == 0) {
            localStorage.removeItem(key)
          }
        }
      }
      cache_data = {}
    }

    /**
     * Return an array of the hostnames for a given role or service
     * name. Returns null if the role or service cannot be found.
     */
    self.get_role = function(role) {
      var data = cache.getItem(role)
      if (typeof(data) === 'string') {
        cache.clear(cache.key(role))
        data = null
      }
      if (data) {
        return data
      } else {
        var url = BASE_URL + '/ua/search/prod,' + role + '.json'
        var result = ''
        $.ajax({
          url: url,
          type: 'GET',
          async: false,
          success: function(data) {
            if (!data || (data.length == 0)) {
              result = []
            } else {
              result = data
            }
          },
          error: function() {
            ds.manager.error('Unable to retrieve role ' + role + ' from clusto')
            return null
          }
        })
        return cache.putItem(role, result)
      }
    }

    self.block_helper = function(role, options) {
      var nodes = ua.clusto.get_role(role)
      /* If called as a block template */
      if (options && options.fn) {
        var out = ''
        for (var i = 0; i < nodes.length; i++) {
          out = out + options.fn(nodes[i])
        }
        return out
      } else { /* else just a single template */
        return '{' + nodes.join(',') + '}'
      }
    }

    return self
})()


/**
 * A helper which returns a graphite metric expression for a set of
 * hostnames, from a role or service literal (i.e. {{clusto
 * 'notices-worker'}}) when called as a single template tag.
 *
 * Can also be used as a block template, in which case the body is
 * called once for each hostname in the returned set.
 */
Handlebars.registerHelper('clusto', function(role, options) {
  return ua.clusto.block_helper(role, options)
})

/**
 * A helper which returns a graphite metric expression for a set of
 * hostnames for a role or service, with the name of the role or
 * service extracted from a URL query parameter on the current page
 * (i.e. {{clusto 'role'}} will retrieve the role name from the
 * ?role=XXX query string).
 */
Handlebars.registerHelper('clusto-query', function(query, options) {
  var role = URI(window.location).query(true)[query]
  return ua.clusto.block_helper(role, options)
})

/**
 * A helper which returns a metric prefix for an Urban Airship Java
 * service, with hostnames retrieved from clusto.
 *
 * Example:
 *   {{ua-service "triggers-state-ingress"}}.observationstreamconsumer.historical_triggers_processed.Count
 *
 * will expand to:
 *   servers.{host1,host2}.rash.triggers-state-ingress.observationstreamconsumer.historical_triggers_processed.Count
 */
Handlebars.registerHelper('ua-service', function(service_name) {
  return 'servers.{' + ua.clusto.get_role(service_name).join(',') + '}.rash.' + service_name
})
;/*
 * Logic for the dashboard-create.html template.
 */

$(document).ready(function() {
  $('#ds-dashboard-tags').tagsManager({
    tagsContainer: '.ds-tag-holder',
    tagClass: 'badge badge-primary'
  })
})

$(document).ready(function() {
    $('#ds-dashboard-create-form').bootstrapValidator()
})


$(document).on('click', '#ds-new-dashboard-create', function(e) {
  var title = $('#ds-dashboard-title')[0].value
  var category = $('#ds-dashboard-category')[0].value
  var summary = $('#ds-dashboard-summary')[0].value
  var description = $('#ds-dashboard-description')[0].value
  var tags = $('#ds-dashboard-tags').tagsManager('tags')

  if (!$('#ds-dashboard-create-form').data('bootstrapValidator').validate().isValid()) {
    return
  }

  var dashboard = ds.models.dashboard({
    title: title,
    category: category,
    summary: summary,
    description: description,
    tags: tags,
    definition: ds.models.factory('dashboard_definition')
  })
  ds.manager.create(dashboard, function(data) {
    window.location = data.view_href
  })
})

$(document).on('click', '#ds-new-dashboard-cancel', function(e) {
    window.history.back()
})
;/*
 * Logic for the dashboard-toolbar.html template.
 */

ds.app.add_mode_handler('edit', {
  enter: function() {
    $(".ds-dashboard-info-edit-panel").html(ds.templates.edit.dashboard_panel(ds.manager.current.dashboard))
    $('#ds-edit-info-button').addClass('active')

      $.fn.editable.defaults.mode = 'inline'

      /** Title */
      $("#ds-info-panel-edit-title").editable({
        unsavedclass: null,
        success: function(ignore, newValue) {
          ds.manager.current.dashboard.title = newValue
          ds.manager.update(ds.manager.current.dashboard)
        }
      })

      /** Category */
      $("#ds-info-panel-edit-category").editable({
        unsavedclass: null,
        success: function(ignore, newValue) {
          ds.manager.current.dashboard.category = newValue
          ds.manager.update(ds.manager.current.dashboard)
        }
      })

      $("#ds-info-panel-edit-summary").editable({
        unsavedclass: null,
        success: function(ignore, newValue) {
          ds.manager.current.dashboard.summary = newValue
          ds.manager.update(ds.manager.current.dashboard)
        }
      })

      $("#ds-info-panel-edit-description").editable({
        unsavedclass: null,
        value: ds.manager.current.dashboard.description || '',
        success: function(ignore, newValue) {
          ds.manager.current.dashboard.description = newValue
          ds.manager.update(ds.manager.current.dashboard)
        },
        display: function(value, response) {
          $(this).html(marked(value))
        }
      })

      var tags = ds.manager.current.dashboard.tags || []
      $("#ds-info-panel-edit-tags").tagsManager({
        hiddenTagListName: 'ds-info-panel-edit-taglist',
        tagClass: 'badge badge-primary',
        prefilled: tags.map(function(tag) {
                     return tag.name
                   })
      })

    /**
     * Handler for tag changes. This gets run each time a complete tag
     * is added or removed.
     */
    $('[name="ds-info-panel-edit-taglist"]').on('change', function(e) {
      var tags = $('#ds-info-panel-edit-tags').tagsManager('tags')
      ds.manager.current.dashboard.set_tags(tags)
      ds.manager.update(ds.manager.current.dashboard)
    })

  },
  exit: function() {
    $('#ds-edit-info-button').removeClass('active')
  }
})

$(document).ready(function() {

  /**
   * Handlers for the Info Edit panel.
   */
  $(document).on('click', '#ds-edit-info-button', function(e) {
    ds.app.toggle_mode(ds.app.Mode.EDIT)
  })

  $(document).on('click', '#ds-toggle-interactive-button', function(e) {
    if (ds.manager.toggle_interactive_charts()) {
      $('#ds-toggle-interactive-button').removeClass('active')
    } else {
      $('#ds-toggle-interactive-button').addClass('active')
    }
  })

  $(document).on('click', '#ds-enter-fullscreen-button', function(e) {
    ds.app.switch_to_mode(ds.app.Mode.DISPLAY)
  })

  $(document).on('click', '#ds-exit-display-mode-button', function(e) {
    ds.app.switch_to_mode(ds.app.Mode.STANDARD)
  })

  $(document).on('click', '#ds-delete-dashboard-button', function(e) {
    ds.manager.delete_current()
  })

  $(document).on('click', '#ds-remove-transform-button', function(e) {
    ds.manager.remove_transform()
  })

  $(document).on('click', '#ds-save-dashboard-button', function(e) {
    ds.manager.update_definition(ds.manager.current.dashboard, function() {
      ds.manager.success('Dashboard saved')
    })
  })


  $(document).on('click', '#ds-new-section-button', function(e) {
    var dash = ds.manager.current.dashboard
    dash.definition.add(ds.models.factory('section'))
    dash.update_index()
    ds.manager.update_item_view(dash.definition)
  })


  $(document).on('click', '#ds-view-dashboard-source-button', function(e) {
    var dashboard = ds.manager.current.dashboard
    $.get(dashboard.href + '?definition=true', function(data) {
      var contents = '<div class="container">' + ds.templates.edit.item_source({item:data.dashboards[0]}) + '</div>'
      $(ds.manager.current.element).html(contents)
    })
  })

})
;ds.app.add_mode_handler('display', {
  enter: function() {
    /* Make sure the fullscreen range indicator is correct */
    var range       = ds.context()
    var description = ds.manager.getRangeDescription(range.from);
    if ( description ) {
      $("a.ds-fullscreen-range-indicator").text(description);
    }
    /* Update the header to match the dashboard if it's fluid */
    var fluid = false
    ds.manager.current.dashboard.definition.visit(function(item) {
      if (item.layout === 'fluid')
        fluid = true
    })
    if (fluid) {
      $('.ds-header-container').removeClass('container')
      $('.ds-header-container').addClass('container-fluid')
    }
  },
  exit: function() {
    $('.ds-header-container').removeClass('container-fluid')
    $('.ds-header-container').addClass('container')
  }
})
;(function () {

  /* -----------------------------------------------------------------------------
     Queries
     ----------------------------------------------------------------------------- */

  /* Query delete buttons */
  $(document).on('click', 'button.ds-delete-query-button', function(e) {
    var $elt = $(this)
    var query_name   = $elt.attr('data-ds-query-name')
    var dashboard = ds.manager.current.dashboard
    if (!dashboard.definition.queries[query_name])
      return true
    var queries_in_use = dashboard.definition.get_queries()
    if (queries_in_use[query_name]) {
      bootbox.dialog({
        message: 'Query ' + query_name + ' is in use. Are you sure you want to delete it?',
        title: 'Confirm query delete',
        buttons: {
          cancel: {
            label: 'Cancel',
            className: 'btn-default'
          },
          confirm: {
            label: 'Delete',
            className: 'btn-danger',
            callback: function() {
              delete_query(dashboard, query_name)
            }
          }
        }
      })
    } else {
      delete_query(dashboard, query_name)
    }
    return true
  })

  /* Query duplicate buttons */
  $(document).on('click', 'button.ds-duplicate-query-button', function(e) {
    var $elt       = $(this)
    var query_name = $elt.attr('data-ds-query-name')
    var dashboard  = ds.manager.current.dashboard
    if (!dashboard.definition.queries[query_name])
      return true
    duplicate_query(dashboard, query_name)
    return true
  })


  ds.edit.edit_queries = function() {
    /* Query names */
    $('th.ds-query-name').each(function(index, e) {
      var element = $(e)
      var query_name = e.getAttribute('data-ds-query-name')
      element.editable({
        type: 'text',
        value: query_name,
        success: function(ignore, newValue) {
          rename_query(ds.manager.current.dashboard, query_name, newValue)
        }
      })
    });
    /* Query targets */
    $('td.ds-query-target').each(function(index, e) {
      var element = $(e)
      var query_name = e.getAttribute('data-ds-query-name')
      element.editable({
        type: 'textarea',
        inputclass: 'ds-source',
        value: element.text() || '',
        success: function(ignore, newValue) {
          var target = newValue.trim()
          var query = ds.manager.current.dashboard.definition.queries[query_name]
          query.targets = [target]
          query.render_templates(ds.context().variables)
          query.load()
        }
      })
    });
  }

  /**
   * Rename a query and update the UI to reflect the change.
   */
  function rename_query(dashboard, old_name, new_name) {
    var query = dashboard.definition.queries[old_name]
    var updated_items = dashboard.definition.rename_query(old_name, new_name)
    $('[data-ds-query-name="' + old_name + '"]').replaceWith(
      ds.templates.edit['dashboard-query-row'](query)
    )
    if (updated_items && (updated_items.length > 0)) {
      for (var i = 0; i < updated_items.length; i++) {
        ds.manager.update_item_view(updated_items[i])
      }
    }
    ds.edit.edit_queries()
    ds.app.refresh_mode()
  }

  /**
   * Delete a query and remove it from the queries list in the UI.
   */
  function delete_query(dashboard, query_name) {
    dashboard.definition.delete_query(query_name)
    $('tr[data-ds-query-name="' + query_name + '"]').remove()
    ds.edit.edit_queries()
    ds.app.refresh_mode()
  }

  /**
   * Add a new query to the dashboard and UI.
   */
  function add_query(dashboard, name, target) {
    var query = ds.models.data.Query({name: name, targets: target})
    dashboard.definition.add_query(query)
    $("#ds-query-panel table").append(ds.templates.edit['dashboard-query-row'](query))
    query.load()
    ds.edit.edit_queries()
    return query
  }

  function duplicate_query(dashboard, name) {
    var new_name = 'Copy of ' + name + ' ' + Object.keys(dashboard.definition.queries).length
    var source   = dashboard.definition.queries[name]
    return add_query(dashboard, new_name, source.targets.slice(0))
  }

  /**
   * Add a new query object to the dashboard and UI with an
   * auto-generated unique name, and an optional set of targets. If
   * targets are not supplied, a function generating random data will
   * be used as a placeholder.
   */
  function new_query(dashboard, targets) {
    var name = "query" + Object.keys(dashboard.definition.queries).length
    return add_query(dashboard, name, targets || 'absolute(randomWalkFunction("' + name + '"))')
  }

  /* -----------------------------------------------------------------------------
     Property Sheets
     ----------------------------------------------------------------------------- */

  /**
   * Helper functions to show & hide the action bar & property sheet
   * for dashboard items.
   */
  ds.edit.hide_details = function(item_id) {
    var details = $('#' + item_id + '-details')
    details.remove()
    $('.ds-edit-bar[data-ds-item-id="' + item_id + '"] .btn-group').hide()
    $('.ds-edit-bar[data-ds-item-id="' + item_id + '"] .badge').removeClass('ds-badge-highlight')
  }

  ds.edit.show_details = function(item_id) {
    // Show the edit button bar across the top of the item
    $('.ds-edit-bar[data-ds-item-id="' + item_id + '"] .btn-group').show()
    var item = ds.manager.current.dashboard.get_item(item_id)
    var item_type = ds.models[item.item_type]
    var bar_id = '.ds-edit-bar[data-ds-item-id="' + item_id + '"]'
    var details_id = '#' + item_id + '-details'
    if ($(details_id).length == 0) {

      // Render the item's property sheet
      var elt = $('.ds-edit-bar[data-ds-item-id="' + item_id + '"]')
      var details = ds.templates['ds-edit-bar-item-details']({item:item})
      elt.append(details)

      if (item_type.interactive_properties) {
        // Run the edit handlers for each property, which make them
        // editable and set up the callbacks for their updates
        for (var i = 0; i < item_type.interactive_properties.length; i++) {
          item_type.interactive_properties[i].edit(item)
        }
      }
    }
  }

  ds.edit.toggle_details = function(item_id) {
    var details = $('#' + item_id + '-details')
    if (details.is(':visible')) {
      ds.edit.hide_details(item_id)
      return false
    } else {
      ds.edit.show_details(item_id)
      return true
    }
  }

  ds.edit.details_visibility = function(item) {
    return $('#' + item.item_id + '-details').is(':visible')
  }

  var PROPERTY_SHEET_TIMEOUT = 3000

  /**
   * Event handlers to show & hide the action bar & property sheet for
   * dashboard items.
   */

  $(document).on('click', '.ds-edit-bar .badge', function(event) {
    var $elt = $(this)
    var id   = $elt.attr('data-ds-item-id')
    if (ds.edit.toggle_details(id)) {
      $elt.addClass('ds-badge-highlight')
    } else {
      $elt.removeClass('ds-badge-highlight')
    }
  })

  $(document).on('mouseenter', '.ds-edit-bar', function(event) {
    var timeout_id = $(this).attr('data-ds-timeout-id')
    if (timeout_id) {
      window.clearTimeout(timeout_id)
    }
  })

  $(document).on('mouseleave', '.ds-edit-bar', function(event) {
    var $elt = $(this)
    var id   = $elt.attr('data-ds-item-id')
    var timeout_id = window.setTimeout(function() {
                       ds.edit.hide_details(id)
                     }, PROPERTY_SHEET_TIMEOUT)
    $elt.attr('data-ds-timeout-id', timeout_id)
  })

  /**
   * Toggle mode-specific CSS rules for dashboard structural elements.
   */

  ds.app.add_mode_handler(ds.app.Mode.EDIT, {
    enter: function() {
      $('.ds-section, .ds-cell, .ds-row').addClass('ds-edit')
      ds.edit.edit_queries()
    },
    exit: function() {
      $('.ds-section, .ds-cell, .ds-row').removeClass('ds-edit')
    },
    refresh: function() {
      $('.ds-section, .ds-cell, .ds-row').addClass('ds-edit')
    }
  })

  /* -----------------------------------------------------------------------------
     Item Actions
     ----------------------------------------------------------------------------- */

  var duplicate_item_action = ds.action({
    name:    'duplicate',
    display: 'Duplicate Item',
    icon:    'fa fa-copy',
    handler: function(action, item) {
      var dashboard = ds.manager.current.dashboard
      var parent = dashboard.find_parent(item)
      var dup = ds.models.factory(item.toJSON()).set_item_id(null)
      dup.visit(function(child) {
        child.item_id = null
      })
      parent.add_after(item, dup)
      dashboard.update_index()
      ds.manager.update_item_view(parent)
    }
  })

  var delete_action = ds.action({
    name:    'delete',
    display: 'Delete item',
    icon:    'fa fa-trash-o',
    handler:  function(action, item) {
      var parent = ds.manager.current.dashboard.find_parent(item)
      if (!parent) {
        return
      }
      if (parent && parent.is_container && parent.remove(item)) {
        ds.manager.update_item_view(parent)
      }
    }
  })

  var move_back_action = ds.action({
    name:    'move-back',
    display: 'Move item back one place',
    icon:    'fa fa-caret-left',
    handler:  function(action, item) {
      var parent = ds.manager.current.dashboard.find_parent(item)
      if (parent.is_container && parent.move(item, -1)) {
        ds.manager.update_item_view(parent)
      }
    }
  })

  var move_forward_action = ds.action({
    name:    'move-forward',
    display: 'Move item forward one place',
    icon:    'fa fa-caret-right',
    handler:  function(action, item) {
      var parent = ds.manager.current.dashboard.find_parent(item)
      if (parent.is_container && parent.move(item, 1)) {
        ds.manager.update_item_view(parent)
      }
    }
  })

  var view_definition_action = ds.action({
    name:    'view-definition',
    display: 'View definition...',
    icon:    'fa fa-code',
    handler: function(action, item) {
      var contents = ds.templates.edit.item_source({item:item})
      bootbox.alert(contents)
    }
  })

  /* -----------------------------------------------------------------------------
     New from Graphite URL Action
     ----------------------------------------------------------------------------- */

  function new_chart_from_graphite_url(url_string) {
    var dash  = ds.manager.current.dashboard
    var url   = URI(url_string)
    var data  = url.search(true)
    var query = new_query(dash, data.target)
    var type  = 'standard_time_series'

    if (data.areaMode && data.areaMode === 'stacked') {
      type = 'stacked_area_chart'
    } else if (data.graphType && data.graphType === 'pie') {
      type = 'donut_chart'
    }

    var chart = ds.models.make(type)
                  .set_query(query.name)
                  .set_dashboard(dash)
                  .set_height(Math.min(8, Math.floor(((data.height || 400) / 80))))
                  .set_title(data.title)

    if (data.vtitle) {
      chart.options = chart.options || {}
      chart.options.yAxisLabel = data.vtitle
    }

    if (data.template) {
      chart.options = chart.options || {}
      chart.options.palette = data.template
    }

    return chart
  }

  ds.actions.register('new-item-chart', {
    name: 'new-chart-from-url',
    display: 'Add new chart from Graphite URL',
    icon: 'fa fa-image',
    class: 'new-item',
    category: 'new-item-chart',
    handler: function(action, container) {
      bootbox.prompt("Enter a Graphite chart URL", function(result) {
        if (result) {
          var item = new_chart_from_graphite_url(result)
          if (item) {
            container.add(item)
            ds.manager.current.dashboard.update_index()
            ds.manager.update_item_view(container)
          }
        }
      })
    }
  })

  /* -----------------------------------------------------------------------------
     New item handling
     ----------------------------------------------------------------------------- */

  var new_item_actions = [].concat(ds.actions.get('new-item-structural', 'row'),
                                   ds.action.divider,
                                   ds.actions.list('new-item-display').sort(function(a, b) {
                                     return a.icon.localeCompare(b.icon)
                                   }),
                                   ds.action.divider,
                                   ds.actions.list('new-item-data-table').sort(function(a, b) {
                                     return a.icon.localeCompare(b.icon)
                                   }),
                                   ds.action.divider,
                                   ds.actions.list('new-item-chart').sort(function(a, b) {
                                     return a.icon.localeCompare(b.icon)
                                   })
                                  )
  var other = ds.actions.list('new-item')
  if (other && other.length) {
    new_item_actions.concat(other.sort(function(a, b) {
                              return a.icon.localeCompare(b.icon)
                            }))
  }

  var new_item_action_for_cell = ds.action({
    name: 'new-item',
    category: 'new-item',
    class: 'ds-new-item',
    display: 'Add new dashboard item...',
    icon: 'fa fa-plus',
    actions: new_item_actions
  })

  var new_item_action_for_section = ds.action({
    name: 'new-item',
    category: 'new-item',
    class: 'ds-new-item',
    display: 'Add new dashboard item...',
    icon: 'fa fa-plus',
    actions: [].concat(ds.actions.get('new-item-structural', 'section'),
                       ds.actions.get('new-item-structural', 'row'),
                       ds.action.divider,
                       ds.actions.list('new-item-display').sort(function(a, b) {
                         return a.icon.localeCompare(b.icon)
                       })
                      )
  })

  $(document).on('click', 'li.new-item', function(event) {
    var elt = $(this)
    var category = elt.attr('data-ds-category')
    var name = elt.attr('data-ds-action')
    var item_id = elt.parent().parent().parent().parent()[0].getAttribute('data-ds-item-id')
    var item = ds.manager.current.dashboard.get_item(item_id)
    var action = ds.actions.get(category, name)

    action.handler(action, item)
    return false
  })

  /* -----------------------------------------------------------------------------
     Section actions
     ----------------------------------------------------------------------------- */

  ds.actions.register('edit-bar-section', [
    new_item_action_for_section,
    duplicate_item_action,
    ds.action.divider,
    move_back_action,
    move_forward_action,
    ds.action.divider,
    delete_action
  ])

  /* -----------------------------------------------------------------------------
     Row actions
     ----------------------------------------------------------------------------- */

  ds.actions.register('edit-bar-row', [
    ds.action({
      name: 'new-cell',
      display: 'Add new Cell',
      icon: 'fa fa-plus',
      handler: function(action, container) {
        container.add('cell')
      }
    }),
    duplicate_item_action,
    ds.action.divider,
    move_back_action,
    move_forward_action,
    ds.action.divider,
    delete_action
  ])

  /* -----------------------------------------------------------------------------
     Cell actions
     ----------------------------------------------------------------------------- */

  ds.actions.register('edit-bar-cell', [
    new_item_action_for_cell,
    duplicate_item_action,
    ds.action.divider,
    move_back_action,
    move_forward_action,
    ds.action({
      name:    'increase-span',
      display: 'Increase cell span by one',
      icon:    'fa fa-expand',
      handler:  function(action, item) {
        if (item.span) {
          item.span += 1
          ds.manager.update_item_view(item)
        }
      }
    }),
    ds.action({
      name:    'decrease-span',
      display: 'Decrease cell span by one',
      icon:    'fa fa-compress',
      handler:  function(action, item) {
        if (item.span) {
          item.span -= 1
          ds.manager.update_item_view(item)
        }
      }
    }),
    ds.action.divider,
    delete_action
  ])

  /* -----------------------------------------------------------------------------
     Item actions
     ----------------------------------------------------------------------------- */

  ds.actions.register('edit-bar-item', [
    duplicate_item_action,
    ds.action.divider,
    move_back_action,
    move_forward_action,
    view_definition_action,
    ds.action.divider,
    delete_action
  ])


  /* -----------------------------------------------------------------------------
     Edit Bar Handler
     ----------------------------------------------------------------------------- */

  $(document).on('click', '.ds-edit-bar button', function(event) {
    var element  = $(this)[0]
    var parent   = $(this).parent()[0]
    var item_id  = parent.getAttribute('data-ds-item-id')
    var name     = element.getAttribute('data-ds-action')
    var category = element.getAttribute('data-ds-category')
    var action   = ds.actions.get(category, name)
    var item     = ds.manager.current.dashboard.get_item(item_id)

    if (action) {
      action.handler(action, item)
    }
  })

  /* -----------------------------------------------------------------------------
     Dashboard Query Panel
     ----------------------------------------------------------------------------- */

  ds.actions.register('dashboard-queries', [
    ds.action({
      name:    'new-query',
      display: 'New Query...',
      icon:    'fa fa-plus',
      handler:  function(action, dashboard) {
        new_query(dashboard)
      }
    })
  ])

  $(document).on('click', '#ds-query-panel button', function(event) {
    var element  = $(this)[0]
    var name     = element.getAttribute('data-ds-action')
    var action   = ds.actions.get('dashboard-queries', name)

    if (action) {
      action.handler(action, ds.manager.current.dashboard)
    }
  })

})()
;/**
 * Actions that operate on dashboard in the listing page, and the
 * handler to invoke them from the dashboard-list.html template.
 */

ds.actions.register('dashboard-list-actions', [
  ds.action({
    name:    'open',
    display: 'Open',
    icon:    'fa fa-external-link',
    handler: function(action, context) {
      window.location = context.view_href
    }
  }),
  ds.action({
    name:    'edit',
    display: 'Edit...',
    icon:    'fa fa-edit',
    handler: function(action, context) {
      var url = URI(context.view_href).setQuery('mode', ds.app.Mode.EDIT).href()
      window.location = url
    }
  }),
  ds.action({
    name:    'duplicate',
    display: 'Duplicate...',
    icon:    'fa fa-copy',
    handler: function(action, context) {
      ds.manager.duplicate(context.href, function() {
        window.location.reload()
      })
    }
  }),
  ds.action({
    divider: true
  }),
  ds.action({
    name:    'delete',
    display: 'Delete...',
    icon:    'fa fa-trash-o',
    handler: function(action, context) {
      ds.manager.delete_with_confirmation(context.href, function() {
        $('tr[data-ds-href="' + context.href + '"]').remove()
        ds.manager.success('Succesfully deleted dashboard ' + context.href)
      })
    }
  })
])

$(document).on('click', 'ul.ds-dashboard-action-menu li', function(event) {
  var element = $(event.target).parent().parent()[0]
  var context = {
    href: element.getAttribute('data-ds-href'),
    view_href: element.getAttribute('data-ds-view-href')
  }
  var action = ds.actions.get('dashboard-list-actions', this.getAttribute('data-ds-action'))
  action.handler(action, context)
})
;/**
 * Handlers for the sort menu in the dashboard-list.html template.a
 */

$(document).on('click', 'ul.ds-dashboard-sort-menu li', function(e) {
  var column = e.target.getAttribute('data-ds-sort-col')
  var order = e.target.getAttribute('data-ds-sort-order')
  var url = URI(window.location)
  if (column) {
    url.setQuery('sort', column)
  }
  if (order) {
    url.setQuery('order', order)
  }
  window.location = url.href()
})


$(document).ready(function() {
  var params = URI(window.location).search(true)
  if (params.sort !== 'default' ) {
    $('ul.ds-dashboard-sort-menu li').removeClass('active')
    $('[data-ds-sort-col="' + params.sort + '"][data-ds-sort-order="' + (params.order || 'asc') + '"]')
                                    .parent()
                                    .addClass('active')
  }
})
;ds.actions.register('presentation-actions', [
  ds.action({
    name:    'open-in-graphite',
    display: 'Open in Graphite...',
    icon:    'fa fa-bar-chart-o',
    handler: function(action, item) {
      var composer_url = ds.charts.graphite.composer_url(item, { showTitle: true })
      window.open(composer_url)
    }
  }),
  ds.action({
    name:    'export-png',
    display: 'Export PNG...',
    icon:    'fa fa-file-image-o',
    handler: function(action, item) {
      var image_url = ds.charts.graphite.chart_url(item, { showTitle: true })
      window.open(image_url)
    }
  }),
  ds.action({
    name:    'export-svg',
    display: 'Export SVG...',
    icon:    'fa fa-file-code-o',
    handler: function(action, item) {
      var image_url = ds.charts.graphite.chart_url(item, { showTitle: true, format: 'svg' })
      window.open(image_url)
    }
  }),
  ds.action({
    name:    'export-csv',
    display: 'Export CSV...',
    icon:    'fa fa-file-excel-o',
    handler: function(action, item) {
      var image_url = ds.charts.graphite.chart_url(item, { showTitle: true, format: 'csv' })
      window.open(image_url)
    }
  })
])

$(document).on('click', 'ul.ds-action-menu li', function(event) {

  var presentation_id = $(this).parent().parent().parent().parent().parent()[0].id
  var item = ds.manager.current.dashboard.get_item(presentation_id)

  var action = ds.actions.get(this.getAttribute('data-ds-category'), this.getAttribute('data-ds-action'))
  action.handler(action, item)

  /* prevents resetting scroll position */
  return false
})
;  $(document).on('click', 'button.ds-refresh-button', function(e) {
    ds.manager.refresh()
  })

  $(document).on('click', 'ul.ds-refresh-menu li', function(e) {
    var target = $(e.target).parent()
    if (target.attr('data-ds-range')) {
      var range = target.attr('data-ds-range')
      ds.manager.set_time_range(range, null)
      $("ul.ds-refresh-menu li[data-ds-range]").removeClass('active')
      $("ul.ds-refresh-menu li[data-ds-range=" + range + "]").addClass('active')
    } else if (target.attr('data-ds-interval')) {
      var interval = target.attr('data-ds-interval')
      ds.manager.set_refresh_interval(interval)
      $("ul.ds-refresh-menu li[data-ds-interval]").removeClass('active')
      $("ul.ds-refresh-menu li[data-ds-interval=" + interval + "]").addClass('active')
    }
    return false
  })
;  $(document).on('click', 'ul.ds-rejigger-menu li a', function(e) {
    var target = $(e.target).parent()
    var cols = target.attr('data-ds-cols')
    var section_type = target.attr('data-ds-section-type')

    var layout = ds.models.transform.SimpleGrid({
      section_type: section_type,
      columns: cols
    })

    ds.manager.apply_transform(layout, ds.manager.current.dashboard, ds.app.current_mode != ds.app.Mode.EDIT)
    ds.app.refresh_mode()
    return false
  })
;$(document).on('dp.change', '#ds-range-picker-from, #ds-range-picker-until', function(e) {
  var from = $('#ds-range-picker-from').data("DateTimePicker").getDate()
  var until = $('#ds-range-picker-until').data("DateTimePicker").getDate()
  if (from && until) {
    var GRAPHITE_FORMAT = 'HH:mm_YYYYMMDD'
    // console.log('setting range to ' + from.calendar() + ' until ' + until.calendar())
    ds.manager.set_time_range(from.format(GRAPHITE_FORMAT), until.format(GRAPHITE_FORMAT))
  }
})

$(document).on('click', '.ds-recent-range-picker li, .ds-recent-range-picker a, .ds-custom-range-picker ul li, .ds-custom-range-picker ul li a', function(e) {
  var range = $(e.target).attr('data-ds-range')

  if (range === 'custom') {
    $('.ds-recent-range-picker').hide()
    $('.ds-custom-range-picker').show()

    var now = moment().utc().startOf('minute')
    now.minute( Math.round(now.minute() / 15) * 15) // quantize to 15-min interval

    var from_picker = $('#ds-range-picker-from').data("DateTimePicker")
    from_picker.setDate(now.clone().subtract('hours', 3))

    var until_picker = $('#ds-range-picker-until').data("DateTimePicker")
    until_picker.setDate(now)
    until_picker.setMaxDate(now.clone().endOf('day'))

  } else if (range === 'recent') {
    $('.ds-custom-range-picker').hide()
    $('.ds-recent-range-picker').show()

  } else {
    ds.manager.set_time_range(range, null)
  }
    return false
})
