-{"options":{"animatedZooms":true,"avoidMinZero":false,"axis":null,"axisLabelColor":"#666666","axisLabelFontSize":14,"axisLabelFormatter":null,"axisLabelWidth":50,"axisLineColor":"#AAAAAA","axisLineWidth":0.3,"axisTickSize":3,"colorSaturation":1,"colorValue":0.5,"colors":["#FF0097","#EF8158","#83BB32","#182B53","#4596FF","#553DC9","#AD3238","#00FFBC","#F1D950"],"connectSeparatedPoints":false,"customBars":false,"dateWindow":null,"delimiter":",","digitsAfterDecimal":2,"displayAnnotations":false,"drawPoints":true,"drawXAxis":true,"drawXGrid":true,"drawYAxis":true,"drawYGrid":true,"errorBars":false,"file":null,"fillAlpha":0.15,"fillGraph":false,"fractions":false,"gridLineColor":"#D8D8D8","gridLineWidth":0.3,"hideOverlayOnMouseOut":true,"highlightCircleSize":4,"includeZero":false,"interactionModel":null,"isZoomedIgnoreProgrammaticZoom":false,"labels":null,"labelsDiv":null,"labelsDivStyles":null,"labelsDivWidth":250,"labelsKMB":true,"labelsKMG2":false,"labelsSeparateLines":true,"labelsShowZeroValues":true,"legend":"always","logscale":true,"maxNumberWidth":30,"panEdgeFraction":null,"pixelsPerLabel":null,"pixelsPerXLabel":null,"pixelsPerYLabel":null,"pointSize":1,"rangeSelectorHeight":40,"rangeSelectorPlotFillColor":"#A7B1C4","rangeSelectorPlotStrokeColor":"#808FAB","rightGap":20,"rollPeriod":1,"showLabelsOnHighlight":true,"showRangeSelector":false,"showRoller":false,"sigFigs":null,"sigma":2,"stackedGraph":false,"stepPlot":false,"strokePattern":null,"strokeWidth":4,"ticker":null,"title":null,"titleHeight":18,"valueFormatter":null,"valueRange":null,"visibility":null,"wilsonInterval":true,"xAxisHeight":null,"xAxisLabelFormatter":null,"xAxisLabelWidth":55,"xLabelHeight":18,"xValueFormatter":null,"xValueParser":null,"xlabel":null,"y2label":null,"yAxisLabelFormatter":null,"yAxisLabelWidth":50,"yLabelWidth":18,"yValueFormatter":null,"ylabel":null},"slug":"ohai","name":"Ohai","desc":"","dataset":"/data/datasources/enwp_articles_created.csv","width":"auto","height":320,"chartType":"dygraphs","parents":["root"],"id":"ohai"}
\ No newline at end of file
+{
+ "id": "ohai",
+ "name": "ohai",
+ "slug": "ohai",
+ "desc": "",
+ "dataset": "/data/datasources/rc/rc_new_article_count.csv",
+ "width": "auto",
+ "height": 320,
+ "parents": [
+ "root"
+ ],
+ "chartType": "dygraphs",
+ "options": {
+ "animatedZooms": true,
+ "avoidMinZero": false,
+ "axis": null,
+ "axisLabelColor": "#666666",
+ "axisLabelFontSize": 14,
+ "axisLabelFormatter": null,
+ "axisLabelWidth": 50,
+ "axisLineColor": "#AAAAAA",
+ "axisLineWidth": 0.3,
+ "axisTickSize": 3,
+ "colorSaturation": 1,
+ "colorValue": 0.5,
+ "colors": [
+ "#FF0097",
+ "#EF8158",
+ "#83BB32",
+ "#182B53",
+ "#4596FF",
+ "#553DC9",
+ "#AD3238",
+ "#00FFBC",
+ "#F1D950"
+ ],
+ "connectSeparatedPoints": false,
+ "customBars": false,
+ "dateWindow": null,
+ "delimiter": ",",
+ "digitsAfterDecimal": 2,
+ "displayAnnotations": false,
+ "drawPoints": true,
+ "drawXAxis": true,
+ "drawXGrid": true,
+ "drawYAxis": true,
+ "drawYGrid": true,
+ "errorBars": false,
+ "file": null,
+ "fillAlpha": 0.15,
+ "fillGraph": false,
+ "fractions": false,
+ "gridLineColor": "#D8D8D8",
+ "gridLineWidth": 0.3,
+ "hideOverlayOnMouseOut": true,
+ "highlightCircleSize": 4,
+ "includeZero": false,
+ "interactionModel": null,
+ "isZoomedIgnoreProgrammaticZoom": false,
+ "labels": null,
+ "labelsDiv": null,
+ "labelsDivStyles": null,
+ "labelsDivWidth": 250,
+ "labelsKMB": true,
+ "labelsKMG2": false,
+ "labelsSeparateLines": true,
+ "labelsShowZeroValues": true,
+ "legend": "always",
+ "logscale": true,
+ "maxNumberWidth": 30,
+ "panEdgeFraction": null,
+ "pixelsPerLabel": null,
+ "pixelsPerXLabel": null,
+ "pixelsPerYLabel": null,
+ "pointSize": 1,
+ "rangeSelectorHeight": 40,
+ "rangeSelectorPlotFillColor": "#A7B1C4",
+ "rangeSelectorPlotStrokeColor": "#808FAB",
+ "rightGap": 20,
+ "rollPeriod": 1,
+ "showLabelsOnHighlight": true,
+ "showRangeSelector": false,
+ "showRoller": false,
+ "sigFigs": null,
+ "sigma": 2,
+ "stackedGraph": false,
+ "stepPlot": false,
+ "strokePattern": null,
+ "strokeWidth": 4,
+ "ticker": null,
+ "title": null,
+ "titleHeight": 18,
+ "valueFormatter": null,
+ "valueRange": null,
+ "visibility": null,
+ "wilsonInterval": true,
+ "xAxisHeight": null,
+ "xAxisLabelFormatter": null,
+ "xAxisLabelWidth": 55,
+ "xLabelHeight": 18,
+ "xValueFormatter": null,
+ "xValueParser": null,
+ "xlabel": null,
+ "y2label": null,
+ "yAxisLabelFormatter": null,
+ "yAxisLabelWidth": 50,
+ "yLabelWidth": 18,
+ "yValueFormatter": null,
+ "ylabel": null
+ }
+}
screen -dr kraken
./lib/server/server.co | tee -a logs/kraken.log
+
+## Deployer Config
+
+ set -xg KRAKEN_DEPLOY_HOST dsc@reportcard2.pmtpa.wmflabs
+ set -xg KRAKEN_DEPLOY_PATH /srv/reportcard/kraken-ui/
+ set -xg KRAKEN_DEV_HOST master.reportcard.wmflabs.org
-/**
- * @class Base model, extending Backbone.Model, used by scaffold and others.
- * @extends Backbone.Model
- */
-BaseModel = exports.BaseModel = Backbone.Model.extend do # {{{
- ctorName : 'BaseModel'
+BaseBackboneMixin = exports.BaseBackboneMixin =
- # A list of method-names to bind on initialize; set this on a subclass to override.
+ initialize: ->
+ @__apply_bind__()
+
+
+ ### Auto-Bound methods
+
+ /**
+ * A list of method-names to bind on `initialize`; set this on a subclass to override.
+ * @type Array<String>
+ */
__bind__ : []
+ /**
+ * Applies the contents of `__bind__`.
+ */
+ __apply_bind__: ->
+ names = _ @pluckSuperAndSelf '__bind__' .chain().flatten().compact().unique().value()
+ _.bindAll this, ...names if names.length
+
+
+
+ ### Synchronization
+
+ /**
+ * Count of outstanding tasks.
+ * @type Number
+ */
+ waitingOn : 0
+
+
+ /**
+ * Increment the waiting task counter.
+ * @returns {this}
+ */
+ wait: ->
+ count = @waitingOn
+ @waitingOn += 1
+ console.log "#this.wait! #count --> #{@waitingOn}"
+ @trigger('start-waiting', this) if count is 0 and @waitingOn > 0
+ this
+
+ /**
+ * Decrement the waiting task counter.
+ * @returns {this}
+ */
+ unwait: ->
+ count = @waitingOn
+ @waitingOn -= 1
+ console.warn "#this.unwait! #{@waitingOn} < 0" if @waitingOn < 0
+ console.log "#this.unwait! #count --> #{@waitingOn}"
+ @trigger('stop-waiting', this) if @waitingOn is 0 and count > 0
+ this
+
+ /**
+ * @param {Function} fn Function to wrap.
+ * @returns {Function} A function wrapping the passed function with a call
+ * to `unwait()`, then delegating with current context and arguments.
+ */
+ unwaitAnd: (fn) ->
+ self = this
+ ->
+ console.log "#self.unwaitAnd( function #{fn.name or fn.displayName}() )"
+ self.unwait()
+ fn ...
+
+
+mixinBase = exports.mixinBase = (body) ->
+ _.clone(BaseBackboneMixin) import body
+
+
+/**
+ * @class Base model, extending Backbone.Model, used by scaffold and others.
+ * @extends Backbone.Model
+ */
+BaseModel = exports.BaseModel = Backbone.Model.extend mixinBase do # {{{
constructor : function BaseModel
@__class__ = @constructor
@__superclass__ = @..__super__.constructor
+ @waitingOn = 0
Backbone.Model ...
@trigger 'create', this
- initialize: ->
- _.bindAll this, ...@__bind__ if @__bind__.length
+
### Accessors
#
+
+
+
### Serialization
serialize: (v) ->
toURL: ->
"?#{@toKV ...}"
- toString: -> "#{@ctorName}(id=#{@id})"
+ toString: -> "#{@..name or @..displayName}(cid=#{@cid}, id=#{@id})"
# Class Methods
BaseModel import do
-
/**
* Factory method which constructs an instance of this model from a string of KV-pairs.
* This is a class method inherited by models which extend {BaseModel}.
* @class Base collection, extending Backbone.Collection, used by scaffold and others.
* @extends Backbone.Collection
*/
-BaseList = exports.BaseList = Backbone.Collection.extend do # {{{
- ctorName : 'BaseList'
-
- # A list of method-names to bind on initialize; set this on a subclass to override.
- __bind__ : []
-
+BaseList = exports.BaseList = Backbone.Collection.extend mixinBase do # {{{
constructor : function BaseList
@__class__ = @constructor
@__superclass__ = @..__super__.constructor
+ @waitingOn = 0
Backbone.Collection ...
@trigger 'create', this
- initialize : ->
- _.bindAll this, ...@__bind__ if @__bind__.length
+ ### Serialization
toKVPairs: ->
_.collapseObject @toJSON()
toURL: (item_delim='&', kv_delim='=') ->
"?#{@toKV ...}"
- toString: -> "#{@ctorName}(length=#{@length})"
+ toString: -> "#{@..name or @..displayName}(length=#{@length})"
# }}}
* @class Base view, extending Backbone.View, used by scaffold and others.
* @extends Backbone.View
*/
-BaseView = exports.BaseView = Backbone.View.extend do # {{{
- ctorName : 'BaseView'
-
- /**
- * A list of method-names to bind on initialize; set this on a subclass to override.
- * @type Array<String>
- */
- __bind__ : []
+BaseView = exports.BaseView = Backbone.View.extend mixinBase do # {{{
+ tagName : 'section'
/**
- * @type Array<BaseView>
+ * Array of [view, selector]-pairs.
+ * @type Array<[BaseView, String]>
*/
subviews : []
constructor : function BaseView
@__class__ = @constructor
@__superclass__ = @..__super__.constructor
+ @waitingOn = 0
@subviews = []
Backbone.View ...
@trigger 'create', this
initialize: ->
- _.bindAll this, ...@__bind__ if @__bind__.length
+ @__apply_bind__()
@setModel @model
@build()
@model.on 'destroy', @remove, this
@model
+
+
+ ### Subviews
+
+ addSubview: (selector, view) ->
+ [view, selector] = [selector, null] unless view
+ @subviews.push [view, selector]
+ view
+
+ removeSubview: (view) ->
+ for [v, sel], idx of @subviews
+ if v is view
+ @subviews.splice(idx, 1)
+ return [v, sel]
+ null
+
+ hasSubview: (view) ->
+ _.any @subviews, ([v]) -> v is view
+
+ attachSubviews: ->
+ for [view, selector] of @subviews
+ return unless view
+ view.undelegateEvents()
+ return unless el = view.render()?.el
+ if selector
+ @$el.find selector .append el
+ else
+ @$el.append el
+ view.delegateEvents()
+ this
+
+
+ ### Rendering Chain
+
toTemplateLocals: ->
json = {value:v} = @model.toJSON()
if _.isArray(v) or _.isObject(v)
json.value = JSON.stringify v
- { $, _, op, @model, view:this } import json
+ json
$template: (locals={}) ->
- $ @template @toTemplateLocals() import locals
+ $ @template do
+ { $, _, op, @model, view:this } import @toTemplateLocals() import locals
build: ->
return this unless @template
-
outer = @$template()
@$el.html outer.html()
.attr do
id : outer.attr 'id'
class : outer.attr('class')
-
+ @attachSubviews()
this
render: ->
@trigger 'render', this
this
+ renderSubviews: ->
+ _.invoke _.pluck(@subviews, 0), 'render'
+ this
+
+
+
+
+ ### UI Utilities
+
hide : -> @$el.hide(); this
show : -> @$el.show(); this
remove : -> @$el.remove(); this
# @$el.appendTo parent if parent?.length
# this
- toString : -> "#{@ctorName}(model=#{@model})"
+ toString : -> "#{@..name or @..displayName}(model=#{@model})"
# Proxy model methods
* @class Field with chart-option-specific handling for validation, parsing, tags, etc.
*/
ChartOption = exports.ChartOption = Field.extend do # {{{
- ctorName : 'ChartOption'
IGNORED_TAGS : <[ callback deprecated debugging ]>
* @class List of ChartOption fields.
*/
ChartOptionList = exports.ChartOptionList = FieldList.extend do # {{{
- ctorName : 'ChartOptionList'
model : ChartOption
*/
ChartOptionView = exports.ChartOptionView = FieldView.extend do # {{{
# __bind__ : <[ onClick ]>
- ctorName : 'ChartOptionView'
tagName : 'div'
className : 'field option'
template : require 'kraken/template/chart-option'
@$el.addClass 'collapsed' if @isCollapsed
this
+
+ /**
+ * Sets the state of `isCollapsed` and updates the UI. If the state changed,
+ * a `'change:collapse`` event will be fired.`
+ *
+ * @param {Boolean} [makeCollapsed=true] If true, set state to collapsed.
+ * @returns {Boolean} Whether the state changed.
+ */
+ collapse: (state=true) ->
+ state = !! state
+ @isCollapsed = @$el.hasClass 'collapsed'
+
+ return this if state is @isCollapsed
+ if state
+ @$el.addClass 'collapsed'
+ else
+ @$el.removeClass 'collapsed'
+ @isCollapsed = state
+ @trigger 'change:collapse', this, @isCollapsed
+ true
+
+ /**
+ * Toggles the collapsed state, updating the UI and firing a `'change:collapse'` event.
+ * @returns {this}
+ */
+ toggleCollapsed: ->
+ @collapse not @$el.hasClass 'collapsed'
+ this
+
onClick: (evt) ->
target = $ evt.target
- # console.log "#this.onClick()", target
@toggleCollapsed() if @$el.hasClass('collapsed') and not target.hasClass('close')
- toggleCollapsed: ->
- starting = @$el.hasClass 'collapsed' #@isCollapsed
- @$el.toggleClass 'collapsed'
- @isCollapsed = not starting
- # console.log "#this.toggleCollapsed!", starting, '->', @isCollapsed
- @trigger 'change:collapse', this, @isCollapsed
- this
# }}}
* @class View for configuring a chart type.
*/
ChartOptionScaffold = exports.ChartOptionScaffold = Scaffold.extend do # {{{
- ctorName : 'ChartOptionScaffold'
+ __bind__ : <[ collapseAll expandAll ]>
tagName : 'form'
className : 'options scaffold'
template : require 'kraken/template/chart-scaffold'
+
collectionType : ChartOptionList
subviewType : ChartOptionView
fields : '.fields'
+ events:
+ 'click .options-filter-button' : 'onFilterOptions'
+ 'click .collapse-all-options-button' : 'collapseAll'
+ 'click .expand-all-options-button' : 'expandAll'
+
# GraphView will set this
- ready : false
+ ready : false
+
constructor: function ChartOptionScaffold
@render = _.debounce @render.bind(this), DEBOUNCE_RENDER
Scaffold::initialize ...
+
render: ->
- # console.log "#this.render() -> .isotope()"
+ console.log "#this.render(ready=#{@ready}) -> .isotope()"
# Scaffold::render ...
return this unless @ready
container = if @fields then @$el.find @fields else @$el
.find '.field.option' .addClass 'isotope-item'
container.isotope do
# itemPositionDataEnabled : true
- itemSelector : '.field.option'
- layoutMode : 'masonry'
- masonry : columnWidth : 10
- getSortData :
+ itemSelector : '.field.option'
+ layoutMode : 'masonry'
+ masonry : { columnWidth:10 }
+ filter : @getOptionsFilter()
+ sortBy : 'category'
+ getSortData :
category: ($el) ->
$el.data 'model' .getCategory()
- sortBy: 'category'
+ this
+
+ getOptionsFilter: ->
+ data = @$el.find '.options-filter-button.active' .toArray().map -> $ it .data()
+ sel = data.reduce do
+ (sel, d) ->
+ sel += that if d.filter
+ sel
+ ''
+ sel
+
+ collapseAll: ->
+ _.invoke @_subviews, 'collapse', true
+ # @renderSubviews()
+ false
+
+ expandAll: ->
+ _.invoke @_subviews, 'collapse', false
+ # @renderSubviews()
+ false
+
/**
* Add a ChartOption to this scaffold, rerendering the isotope
*/
addOne: (field) ->
view = Scaffold::addOne ...
- view.on 'change:collapse render', @render
+ view.on 'change:collapse render', @render, this
view
toKV: ->
@collection.toKV ...
+
+ onFilterOptions: (evt) ->
+ evt.preventDefault()
+ _.defer @render
+
# }}}
* @class
*/
DataView = exports.DataView = BaseView.extend do # {{{
- ctorName : 'DataView'
+ __bind__ : <[ onReady ]>
tagName : 'section'
className : 'data-ui'
template : require 'kraken/template/data'
- data : {}
+ datasources : {}
initialize: ->
@graph_id = @options.graph_id
BaseView::initialize ...
-
+ @on 'ready', @onReady
@load()
-
- # @subviews.push @dataset_view = new DataSetView {@model, @graph_id}
- # @$el.append @dataset_view.render().el
- # @dataset_view.on 'edit-metric', @editMetric, this
- #
- # @subviews.push @metric_edit_view = new MetricEditView {dataset:@model, @graph_id}
- # @$el.append @metric_edit_view.render().hide().el
-
-
- toTemplateLocals: ->
- attrs = _.clone @model.attributes
- { $, _, op, @model, view:this, @data, @graph_id } import attrs
load: ->
- $.getJSON '/datasources/all', (@data) ~>
+ $.getJSON '/datasources/all', (@datasources) ~>
+ @canonicalizeDataSources @datasources
@ready = true
@render()
@trigger 'ready', this
+ /**
+ * Transform the `columns` field to ensure an Array of {label, type} objects.
+ */
+ canonicalizeDataSources: (datasources) ->
+ _.each datasources, (ds) ->
+ ds.shortName or= ds.name
+ ds.title or=