Milimetric has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/316834

Change subject: Clean up folder hierarchy
......................................................................

Clean up folder hierarchy

* Move all utilities to lib
* Make converters and apis work the same
* Move timeseries-data into its own models folder (+ more models later)
* Organize components into logical categories
* remove unused components
* update require config and knockout config

Bug: T147884
Change-Id: Id56c4d1dd0bf7787a493d39759ac44878d4610b1
---
M src/app/apis/annotations-api.js
M src/app/apis/api-finder.js
M src/app/apis/aqs-api.js
M src/app/apis/datasets-api.js
M src/app/apis/wikimetrics.js
A src/app/converters/converter-finder.js
R src/app/converters/hierarchy/hierarchy-data.js
R src/app/converters/timeseries/annotations-data.js
R src/app/converters/timeseries/aqs-api-response.js
R src/app/converters/timeseries/separated-values.js
R src/app/converters/timeseries/wikimetrics-timeseries.js
D src/app/data-converters/factory.js
D src/app/data-converters/simple-separated-values.js
R src/app/models/timeseries-data.js
M src/app/require.config.js
M src/app/startup.js
R src/components/controls/breakdown-toggle/breakdown-toggle.html
R src/components/controls/breakdown-toggle/breakdown-toggle.js
R src/components/controls/button-group/button-group.html
R src/components/controls/button-group/button-group.js
R src/components/controls/dropdown/dropdown.html
R src/components/controls/dropdown/dropdown.js
R src/components/layouts/compare/compare.html
R src/components/layouts/compare/compare.js
R src/components/layouts/metrics-by-project/metrics-by-project.html
R src/components/layouts/metrics-by-project/metrics-by-project.js
R src/components/layouts/out-of-service/out-of-service.html
A src/components/layouts/out-of-service/out-of-service.js
R src/components/layouts/tabs/tabs.html
R src/components/layouts/tabs/tabs.js
D src/components/out-of-service/out-of-service.js
R src/components/selectors/metric/bindings.js
R src/components/selectors/metric/metric.html
R src/components/selectors/metric/metric.js
R src/components/selectors/project/bindings.js
R src/components/selectors/project/project.html
R src/components/selectors/project/project.js
R src/components/visualizers/a-b-compare/a-b-compare.html
R src/components/visualizers/a-b-compare/a-b-compare.js
R src/components/visualizers/a-b-compare/compare-stacked-bars.html
R src/components/visualizers/a-b-compare/compare-stacked-bars.js
R src/components/visualizers/a-b-compare/compare-sunburst.html
R src/components/visualizers/a-b-compare/compare-sunburst.js
R src/components/visualizers/a-b-compare/compare-timeseries.html
R src/components/visualizers/a-b-compare/compare-timeseries.js
R src/components/visualizers/annotation-list/annotation-list.html
R src/components/visualizers/annotation-list/annotation-list.js
R src/components/visualizers/annotation-list/bindings.js
D src/components/visualizers/nvd3-timeseries/bindings.js
D src/components/visualizers/nvd3-timeseries/nvd3-timeseries.html
D src/components/visualizers/nvd3-timeseries/nvd3-timeseries.js
D src/components/visualizers/rickshaw-timeseries/bindings.js
D src/components/visualizers/rickshaw-timeseries/rickshaw-extensions.js
D src/components/visualizers/rickshaw-timeseries/rickshaw-timeseries.html
D src/components/visualizers/rickshaw-timeseries/rickshaw-timeseries.js
M src/components/visualizers/visualizer/visualizer.js
M src/components/visualizers/wikimetrics/wikimetrics.js
M src/layouts/compare/index.js
M src/layouts/metrics-by-project/index.js
M src/layouts/tabs/index.js
R src/lib/knockout-utils/async-observables.js
R src/lib/knockout-utils/bindings/datepicker.js
A src/lib/knockout-utils/bindings/dropdown.js
A src/lib/knockout-utils/bindings/popup.js
R src/lib/knockout-utils/bindings/table.js
R src/lib/knockout-utils/bindings/toggle.js
R src/lib/knockout-utils/viewmodels/copy-params.js
R src/lib/knockout-utils/viewmodels/single-select.js
R src/lib/utils/arrays.js
R src/lib/utils/colors.js
R src/lib/utils/datetime.js
R src/lib/utils/elements.js
R src/lib/utils/numbers.js
R src/lib/utils/strings.js
M test/SpecRunner.browser.js
M test/app/data-converters.js
M test/app/timeseries-data.js
M test/components/annotation-list.js
M test/components/breakdown-toggle.js
M test/components/metric-selector.js
M test/components/project-selector.js
M test/components/tabs-layout.js
M test/components/vega-timeseries.js
M test/components/wikimetrics-layout.js
M test/components/wikimetrics-visualizer.js
85 files changed, 292 insertions(+), 571 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/analytics/dashiki 
refs/changes/34/316834/1

diff --git a/src/app/apis/annotations-api.js b/src/app/apis/annotations-api.js
index 481869b..ec6a523 100644
--- a/src/app/apis/annotations-api.js
+++ b/src/app/apis/annotations-api.js
@@ -9,7 +9,7 @@
         moment = require('moment'),
         logger = require('logger'),
         converter = require('converters.annotations'),
-        TimeseriesData = require('converters.timeseries');
+        TimeseriesData = require('models.timeseries');
 
     function AnnotationsApi () {}
 
diff --git a/src/app/apis/api-finder.js b/src/app/apis/api-finder.js
index 90dec31..7ff360d 100644
--- a/src/app/apis/api-finder.js
+++ b/src/app/apis/api-finder.js
@@ -1,3 +1,4 @@
+'use strict';
 /**
  * Defines a function that can find an api from a metric object
  *
@@ -7,19 +8,22 @@
  *                data for that metric.  metric.api defaults to wikimetrics
  */
 define(function (require) {
-    'use strict';
 
     var wikimetricsApi = require('apis.wikimetrics'),
         aqsApi = require('apis.aqs'),
-        datasetsApi = require('apis.datasets');
+        datasetsApi = require('apis.datasets'),
+        annotationsApi = require('apis.annotations'),
+        configApi = require('apis.config');
 
     /* matches metric.api to an api instance */
     return function (metric) {
         var mapping = {
-            wikimetrics: wikimetricsApi,
+            annotations: annotationsApi,
             aqsApi: aqsApi,
-            pageviewApi: aqsApi,
+            config: configApi,
             datasets: datasetsApi,
+            pageviewApi: aqsApi,
+            wikimetrics: wikimetricsApi,
         };
 
         return mapping[metric.api || 'wikimetrics'];
diff --git a/src/app/apis/aqs-api.js b/src/app/apis/aqs-api.js
index 35499b4..5c8db19 100644
--- a/src/app/apis/aqs-api.js
+++ b/src/app/apis/aqs-api.js
@@ -6,12 +6,12 @@
 define(function (require) {
 
     var siteConfig = require('config'),
-        dataConverterFactory = require('dataConverterFactory'),
+        converterFinder = require('finders.converter'),
         logger = require('logger'),
         moment = require('moment'),
         pageviews = require('pageviews'),
         _ = require('lodash'),
-        TimeseriesData = require('converters.timeseries'),
+        TimeseriesData = require('models.timeseries'),
         sitematrix = require('sitematrix');
 
     require('uri/URITemplate');
@@ -21,7 +21,7 @@
         // Note that dataConverters are functions that will need
         // to be executed in the context of the metric.
         this.dataConverters = _.mapValues(config.aqsApi, function (apiConfig) {
-            return dataConverterFactory.getDataConverter(
+            return converterFinder(
                 'aqs-api-response',
                 apiConfig.valueField
             );
diff --git a/src/app/apis/datasets-api.js b/src/app/apis/datasets-api.js
index be328f0..306725a 100644
--- a/src/app/apis/datasets-api.js
+++ b/src/app/apis/datasets-api.js
@@ -6,8 +6,8 @@
     'use strict';
 
     var siteConfig = require('config'),
-        converters = require('dataConverterFactory'),
-        TimeseriesData = require('converters.timeseries'),
+        converterFinder = require('finders.converter'),
+        TimeseriesData = require('models.timeseries'),
         uri = require('uri/URI'),
         logger = require('logger');
 
@@ -30,7 +30,7 @@
     DatasetsApi.prototype.getData = function (metricInfo, project) {
         var deferred = new $.Deferred(),
             address = '',
-            converter = converters.getDataConverter(this.config.format),
+            converter = converterFinder(this.config.format),
             handleFailure = function (error) {
                 // resolve as done with empty results and log the error
                 // to avoid crashing the ui when a metric has problems
diff --git a/src/app/apis/wikimetrics.js b/src/app/apis/wikimetrics.js
index e65919b..f823f96 100644
--- a/src/app/apis/wikimetrics.js
+++ b/src/app/apis/wikimetrics.js
@@ -6,9 +6,9 @@
     'use strict';
 
     var siteConfig = require('config'),
-        dataConverterFactory = require('dataConverterFactory'),
+        converterFinder = require('finders.converter'),
         uri = require('uri/URI'),
-        TimeseriesData = require('converters.timeseries');
+        TimeseriesData = require('models.timeseries');
 
     require('uri/URITemplate');
     require('logger');
@@ -18,7 +18,7 @@
         this.config = config;
         // note that dataCoverter is a function that will need to be executed
         // in the context of the metric
-        this.dataConverter = 
dataConverterFactory.getDataConverter(config.wikimetricsApi.format);
+        this.dataConverter = converterFinder(config.wikimetricsApi.format);
     }
 
     function ProjectOption(data, prettyNames) {
diff --git a/src/app/converters/converter-finder.js 
b/src/app/converters/converter-finder.js
new file mode 100644
index 0000000..1e0adbc
--- /dev/null
+++ b/src/app/converters/converter-finder.js
@@ -0,0 +1,47 @@
+'use strict';
+/**
+ * Find what converter is needed based on format
+ */
+define(function (require) {
+
+    var separatedValues = require('converters.separated-values'),
+        wikimetricsTimeseries = require('converters.wikimetrics-timeseries'),
+        buildHierarchy = require('converters.hierarchy-data'),
+        aqsApiResponse = require('converters.aqs-api-response'),
+        annotationsData = require('converters.annotations');
+
+    /**
+     * Based on format determine an appropriate converter and return it
+     * Note that converters are returned as functions that execute taking 
rawdata
+     * as an argument.
+     *
+     * Parameters
+     *  format: enum - (tsv, cvs, json)
+     *  valueField: string - The field in the results that contains the values.
+     *                       For now only used in the AQS.
+     */
+    return function (format, valueField) {
+
+        // note that the data converter modules return a function
+
+        switch (format) {
+            case 'tsv':
+                return separatedValues('\t');
+
+            case 'csv':
+                return separatedValues(',');
+
+            case 'json':
+                return wikimetricsTimeseries();
+
+            case 'aqs-api-response':
+                return aqsApiResponse(valueField);
+
+            case 'hierarchy':
+                return buildHierarchy;
+
+            case 'annotations':
+                return annotationsData();
+        }
+    };
+});
diff --git a/src/app/data-converters/hierarchy-data.js 
b/src/app/converters/hierarchy/hierarchy-data.js
similarity index 100%
rename from src/app/data-converters/hierarchy-data.js
rename to src/app/converters/hierarchy/hierarchy-data.js
diff --git a/src/app/data-converters/annotations-data.js 
b/src/app/converters/timeseries/annotations-data.js
similarity index 97%
rename from src/app/data-converters/annotations-data.js
rename to src/app/converters/timeseries/annotations-data.js
index d30fedb..e0b13c8 100644
--- a/src/app/data-converters/annotations-data.js
+++ b/src/app/converters/timeseries/annotations-data.js
@@ -18,7 +18,7 @@
     'use strict';
 
     var _ = require('lodash'),
-        TimeseriesData = require('converters.timeseries');
+        TimeseriesData = require('models.timeseries');
 
     return function () {
 
diff --git a/src/app/data-converters/aqs-api-response.js 
b/src/app/converters/timeseries/aqs-api-response.js
similarity index 97%
rename from src/app/data-converters/aqs-api-response.js
rename to src/app/converters/timeseries/aqs-api-response.js
index d67407e..bc1f9ba 100644
--- a/src/app/data-converters/aqs-api-response.js
+++ b/src/app/converters/timeseries/aqs-api-response.js
@@ -17,7 +17,7 @@
 
     var _ = require('lodash'),
         moment = require('moment'),
-        TimeseriesData = require('converters.timeseries');
+        TimeseriesData = require('models.timeseries');
 
     /**
      * Parameters
diff --git a/src/app/data-converters/separated-values.js 
b/src/app/converters/timeseries/separated-values.js
similarity index 98%
rename from src/app/data-converters/separated-values.js
rename to src/app/converters/timeseries/separated-values.js
index 23bf8ed..722d055 100644
--- a/src/app/data-converters/separated-values.js
+++ b/src/app/converters/timeseries/separated-values.js
@@ -6,7 +6,7 @@
     'use strict';
 
     var _ = require('lodash'),
-        TimeseriesData = require('converters.timeseries');
+        TimeseriesData = require('models.timeseries');
 
     /**
      * Parses a CSV, TSV, or similar using some assumptions:
diff --git a/src/app/data-converters/wikimetrics-timeseries.js 
b/src/app/converters/timeseries/wikimetrics-timeseries.js
similarity index 96%
rename from src/app/data-converters/wikimetrics-timeseries.js
rename to src/app/converters/timeseries/wikimetrics-timeseries.js
index 113b300..83956e7 100644
--- a/src/app/data-converters/wikimetrics-timeseries.js
+++ b/src/app/converters/timeseries/wikimetrics-timeseries.js
@@ -6,7 +6,7 @@
     'use strict';
 
     var _ = require('lodash'),
-        TimeseriesData = require('converters.timeseries');
+        TimeseriesData = require('models.timeseries');
 
     /**
      * Parameters
diff --git a/src/app/data-converters/factory.js 
b/src/app/data-converters/factory.js
deleted file mode 100644
index f93760d..0000000
--- a/src/app/data-converters/factory.js
+++ /dev/null
@@ -1,52 +0,0 @@
-'use strict';
-
-/**
- * A simple factory that determines what converter is needed based on
- *   different inputs
- */
-define(function (require) {
-
-    var separatedValues = require('converters.separated-values'),
-        wikimetricsTimeseries = require('converters.wikimetrics-timeseries'),
-        buildHierarchy = require('converters.hierarchy-data'),
-        aqsApiResponse = require('converters.aqs-api-response');
-
-    function ConverterFactory() {
-        return;
-    }
-
-    /**
-     * Based on format determine an appropriate converter and return it
-     * Note that converters are returned as functions that execute taking 
rawdata
-     * as an argument.
-     *
-     * Parameters
-     *  format: enum - (tsv, cvs, json)
-     *  valueField: string - The field in the results that contains the values.
-     *                       For now only used in the AQS.
-     */
-    ConverterFactory.prototype.getDataConverter = function (format, 
valueField) {
-
-        // note that the data converter modules return a function
-
-        switch (format) {
-        case 'tsv':
-            return separatedValues('\t');
-
-        case 'csv':
-            return separatedValues(',');
-
-        case 'json':
-            return wikimetricsTimeseries();
-
-        case 'aqs-api-response':
-            return aqsApiResponse(valueField);
-
-        case 'hierarchy':
-            return buildHierarchy;
-
-        }
-    };
-
-    return new ConverterFactory();
-});
diff --git a/src/app/data-converters/simple-separated-values.js 
b/src/app/data-converters/simple-separated-values.js
deleted file mode 100644
index fb595f4..0000000
--- a/src/app/data-converters/simple-separated-values.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * This module returns a method that knows how to parse a file with values
- *   separated by arbitrary characters on lines separated by arbitrary 
characters
- */
-'use strict';
-define(function (require) {
-
-    var stringUtils = require('utils.strings');
-
-    /**
-     * Parses a CSV, TSV, or similar into an array of rows.
-     * The TSV must have a header row.
-     * Looks at the second row to determine data type and applies that,
-     *   by column, to the rest of the rows.
-     */
-    return function (valueSeparator) {
-
-        return function (options, rawData) {
-
-            var opt = $.extend({
-                lineSeparator: '\n',
-                valueSeparator: {
-                    tsv: '\t',
-                    csv: ',',
-                }[valueSeparator],
-
-            }, options);
-
-            var rows = rawData.split(opt.lineSeparator)
-                .filter(function (line) {
-                    return line && line.length && line.length > 1;
-                }).map(function (line) {
-                    return line.split(opt.valueSeparator);
-                });
-
-            if (rows.length <= 1) { return []; }
-
-            // try to determine the type of each column
-            var parsers = rows[1].map(stringUtils.parserFromSample);
-            var trimmer = function (s) { return s.trim(); };
-
-            // NOTE: will return the header but take care not to parse it
-            return rows.map(function (row, rowIndex) {
-                return rowIndex === 0 ?
-                    row.map(trimmer) :
-                    row.map(function (value, index) {
-                        return parsers[index](value);
-                    });
-            });
-        };
-    };
-});
diff --git a/src/app/data-converters/timeseries-data.js 
b/src/app/models/timeseries-data.js
similarity index 100%
rename from src/app/data-converters/timeseries-data.js
rename to src/app/models/timeseries-data.js
diff --git a/src/app/require.config.js b/src/app/require.config.js
index 261f0a3..4ffcaf3 100644
--- a/src/app/require.config.js
+++ b/src/app/require.config.js
@@ -29,54 +29,60 @@
         // URI and URITemplate like define(['uri/URI', 'uri/URITemplate'] ...
         // because URITemplate modifies URI when it's parsed
         'uri'                   : 'bower_modules/URIjs/src',
-        'config'                : 'app/config',
-        'logger'                : 'lib/logger',
-
-        'api-finder'            : 'app/apis/api-finder',
-        'dataConverterFactory'  : 'app/data-converters/factory',
         'typeahead'             : 
'bower_modules/typeahead.js/dist/typeahead.bundle',
+
+        // *** app
+        'config'                : 'app/config',
+        'sitematrix'            : 'app/sitematrix',
+
+        // *** lib
+        'lib.polyfills'         : 'lib/polyfills',
         'ajaxWrapper'           : 'lib/ajax-wrapper',
         'window'                : 'lib/window',
         'stateManager'          : 'lib/state-manager',
-        'sitematrix'            : 'app/sitematrix',
+        'logger'                : 'lib/logger',
 
-        // *** viewmodels
-        'viewmodels.copy-params'    : 
'app/ko-extensions/common-viewmodels/copy-params',
-        'viewmodels.single-select'  : 
'app/ko-extensions/common-viewmodels/single-select',
-
-        // *** custom observables
-        'observables.async'         : 'app/ko-extensions/async-observables',
-
-        // *** general custom bindings
-        'datepicker-binding'        : 'app/ko-extensions/datepicker-binding',
-        'knockout.table'            : 'lib/knockout-extensions/knockout-table',
+        // *** finders
+        'finders.api'           : 'app/apis/api-finder',
+        'finders.converter'     : 'app/converters/converter-finder',
 
         // *** apis
-        'apis.wikimetrics'          : 'app/apis/wikimetrics',
-        'apis.annotations'          : 'app/apis/annotations-api',
-        'apis.aqs'                  : 'app/apis/aqs-api',
-        'apis.datasets'             : 'app/apis/datasets-api',
-        'apis.config'               : 'app/apis/config-api',
+        'apis.wikimetrics'      : 'app/apis/wikimetrics',
+        'apis.annotations'      : 'app/apis/annotations-api',
+        'apis.aqs'              : 'app/apis/aqs-api',
+        'apis.datasets'         : 'app/apis/datasets-api',
+        'apis.config'           : 'app/apis/config-api',
 
         // *** converters
-        'converters.separated-values'       : 
'app/data-converters/separated-values',
-        'converters.simple-separated-values': 
'app/data-converters/simple-separated-values',
-        'converters.wikimetrics-timeseries' : 
'app/data-converters/wikimetrics-timeseries',
-        'converters.hierarchy-data'         : 
'app/data-converters/hierarchy-data',
-        'converters.timeseries'             : 
'app/data-converters/timeseries-data',
-        'converters.annotations'            : 
'app/data-converters/annotations-data',
-        'converters.aqs-api-response'       : 
'app/data-converters/aqs-api-response',
+        'converters.hierarchy-data'         : 
'app/converters/hierarchy/hierarchy-data',
+        'converters.annotations'            : 
'app/converters/timeseries/annotations-data',
+        'converters.aqs-api-response'       : 
'app/converters/timeseries/aqs-api-response',
+        'converters.separated-values'       : 
'app/converters/timeseries/separated-values',
+        'converters.wikimetrics-timeseries' : 
'app/converters/timeseries/wikimetrics-timeseries',
+
+        // *** models
+        'models.timeseries'         : 'app/models/timeseries-data',
+
+        // *** knockout utils
+        // custom bindings
+        'knockout.datepicker'       : 'lib/knockout-utils/bindings/datepicker',
+        'knockout.dropdown'         : 'lib/knockout-utils/bindings/dropdown',
+        'knockout.popup'            : 'lib/knockout-utils/bindings/popup',
+        'knockout.table'            : 'lib/knockout-utils/bindings/table',
+        'knockout.toggle'           : 'lib/knockout-utils/bindings/toggle',
+        // viewmodels
+        'viewmodels.copy-params'    : 
'lib/knockout-utils/viewmodels/copy-params',
+        'viewmodels.single-select'  : 
'lib/knockout-utils/viewmodels/single-select',
+        // custom observables
+        'observables.async'         : 'lib/knockout-utils/async-observables',
 
         // *** utils
-        'utils.arrays'              : 'app/utils/arrays',
-        'utils.strings'             : 'app/utils/strings',
-        'utils.datetime'            : 'app/utils/datetime',
-        'utils.numbers'             : 'app/utils/numbers',
-        'utils.colors'              : 'app/utils/colors',
-        'utils.elements'            : 'app/utils/elements',
-
-        // *** lib
-        'lib.polyfills'             : 'lib/polyfills',
+        'utils.arrays'              : 'lib/utils/arrays',
+        'utils.strings'             : 'lib/utils/strings',
+        'utils.datetime'            : 'lib/utils/datetime',
+        'utils.numbers'             : 'lib/utils/numbers',
+        'utils.colors'              : 'lib/utils/colors',
+        'utils.elements'            : 'lib/utils/elements',
     },
     shim: {
         'ajaxWrapper': {
diff --git a/src/app/startup.js b/src/app/startup.js
index 0c04af3..5b833d0 100644
--- a/src/app/startup.js
+++ b/src/app/startup.js
@@ -10,48 +10,45 @@
     require('jquery');
     require('ajaxWrapper');
     require('logger');
-    require('app/ko-extensions/global-bindings');
-    
+
     // out-of-service component is common
-    ko.components.register('out-of-service', {require: 
'components/out-of-service/out-of-service'    });
+    ko.components.register('out-of-service', {require: 
'components/layouts/out-of-service/out-of-service'    });
 
     // separate layouts, TODO: make each layout register its own components
 
     // *********** BEGIN Metrics By Project Layout Components ********** //
-    ko.components.register('metrics-by-project-layout', { require: 
'components/metrics-by-project-layout/metrics-by-project-layout' });
-    ko.components.register('wikimetrics', { require: 
'components/visualizers/wikimetrics/wikimetrics' });
-    ko.components.register('project-selector', { require: 
'components/project-selector/project-selector' });
-    ko.components.register('metric-selector', { require: 
'components/metric-selector/metric-selector' });
-    ko.components.register('breakdown-toggle', { require: 
'components/breakdown-toggle/breakdown-toggle' });
-    ko.components.register('vega-timeseries', { require: 
'components/visualizers/vega-timeseries/vega-timeseries' });
-    ko.components.register('annotation-list', { require: 
'components/annotation-list/annotation-list' });
+    ko.components.register('metrics-by-project-layout', { require: 
'components/layouts/metrics-by-project/metrics-by-project' });
+    ko.components.register('wikimetrics',               { require: 
'components/visualizers/wikimetrics/wikimetrics' });
+    ko.components.register('project-selector',          { require: 
'components/selectors/project/project' });
+    ko.components.register('metric-selector',           { require: 
'components/selectors/metric/metric' });
+    ko.components.register('breakdown-toggle',          { require: 
'components/controls/breakdown-toggle/breakdown-toggle' });
+    ko.components.register('vega-timeseries',           { require: 
'components/visualizers/vega-timeseries/vega-timeseries' });
+    ko.components.register('annotation-list',           { require: 
'components/visualizers/annotation-list/annotation-list' });
     // *********** END Metrics By Project Layout Components ************ //
 
 
     // *********** BEGIN Compare Layout Components ********** //
-    ko.components.register('compare-layout', { require: 
'components/compare-layout/compare-layout' });
-    ko.components.register('dropdown', { require: 
'components/dropdown/dropdown' });
-    ko.components.register('button-group', { require: 
'components/button-group/button-group' });
-    ko.components.register('sunburst', { require: 
'components/visualizers/sunburst/sunburst' });
-    ko.components.register('hierarchy', { require: 
'components/visualizers/hierarchy/hierarchy' });
-    ko.components.register('rickshaw-timeseries', { require: 
'components/visualizers/rickshaw-timeseries/rickshaw-timeseries' });
-    ko.components.register('nvd3-timeseries', { require: 
'components/visualizers/nvd3-timeseries/nvd3-timeseries' });
-    ko.components.register('dygraphs-timeseries', { require: 
'components/visualizers/dygraphs-timeseries/dygraphs-timeseries' });
-    ko.components.register('filter-timeseries', { require: 
'components/visualizers/filter-timeseries/filter-timeseries' });
-    ko.components.register('stacked-bars', { require: 
'components/visualizers/stacked-bars/stacked-bars' });
-
+    ko.components.register('compare-layout',        { require: 
'components/layouts/compare/compare' });
+    ko.components.register('dropdown',              { require: 
'components/controls/dropdown/dropdown' });
+    ko.components.register('button-group',          { require: 
'components/controls/button-group/button-group' });
+    ko.components.register('sunburst',              { require: 
'components/visualizers/sunburst/sunburst' });
+    ko.components.register('hierarchy',             { require: 
'components/visualizers/hierarchy/hierarchy' });
+    ko.components.register('dygraphs-timeseries',   { require: 
'components/visualizers/dygraphs-timeseries/dygraphs-timeseries' });
+    ko.components.register('filter-timeseries',     { require: 
'components/visualizers/filter-timeseries/filter-timeseries' });
+    ko.components.register('stacked-bars',          { require: 
'components/visualizers/stacked-bars/stacked-bars' });
 
     // comparison components
-    ko.components.register('a-b-compare', { require: 
'components/a-b-compare/a-b-compare' });
-    ko.components.register('compare-sunburst', { require: 
'components/a-b-compare/compare-sunburst' });
-    ko.components.register('compare-timeseries', { require: 
'components/a-b-compare/compare-timeseries' });
-    ko.components.register('compare-stacked-bars', { require: 
'components/a-b-compare/compare-stacked-bars' });
+    ko.components.register('a-b-compare',           { require: 
'components/visualizers/a-b-compare/a-b-compare' });
+    ko.components.register('compare-sunburst',      { require: 
'components/visualizers/a-b-compare/compare-sunburst' });
+    ko.components.register('compare-timeseries',    { require: 
'components/visualizers/a-b-compare/compare-timeseries' });
+    ko.components.register('compare-stacked-bars',  { require: 
'components/visualizers/a-b-compare/compare-stacked-bars' });
     // *********** END Compare Layout Components ************ //
 
+
     // *********** BEGIN Tabs Layout Components ********** //
-    ko.components.register('tabs-layout', { require: 
'components/tabs-layout/tabs-layout' });
-    ko.components.register('visualizer', { require: 
'components/visualizers/visualizer/visualizer' });
-    ko.components.register('table-timeseries', { require: 
'components/visualizers/table-timeseries/table-timeseries' });
+    ko.components.register('tabs-layout',       { require: 
'components/layouts/tabs/tabs' });
+    ko.components.register('visualizer',        { require: 
'components/visualizers/visualizer/visualizer' });
+    ko.components.register('table-timeseries',  { require: 
'components/visualizers/table-timeseries/table-timeseries' });
     // *********** END Tabs Layout Components ************ //
 
     // Setup knockout to globally defer updates
diff --git a/src/components/breakdown-toggle/breakdown-toggle.html 
b/src/components/controls/breakdown-toggle/breakdown-toggle.html
similarity index 96%
rename from src/components/breakdown-toggle/breakdown-toggle.html
rename to src/components/controls/breakdown-toggle/breakdown-toggle.html
index d71f36e..386eb36 100644
--- a/src/components/breakdown-toggle/breakdown-toggle.html
+++ b/src/components/controls/breakdown-toggle/breakdown-toggle.html
@@ -2,7 +2,7 @@
     <a class="item" data-bind="css: {'active': display()}, click: toggle">
         <span>
             <i class="large block layout icon"></i>
-            Data Breakdowns
+            Break Down by Site
         </span>
     </a>
     <!-- ko if: display() -->
diff --git a/src/components/breakdown-toggle/breakdown-toggle.js 
b/src/components/controls/breakdown-toggle/breakdown-toggle.js
similarity index 100%
rename from src/components/breakdown-toggle/breakdown-toggle.js
rename to src/components/controls/breakdown-toggle/breakdown-toggle.js
diff --git a/src/components/button-group/button-group.html 
b/src/components/controls/button-group/button-group.html
similarity index 100%
rename from src/components/button-group/button-group.html
rename to src/components/controls/button-group/button-group.html
diff --git a/src/components/button-group/button-group.js 
b/src/components/controls/button-group/button-group.js
similarity index 100%
rename from src/components/button-group/button-group.js
rename to src/components/controls/button-group/button-group.js
diff --git a/src/components/dropdown/dropdown.html 
b/src/components/controls/dropdown/dropdown.html
similarity index 100%
rename from src/components/dropdown/dropdown.html
rename to src/components/controls/dropdown/dropdown.html
diff --git a/src/components/dropdown/dropdown.js 
b/src/components/controls/dropdown/dropdown.js
similarity index 100%
rename from src/components/dropdown/dropdown.js
rename to src/components/controls/dropdown/dropdown.js
diff --git a/src/components/compare-layout/compare-layout.html 
b/src/components/layouts/compare/compare.html
similarity index 100%
rename from src/components/compare-layout/compare-layout.html
rename to src/components/layouts/compare/compare.html
diff --git a/src/components/compare-layout/compare-layout.js 
b/src/components/layouts/compare/compare.js
similarity index 94%
rename from src/components/compare-layout/compare-layout.js
rename to src/components/layouts/compare/compare.js
index c88a2a8..cd30931 100644
--- a/src/components/compare-layout/compare-layout.js
+++ b/src/components/layouts/compare/compare.js
@@ -5,18 +5,22 @@
 
     var ko = require('knockout'),
         _ = require('lodash'),
-        templateMarkup = require('text!./compare-layout.html'),
-        apiFinder = require('api-finder'),
-        configApi = require('apis.config'),
+        templateMarkup = require('text!./compare.html'),
+        apiFinder = require('finders.api'),
         marked = require('marked'),
         moment = require('moment'),
         dateUtils = require('utils.datetime'),
         colorUtils = require('utils.colors'),
         numberUtils = require('utils.numbers'),
         asyncObs = require('observables.async'),
-        TimeseriesData = require('converters.timeseries');
+        TimeseriesData = require('models.timeseries');
 
     require('twix');
+    require('knockout.popup');
+
+    var configApi = apiFinder({api: 'config'}),
+        wikimetricsApi = apiFinder({api: 'wikimetrics'}),
+        datasetsApi = apiFinder({api: 'datasets'});
 
     function FunnelLayout() {
         // *** set up the filters with empty options, fill later from config
@@ -24,8 +28,7 @@
             options: ko.observable([]),
             selected: ko.observable(),
         };
-        var wikimetricsApi = apiFinder({api: 'wikimetrics'}),
-            wikiPromise = wikimetricsApi.getProjectAndLanguageChoices(function 
(data) {
+        var wikiPromise = wikimetricsApi.getProjectAndLanguageChoices(function 
(data) {
                 var databases = 
Object.getOwnPropertyNames(data.reverseLookup).sort();
                 this.wiki.options(['all'].concat(databases));
             }.bind(this));
@@ -114,7 +117,7 @@
 
                 // comparisons
                 var asyncData = asyncObs.asyncData.bind(this),
-                    api = apiFinder({api: 'datasets'}),
+                    api = datasetsApi,
                     emptyPromise = new $.Deferred();
 
                 emptyPromise.resolveWith(null, [new TimeseriesData([])]);
diff --git 
a/src/components/metrics-by-project-layout/metrics-by-project-layout.html 
b/src/components/layouts/metrics-by-project/metrics-by-project.html
similarity index 100%
rename from 
src/components/metrics-by-project-layout/metrics-by-project-layout.html
rename to src/components/layouts/metrics-by-project/metrics-by-project.html
diff --git 
a/src/components/metrics-by-project-layout/metrics-by-project-layout.js 
b/src/components/layouts/metrics-by-project/metrics-by-project.js
similarity index 96%
rename from 
src/components/metrics-by-project-layout/metrics-by-project-layout.js
rename to src/components/layouts/metrics-by-project/metrics-by-project.js
index a2fae77..5b84a7d 100644
--- a/src/components/metrics-by-project-layout/metrics-by-project-layout.js
+++ b/src/components/layouts/metrics-by-project/metrics-by-project.js
@@ -6,7 +6,7 @@
         wikimetricsApi = require('apis.wikimetrics'),
         configApi = require('apis.config'),
         stateManagerFactory = require('stateManager'),
-        templateMarkup = require('text!./metrics-by-project-layout.html');
+        templateMarkup = require('text!./metrics-by-project.html');
 
     function WikimetricsLayout() {
         var self = this;
diff --git a/src/components/out-of-service/out-of-service.html 
b/src/components/layouts/out-of-service/out-of-service.html
similarity index 100%
rename from src/components/out-of-service/out-of-service.html
rename to src/components/layouts/out-of-service/out-of-service.html
diff --git a/src/components/layouts/out-of-service/out-of-service.js 
b/src/components/layouts/out-of-service/out-of-service.js
new file mode 100644
index 0000000..234ff42
--- /dev/null
+++ b/src/components/layouts/out-of-service/out-of-service.js
@@ -0,0 +1,29 @@
+'use strict';
+/**
+ * When a global config is set for this dashboard a message will
+ * about the dashboard being out of service will pop out.
+ * See config: https://meta.wikimedia.org/wiki/Dashiki:OutOfService
+ **/
+define(function (require) {
+
+    var templateMarkup = require('text!./out-of-service.html'),
+        ko = require('knockout'),
+        configApi = require('apis.config');
+
+    function OutOfServiceBanner() {
+        this.isBannerOn = ko.observable(false);
+        this.customMessage = ko.observable('');
+
+        //request data and change out of banner state if proceeds
+        configApi.getOutOfService(function (config) {
+            if (config.outOfService === 'true') {
+                this.isBannerOn(true);
+                this.customMessage(config.customMessage);
+            }
+        }.bind(this));
+    }
+    return {
+        viewModel: OutOfServiceBanner,
+        template: templateMarkup
+    };
+});
diff --git a/src/components/tabs-layout/tabs-layout.html 
b/src/components/layouts/tabs/tabs.html
similarity index 100%
rename from src/components/tabs-layout/tabs-layout.html
rename to src/components/layouts/tabs/tabs.html
diff --git a/src/components/tabs-layout/tabs-layout.js 
b/src/components/layouts/tabs/tabs.js
similarity index 97%
rename from src/components/tabs-layout/tabs-layout.js
rename to src/components/layouts/tabs/tabs.js
index 941a7b9..6866983 100644
--- a/src/components/tabs-layout/tabs-layout.js
+++ b/src/components/layouts/tabs/tabs.js
@@ -6,7 +6,7 @@
 
     var ko = require('knockout'),
         _ = require('lodash'),
-        templateMarkup = require('text!./tabs-layout.html'),
+        templateMarkup = require('text!./tabs.html'),
         configApi = require('apis.config');
 
     require('twix');
diff --git a/src/components/out-of-service/out-of-service.js 
b/src/components/out-of-service/out-of-service.js
deleted file mode 100644
index 83c12a6..0000000
--- a/src/components/out-of-service/out-of-service.js
+++ /dev/null
@@ -1,29 +0,0 @@
-'use strict';
-/**
- * When a global config is set for this dashboard a message will
- * about the dashboard being out of service will pop out.
- * See config: https://meta.wikimedia.org/wiki/Dashiki:OutOfService
- **/
-define(function (require) {
-
-       var templateMarkup = require('text!./out-of-service.html'),
-               ko = require('knockout'),
-               configApi = require('apis.config');
-
-       function OutOfServiceBanner() {
-               this.isBannerOn = ko.observable(false);
-               this.customMessage = ko.observable("");
-
-               //request data and change out of banner state if proceeds
-               configApi.getOutOfService(function (config) {
-                       if (config.outOfService === "true") {
-                               this.isBannerOn(true);
-                               this.customMessage(config.customMessage);
-                       }
-               }.bind(this));
-       }
-       return {
-               viewModel: OutOfServiceBanner,
-               template: templateMarkup
-       };
-});
\ No newline at end of file
diff --git a/src/components/metric-selector/bindings.js 
b/src/components/selectors/metric/bindings.js
similarity index 100%
rename from src/components/metric-selector/bindings.js
rename to src/components/selectors/metric/bindings.js
diff --git a/src/components/metric-selector/metric-selector.html 
b/src/components/selectors/metric/metric.html
similarity index 100%
rename from src/components/metric-selector/metric-selector.html
rename to src/components/selectors/metric/metric.html
diff --git a/src/components/metric-selector/metric-selector.js 
b/src/components/selectors/metric/metric.js
similarity index 97%
rename from src/components/metric-selector/metric-selector.js
rename to src/components/selectors/metric/metric.js
index 84cf70d..2a95e26 100644
--- a/src/components/metric-selector/metric-selector.js
+++ b/src/components/selectors/metric/metric.js
@@ -22,10 +22,11 @@
 define(function (require) {
 
     var ko = require('knockout'),
-        templateMarkup = require('text!./metric-selector.html'),
+        templateMarkup = require('text!./metric.html'),
         arrayUtils = require('utils.arrays');
 
     require('./bindings');
+    require('knockout.toggle');
 
     function MetricSelector(params) {
         var self = this;
diff --git a/src/components/project-selector/bindings.js 
b/src/components/selectors/project/bindings.js
similarity index 100%
rename from src/components/project-selector/bindings.js
rename to src/components/selectors/project/bindings.js
diff --git a/src/components/project-selector/project-selector.html 
b/src/components/selectors/project/project.html
similarity index 100%
rename from src/components/project-selector/project-selector.html
rename to src/components/selectors/project/project.html
diff --git a/src/components/project-selector/project-selector.js 
b/src/components/selectors/project/project.js
similarity index 98%
rename from src/components/project-selector/project-selector.js
rename to src/components/selectors/project/project.js
index 625a42e..02a482b 100644
--- a/src/components/project-selector/project-selector.js
+++ b/src/components/selectors/project/project.js
@@ -2,7 +2,7 @@
 define(function (require) {
 
     var ko              = require('knockout'),
-        templateMarkup  = require('text!./project-selector.html'),
+        templateMarkup  = require('text!./project.html'),
         arrayUtils      = require('utils.arrays');
     require('./bindings');
 
diff --git a/src/components/a-b-compare/a-b-compare.html 
b/src/components/visualizers/a-b-compare/a-b-compare.html
similarity index 100%
rename from src/components/a-b-compare/a-b-compare.html
rename to src/components/visualizers/a-b-compare/a-b-compare.html
diff --git a/src/components/a-b-compare/a-b-compare.js 
b/src/components/visualizers/a-b-compare/a-b-compare.js
similarity index 100%
rename from src/components/a-b-compare/a-b-compare.js
rename to src/components/visualizers/a-b-compare/a-b-compare.js
diff --git a/src/components/a-b-compare/compare-stacked-bars.html 
b/src/components/visualizers/a-b-compare/compare-stacked-bars.html
similarity index 100%
rename from src/components/a-b-compare/compare-stacked-bars.html
rename to src/components/visualizers/a-b-compare/compare-stacked-bars.html
diff --git a/src/components/a-b-compare/compare-stacked-bars.js 
b/src/components/visualizers/a-b-compare/compare-stacked-bars.js
similarity index 100%
rename from src/components/a-b-compare/compare-stacked-bars.js
rename to src/components/visualizers/a-b-compare/compare-stacked-bars.js
diff --git a/src/components/a-b-compare/compare-sunburst.html 
b/src/components/visualizers/a-b-compare/compare-sunburst.html
similarity index 100%
rename from src/components/a-b-compare/compare-sunburst.html
rename to src/components/visualizers/a-b-compare/compare-sunburst.html
diff --git a/src/components/a-b-compare/compare-sunburst.js 
b/src/components/visualizers/a-b-compare/compare-sunburst.js
similarity index 100%
rename from src/components/a-b-compare/compare-sunburst.js
rename to src/components/visualizers/a-b-compare/compare-sunburst.js
diff --git a/src/components/a-b-compare/compare-timeseries.html 
b/src/components/visualizers/a-b-compare/compare-timeseries.html
similarity index 100%
rename from src/components/a-b-compare/compare-timeseries.html
rename to src/components/visualizers/a-b-compare/compare-timeseries.html
diff --git a/src/components/a-b-compare/compare-timeseries.js 
b/src/components/visualizers/a-b-compare/compare-timeseries.js
similarity index 100%
rename from src/components/a-b-compare/compare-timeseries.js
rename to src/components/visualizers/a-b-compare/compare-timeseries.js
diff --git a/src/components/annotation-list/annotation-list.html 
b/src/components/visualizers/annotation-list/annotation-list.html
similarity index 100%
rename from src/components/annotation-list/annotation-list.html
rename to src/components/visualizers/annotation-list/annotation-list.html
diff --git a/src/components/annotation-list/annotation-list.js 
b/src/components/visualizers/annotation-list/annotation-list.js
similarity index 98%
rename from src/components/annotation-list/annotation-list.js
rename to src/components/visualizers/annotation-list/annotation-list.js
index e560c4c..5259592 100644
--- a/src/components/annotation-list/annotation-list.js
+++ b/src/components/visualizers/annotation-list/annotation-list.js
@@ -9,6 +9,7 @@
         dateUtils = require('utils.datetime');
 
     require('./bindings');
+    require('knockout.popup');
 
     function AnnotationList(params) {
 
diff --git a/src/components/annotation-list/bindings.js 
b/src/components/visualizers/annotation-list/bindings.js
similarity index 100%
rename from src/components/annotation-list/bindings.js
rename to src/components/visualizers/annotation-list/bindings.js
diff --git a/src/components/visualizers/nvd3-timeseries/bindings.js 
b/src/components/visualizers/nvd3-timeseries/bindings.js
deleted file mode 100644
index a428eab..0000000
--- a/src/components/visualizers/nvd3-timeseries/bindings.js
+++ /dev/null
@@ -1,46 +0,0 @@
-define(function(require) {
-
-    var ko = require('knockout'),
-        d3 = require('d3'),
-        nv = require('nvd3');
-
-    ko.bindingHandlers.nvLineFocus = {
-        init: function (element, valueAccessor) {
-            var val = ko.unwrap(valueAccessor()),
-                chart = nv.models.lineWithFocusChart();
-
-            chart.lines.xScale(d3.time.scale());
-            chart.lines2.xScale(d3.time.scale());
-
-            var timeFormat = d3.time.format('%Y-%m-%d');
-            chart.xAxis.tickFormat(timeFormat).showMaxMin(false);
-            chart.x2Axis.tickFormat(timeFormat).showMaxMin(false);
-            chart.yAxis.showMaxMin(false);
-            chart.y2Axis.showMaxMin(false);
-
-            element.root = 
d3.select(element).select(val.graphSelect).append('svg');
-            element.root.datum([]).call(chart);
-
-            // These are glitchy but not sure if to leave them on or off
-            //chart.useVoronoi(false);
-            //chart.clipVoronoi(false);
-
-            nv.utils.windowResize(chart.update);
-
-            element.chart = chart;
-        },
-        update: function (element, valueAccessor) {
-            var val = ko.unwrap(valueAccessor());
-
-            element.root.datum(val.series().map(function(serie) {
-                return {
-                    key: serie.name,
-                    values: serie.data,
-                    color: serie.color,
-                    // NOTE: This does not appear to work
-                    classed: serie.renderer === 'line' ? undefined : 'dashed',
-                };
-            })).call(element.chart);
-        }
-    };
-});
diff --git a/src/components/visualizers/nvd3-timeseries/nvd3-timeseries.html 
b/src/components/visualizers/nvd3-timeseries/nvd3-timeseries.html
deleted file mode 100644
index 8ac1e3b..0000000
--- a/src/components/visualizers/nvd3-timeseries/nvd3-timeseries.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<div data-bind="style: {height: height + 'px'}, nvLineFocus: {
-        series: series,
-        graphSelect: '.graph',
-        colors: $data.colors,
-}">
-    <div class="resizable container graph with-3d-shadow 
with-transitions"></div>
-</div>
diff --git a/src/components/visualizers/nvd3-timeseries/nvd3-timeseries.js 
b/src/components/visualizers/nvd3-timeseries/nvd3-timeseries.js
deleted file mode 100644
index af82b95..0000000
--- a/src/components/visualizers/nvd3-timeseries/nvd3-timeseries.js
+++ /dev/null
@@ -1,12 +0,0 @@
-define(function(require) {
-
-    var CopyParams = require('app/common-viewmodels/copy-params'),
-        templateMarkup = require('text!./nvd3-timeseries.html');
-
-    require('./bindings');
-
-    return {
-        viewModel: CopyParams,
-        template: templateMarkup
-    };
-});
diff --git a/src/components/visualizers/rickshaw-timeseries/bindings.js 
b/src/components/visualizers/rickshaw-timeseries/bindings.js
deleted file mode 100644
index 54a0cc1..0000000
--- a/src/components/visualizers/rickshaw-timeseries/bindings.js
+++ /dev/null
@@ -1,102 +0,0 @@
-'use strict';
-define(function(require) {
-
-    var Rickshaw = require('rickshaw'),
-        ko = require('knockout'),
-        d3 = require('d3'),
-        getBounds = require('utils.elements').getBounds;
-
-    require('lib/polyfills');
-
-    ko.bindingHandlers.rickshawTime = {
-        init: function (element, valueAccessor) {
-            var val = ko.unwrap(valueAccessor());
-
-            var graphEl = d3.select(element).select(val.graphSelect).node(),
-                legendEl = d3.select(element).select(val.legendSelect).node(),
-                timelineEl = 
d3.select(element).select(val.timelineSelect).node(),
-                zoomSliderEl = 
d3.select(element).select(val.zoomSliderSelect).node(),
-
-                opt = val.options || {},
-
-                bounds = getBounds(element, '.resizable.container');
-
-            graphEl.graph = new Rickshaw.Graph({
-                element: graphEl,
-                width: opt.width || bounds.width * (opt.widthRatio || 13 / 16),
-                height: opt.height || bounds.height * (opt.heightRatio || 5 / 
6),
-                renderer: 'multi',
-                series: [],
-            });
-
-            graphEl.xAxis = new Rickshaw.Graph.Axis.Time({ graph: 
graphEl.graph });
-            graphEl.yAxis = new Rickshaw.Graph.Axis.Y({
-                graph: graphEl.graph,
-                orientation: 'right',
-                tickFormat: Rickshaw.Fixtures.Number.formatKMBT
-            });
-
-            graphEl.legend = new Rickshaw.Graph.Legend({
-                graph: graphEl.graph,
-                element: legendEl
-            });
-
-            graphEl.annotator = new Rickshaw.Graph.Annotate({
-                graph: graphEl.graph,
-                element: timelineEl
-            });
-
-            graphEl.zoomSlider = new Rickshaw.Graph.RangeSlider.Preview({
-                graph: graphEl.graph,
-                element: zoomSliderEl
-            });
-
-            graphEl.graph.render();
-        },
-        update: function (element, valueAccessor) {
-            var val = ko.unwrap(valueAccessor()),
-                graphEl = d3.select(element).select(val.graphSelect).node(),
-                graph = graphEl.graph,
-                legend = graphEl.legend;
-
-            // reset the graph
-            graph.series.length = 0;
-            ko.unwrap(val.series).forEach(function (serie) {
-                // transform x to seconds
-                serie.data.forEach(function (point) {
-                    point.x = point.x.getTime() / 1000;
-                });
-                var existing = graph.series.find(function (existingSerie) {
-                    return existingSerie.name === serie.name;
-                });
-                if (!existing) {
-                    graph.series.push(serie);
-                } else {
-                    existing.data.length = 0;
-                    existing.data.push.apply(existing.data, serie.data);
-                }
-            });
-
-            graph.validateSeries(graph.series);
-            graph.update();
-            legend.render();
-
-            // some extended behavior needs to be re-created
-            graphEl.hoverDetail = new Rickshaw.Graph.HoverDetail({
-                graph: graph
-            });
-
-            graphEl.shelving = new Rickshaw.Graph.Behavior.Series.Toggle({
-                graph: graph,
-                legend: legend
-            });
-
-            /* NOTE the Rickshaw Highlighter below messes up the scale:
-            graphEl.highlighter = new 
Rickshaw.Graph.Behavior.Series.Highlight({
-                graph: graph,
-                legend: legend
-            });
-            */
-        }
-    };
-});
diff --git 
a/src/components/visualizers/rickshaw-timeseries/rickshaw-extensions.js 
b/src/components/visualizers/rickshaw-timeseries/rickshaw-extensions.js
deleted file mode 100644
index cf868e4..0000000
--- a/src/components/visualizers/rickshaw-timeseries/rickshaw-extensions.js
+++ /dev/null
@@ -1,53 +0,0 @@
-define(function(require) {
-
-    var Rickshaw = require('rickshaw'),
-        d3 = require('d3');
-
-    Rickshaw.namespace('Rickshaw.Graph.Renderer.DashedLine');
-    Rickshaw.Graph.Renderer.DashedLine = Rickshaw.Class.create( 
Rickshaw.Graph.Renderer, {
-
-        name: 'dashed-line',
-
-        defaults: function($super) {
-
-            return Rickshaw.extend( $super(), {
-                unstack: true,
-                fill: false,
-                stroke: true
-            } );
-        },
-
-        seriesPathFactory: function() {
-
-            var graph = this.graph;
-
-            var factory = d3.svg.line()
-                .x( function(d) { return graph.x(d.x); } )
-                .y( function(d) { return graph.y(d.y); } )
-                .interpolate(this.graph.interpolation).tension(this.tension);
-
-            if(factory.defined) {
-                factory.defined( function(d) { return d.y !== null; } );
-            }
-            return factory;
-        },
-
-        _styleSeries: function(series) {
-            var fill = this.fill ? series.color : 'none';
-            var stroke = this.stroke ? series.color : 'none';
-
-            series.path.setAttribute('fill', fill);
-            series.path.setAttribute('stroke', stroke);
-            series.path.setAttribute('stroke-width', this.strokeWidth);
-
-            if (series.className) {
-                d3.select(series.path).classed(series.className, true);
-            }
-            if (series.className && this.stroke) {
-                d3.select(series.stroke).classed(series.className, true);
-            }
-            // This is the only line that changed, is there a better way?
-            series.path.setAttribute('stroke-dasharray', '5,5');
-        }
-    } );
-});
diff --git 
a/src/components/visualizers/rickshaw-timeseries/rickshaw-timeseries.html 
b/src/components/visualizers/rickshaw-timeseries/rickshaw-timeseries.html
deleted file mode 100644
index 1501878..0000000
--- a/src/components/visualizers/rickshaw-timeseries/rickshaw-timeseries.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<div class="resizable container ui tight grid">
-    <div data-bind="rickshawTime: {
-        series: series,
-        graphSelect: '.graph',
-        legendSelect: '.legend',
-        timelineSelect: '.timeline',
-        zoomSliderSelect: '.zoomSlider',
-        options: {
-            height: $data.height,
-            widthRatio: 13/16*0.97,
-        }
-    }" class="rickshaw row">
-        <div class="thirteen wide column">
-            <div class="graph"></div>
-            <div class="timeline"></div>
-            <div class="zoomSlider"></div>
-        </div>
-        <div class="right aligned three wide column">
-            <div class="legend"></div>
-        </div>
-    </div>
-</div>
diff --git 
a/src/components/visualizers/rickshaw-timeseries/rickshaw-timeseries.js 
b/src/components/visualizers/rickshaw-timeseries/rickshaw-timeseries.js
deleted file mode 100644
index 5202689..0000000
--- a/src/components/visualizers/rickshaw-timeseries/rickshaw-timeseries.js
+++ /dev/null
@@ -1,13 +0,0 @@
-define(function(require) {
-
-    var CopyParams = require('app/common-viewmodels/copy-params'),
-        templateMarkup = require('text!./rickshaw-timeseries.html');
-
-    require('./bindings');
-    require('./rickshaw-extensions');
-
-    return {
-        viewModel: CopyParams,
-        template: templateMarkup
-    };
-});
diff --git a/src/components/visualizers/visualizer/visualizer.js 
b/src/components/visualizers/visualizer/visualizer.js
index 0a7991b..490b5e2 100644
--- a/src/components/visualizers/visualizer/visualizer.js
+++ b/src/components/visualizers/visualizer/visualizer.js
@@ -4,13 +4,13 @@
     var templateMarkup = require('text!./visualizer.html'),
         ko = require('knockout'),
         _ = require('lodash'),
-        apiFinder = require('api-finder'),
+        apiFinder = require('finders.api'),
         colorUtils = require('utils.colors'),
         numberUtils = require('utils.numbers'),
-        TimeseriesData = require('converters.timeseries'),
+        TimeseriesData = require('models.timeseries'),
         moment = require('moment');
 
-    require('datepicker-binding');
+    require('knockout.datepicker');
 
     function Visualizer(params) {
         var api = apiFinder({
diff --git a/src/components/visualizers/wikimetrics/wikimetrics.js 
b/src/components/visualizers/wikimetrics/wikimetrics.js
index fdd9771..6aca3b1 100644
--- a/src/components/visualizers/wikimetrics/wikimetrics.js
+++ b/src/components/visualizers/wikimetrics/wikimetrics.js
@@ -15,11 +15,12 @@
 
     var ko = require('knockout'),
         _ = require('lodash'),
-        TimeseriesData = require('converters.timeseries'),
+        TimeseriesData = require('models.timeseries'),
         templateMarkup = require('text!./wikimetrics.html'),
-        annotationsApi = require('apis.annotations'),
-        apiFinder = require('app/apis/api-finder'),
+        apiFinder = require('finders.api'),
         numberUtils = require('utils.numbers');
+
+    var annotationsApi = apiFinder({api: 'annotations'});
 
     function WikimetricsVisualizer(params) {
         var visualizer = this;
@@ -43,7 +44,7 @@
                     for (var i = 0; i < this.breakdownColumns().length; i++) {
                         var column = this.breakdownColumns()[i];
                         if (column.selected()) {
-                            breakdown.push(column.label)
+                            breakdown.push(column.label);
                         }
                     }
                 }
@@ -132,4 +133,4 @@
         viewModel: WikimetricsVisualizer,
         template: templateMarkup
     };
-});
\ No newline at end of file
+});
diff --git a/src/layouts/compare/index.js b/src/layouts/compare/index.js
index b01cfb5..8ac80f4 100644
--- a/src/layouts/compare/index.js
+++ b/src/layouts/compare/index.js
@@ -5,21 +5,21 @@
         optimizerConfig: {
             include: [
                 'requireLib',
-                'components/compare-layout/compare-layout',
-                'components/dropdown/dropdown',
-                'components/button-group/button-group',
-                'components/button-group/button-group',
-                'components/a-b-compare/a-b-compare',
-                'components/a-b-compare/compare-sunburst',
-                'components/a-b-compare/compare-timeseries',
-                'components/a-b-compare/compare-stacked-bars',
+                'components/layouts/compare/compare',
+                'components/controls/dropdown/dropdown',
+                'components/controls/button-group/button-group',
+                'components/controls/button-group/button-group',
+                'components/visualizers/a-b-compare/a-b-compare',
+                'components/visualizers/a-b-compare/compare-sunburst',
+                'components/visualizers/a-b-compare/compare-timeseries',
+                'components/visualizers/a-b-compare/compare-stacked-bars',
             ],
             bundles: {
                 // If you want parts of the site to load on demand, remove 
them from the 'include' list
                 // above, and group them into bundles here.
-                'out-of-service' :['components/out-of-service/out-of-service'],
-                'sunburst': ['components/visualizers/sunburst/sunburst'],
-                'stacked-bars': 
['components/visualizers/stacked-bars/stacked-bars'],
+                'out-of-service':   
['components/layouts/out-of-service/out-of-service'],
+                'sunburst':         
['components/visualizers/sunburst/sunburst'],
+                'stacked-bars':     
['components/visualizers/stacked-bars/stacked-bars'],
                 'filter-timeseries': [
                     
'components/visualizers/filter-timeseries/filter-timeseries',
                     
'components/visualizers/dygraphs-timeseries/dygraphs-timeseries'
diff --git a/src/layouts/metrics-by-project/index.js 
b/src/layouts/metrics-by-project/index.js
index e4dadce..8ae999f 100644
--- a/src/layouts/metrics-by-project/index.js
+++ b/src/layouts/metrics-by-project/index.js
@@ -5,17 +5,17 @@
         optimizerConfig: {
             include: [
                 'requireLib',
+                'components/layouts/metrics-by-project/metrics-by-project',
                 'components/visualizers/wikimetrics/wikimetrics',
-                
'components/metrics-by-project-layout/metrics-by-project-layout',
-                'components/metric-selector/metric-selector'
+                'components/selectors/metric/metric',
             ],
             bundles: {
                 // If you want parts of the site to load on demand, remove 
them from the 'include' list
                 // above, and group them into bundles here.
-                'out-of-service' :['components/out-of-service/out-of-service'],
-                'project-selector': 
['components/project-selector/project-selector'],
-                'dygraphs-timeseries': 
['components/visualizers/dygraphs-timeseries/dygraphs-timeseries'],
-                'breakdown-toggle': 
['components/breakdown-toggle/breakdown-toggle']
+                'out-of-service':       
['components/layouts/out-of-service/out-of-service'],
+                'project-selector':     
['components/selectors/project/project'],
+                'dygraphs-timeseries':  
['components/visualizers/dygraphs-timeseries/dygraphs-timeseries'],
+                'breakdown-toggle':     
['components/controls/breakdown-toggle/breakdown-toggle'],
             }
         }
     };
diff --git a/src/layouts/tabs/index.js b/src/layouts/tabs/index.js
index b4b369f..1aaf523 100644
--- a/src/layouts/tabs/index.js
+++ b/src/layouts/tabs/index.js
@@ -5,23 +5,23 @@
         optimizerConfig: {
             include: [
                 'requireLib',
-                'components/tabs-layout/tabs-layout',
+                'components/layouts/tabs/tabs',
                 'components/visualizers/visualizer/visualizer',
             ],
             bundles: {
                 // If you want parts of the site to load on demand, remove 
them from the 'include' list
                 // above, and group them into bundles here.
-                'out-of-service': ['components/out-of-service/out-of-service'],
-                'sunburst': ['components/visualizers/sunburst/sunburst'],
-                'hierarchy': ['components/visualizers/hierarchy/hierarchy'],
-                'stacked-bars': 
['components/visualizers/stacked-bars/stacked-bars'],
-                'dygraphs-timeseries': 
['components/visualizers/dygraphs-timeseries/dygraphs-timeseries'],
+                'out-of-service':       
['components/layouts/out-of-service/out-of-service'],
+                'sunburst':             
['components/visualizers/sunburst/sunburst'],
+                'hierarchy':            
['components/visualizers/hierarchy/hierarchy'],
+                'stacked-bars':         
['components/visualizers/stacked-bars/stacked-bars'],
+                'dygraphs-timeseries':  
['components/visualizers/dygraphs-timeseries/dygraphs-timeseries'],
+                'table-timeseries':     
['components/visualizers/table-timeseries/table-timeseries'],
                 'filter-timeseries': [
                     
'components/visualizers/filter-timeseries/filter-timeseries',
                     
'components/visualizers/dygraphs-timeseries/dygraphs-timeseries'
                 ],
-                'table-timeseries': 
['components/visualizers/table-timeseries/table-timeseries'],
             }
         }
     };
-}
\ No newline at end of file
+}
diff --git a/src/app/ko-extensions/async-observables.js 
b/src/lib/knockout-utils/async-observables.js
similarity index 94%
rename from src/app/ko-extensions/async-observables.js
rename to src/lib/knockout-utils/async-observables.js
index e8baa01..646bca3 100644
--- a/src/app/ko-extensions/async-observables.js
+++ b/src/lib/knockout-utils/async-observables.js
@@ -5,7 +5,7 @@
     'use strict';
 
     var ko = require('knockout'),
-        TimeseriesData = require('converters.timeseries');
+        TimeseriesData = require('models.timeseries');
 
     return {
 
diff --git a/src/app/ko-extensions/datepicker-binding.js 
b/src/lib/knockout-utils/bindings/datepicker.js
similarity index 100%
rename from src/app/ko-extensions/datepicker-binding.js
rename to src/lib/knockout-utils/bindings/datepicker.js
diff --git a/src/lib/knockout-utils/bindings/dropdown.js 
b/src/lib/knockout-utils/bindings/dropdown.js
new file mode 100644
index 0000000..ebbf2b3
--- /dev/null
+++ b/src/lib/knockout-utils/bindings/dropdown.js
@@ -0,0 +1,17 @@
+'use strict';
+define(function (require) {
+    var ko = require('knockout');
+
+    require('semantic2-dropdown');
+
+    ko.bindingHandlers.dropdown = {
+        init: function (element, valueAccessor) {
+
+            $(element).dropdown(ko.unwrap(valueAccessor()));
+
+            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
+                $(element).dropdown('destroy');
+            });
+        }
+    };
+});
diff --git a/src/lib/knockout-utils/bindings/popup.js 
b/src/lib/knockout-utils/bindings/popup.js
new file mode 100644
index 0000000..34cd4e2
--- /dev/null
+++ b/src/lib/knockout-utils/bindings/popup.js
@@ -0,0 +1,18 @@
+'use strict';
+define(function (require) {
+    var ko = require('knockout');
+
+    require('semantic2-popup');
+    require('semantic2-transition');
+
+    ko.bindingHandlers.popup = {
+        init: function (element, valueAccessor) {
+
+            $(element).popup(ko.unwrap(valueAccessor()));
+
+            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
+                $(element).popup('destroy');
+            });
+        }
+    };
+});
diff --git a/src/lib/knockout-extensions/knockout-table.js 
b/src/lib/knockout-utils/bindings/table.js
similarity index 100%
rename from src/lib/knockout-extensions/knockout-table.js
rename to src/lib/knockout-utils/bindings/table.js
diff --git a/src/app/ko-extensions/global-bindings.js 
b/src/lib/knockout-utils/bindings/toggle.js
similarity index 80%
rename from src/app/ko-extensions/global-bindings.js
rename to src/lib/knockout-utils/bindings/toggle.js
index e3db080..85a12e4 100644
--- a/src/app/ko-extensions/global-bindings.js
+++ b/src/lib/knockout-utils/bindings/toggle.js
@@ -2,11 +2,6 @@
 define(function (require) {
     var ko = require('knockout');
 
-    require('jquery');
-    require('semantic2-popup');
-    require('semantic2-dropdown');
-    require('semantic2-transition');
-
     /**
      * Convention-based binding that expects html like this:
      *   <div data-bind="toggle: 'observableName'">... style this as the 
trigger that toggles ...</div>
@@ -87,26 +82,4 @@
             t.observable(false);
         });
     });
-
-    ko.bindingHandlers.popup = {
-        init: function (element, valueAccessor) {
-
-            $(element).popup(ko.unwrap(valueAccessor()));
-
-            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
-                $(element).popup('destroy');
-            });
-        }
-    };
-
-    ko.bindingHandlers.dropdown = {
-        init: function (element, valueAccessor) {
-
-            $(element).dropdown(ko.unwrap(valueAccessor()));
-
-            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
-                $(element).dropdown('destroy');
-            });
-        }
-    };
 });
diff --git a/src/app/ko-extensions/common-viewmodels/copy-params.js 
b/src/lib/knockout-utils/viewmodels/copy-params.js
similarity index 100%
rename from src/app/ko-extensions/common-viewmodels/copy-params.js
rename to src/lib/knockout-utils/viewmodels/copy-params.js
diff --git a/src/app/ko-extensions/common-viewmodels/single-select.js 
b/src/lib/knockout-utils/viewmodels/single-select.js
similarity index 100%
rename from src/app/ko-extensions/common-viewmodels/single-select.js
rename to src/lib/knockout-utils/viewmodels/single-select.js
diff --git a/src/app/utils/arrays.js b/src/lib/utils/arrays.js
similarity index 100%
rename from src/app/utils/arrays.js
rename to src/lib/utils/arrays.js
diff --git a/src/app/utils/colors.js b/src/lib/utils/colors.js
similarity index 100%
rename from src/app/utils/colors.js
rename to src/lib/utils/colors.js
diff --git a/src/app/utils/datetime.js b/src/lib/utils/datetime.js
similarity index 100%
rename from src/app/utils/datetime.js
rename to src/lib/utils/datetime.js
diff --git a/src/app/utils/elements.js b/src/lib/utils/elements.js
similarity index 100%
rename from src/app/utils/elements.js
rename to src/lib/utils/elements.js
diff --git a/src/app/utils/numbers.js b/src/lib/utils/numbers.js
similarity index 100%
rename from src/app/utils/numbers.js
rename to src/lib/utils/numbers.js
diff --git a/src/app/utils/strings.js b/src/lib/utils/strings.js
similarity index 100%
rename from src/app/utils/strings.js
rename to src/lib/utils/strings.js
diff --git a/test/SpecRunner.browser.js b/test/SpecRunner.browser.js
index 6837480..3fe60db 100644
--- a/test/SpecRunner.browser.js
+++ b/test/SpecRunner.browser.js
@@ -2,11 +2,11 @@
 (function () {
     // Reference your test modules here
     var testModules = [
-        'components/metrics-by-project-layout',
+        'components/layouts/metrics-by-project',
         'components/visualizers/wikimetrics',
-        'components/vega-timeseries',
-        'components/project-selector',
-        'app/data-converters',
+        'components/visualizers/vega-timeseries',
+        'components/selectors/project',
+        'app/converters',
         'app/apis',
         'lib/state-manager'
     ];
diff --git a/test/app/data-converters.js b/test/app/data-converters.js
index 916a36c..2121a21 100644
--- a/test/app/data-converters.js
+++ b/test/app/data-converters.js
@@ -1,14 +1,12 @@
 define(function (require) {
     'use strict';
 
-    var factory = require('dataConverterFactory'),
-        // TODO: karma or something has problems with buildHierarchy = 
factory.getDataConverter('hierarchy');
-        buildHierarchy = require('converters.hierarchy-data');
+    var converterFinder = require('finders.converter');
 
 
     describe('sv converter', function () {
-        var converterCSV = factory.getDataConverter('csv');
-        var converterTSV = factory.getDataConverter('tsv');
+        var converterCSV = converterFinder('csv');
+        var converterTSV = converterFinder('tsv');
 
 
         it('should convert comma separated', function () {
@@ -128,7 +126,7 @@
 
 
     describe('wikimetrics-timeseries converter', function () {
-        var converterWikimetrics = factory.getDataConverter('json');
+        var converterWikimetrics = converterFinder('json');
         // pass the configuration to the converter
         var options = {
             defaultSubmetrics: {
@@ -174,7 +172,8 @@
     });
 
     describe('hierarchy-data converter', function () {
-        var converterCSV = factory.getDataConverter('csv');
+        var converterCSV = converterFinder('csv'),
+            buildHierarchy = converterFinder('hierarchy');
 
         it('should convert tsv to hierarchy', function () {
             var csvData = (
@@ -231,7 +230,7 @@
 
         it('happy case ', function () {
 
-            var converterAQSApi = factory.getDataConverter(
+            var converterAQSApi = converterFinder(
                 'aqs-api-response', 'views');
 
             var sample = {
diff --git a/test/app/timeseries-data.js b/test/app/timeseries-data.js
index b17c3b2..3b05ae8 100644
--- a/test/app/timeseries-data.js
+++ b/test/app/timeseries-data.js
@@ -1,7 +1,7 @@
 define(function (require) {
     'use strict';
 
-    var TimeseriesData = require('converters.timeseries'),
+    var TimeseriesData = require('models.timeseries'),
         _ = require('lodash');
 
     describe('TimeseriesData class', function () {
diff --git a/test/components/annotation-list.js 
b/test/components/annotation-list.js
index 4e4c2ed..4f6ac82 100644
--- a/test/components/annotation-list.js
+++ b/test/components/annotation-list.js
@@ -1,7 +1,9 @@
+'use strict';
 define(function (require) {
-    var component = require('components/annotation-list/annotation-list'),
-        annotationsApi = require('apis.annotations'),
-        AnnotationList = component.viewModel;
+    var component = 
require('components/visualizers/annotation-list/annotation-list'),
+        annotationsApi = require('apis.annotations');
+
+    var AnnotationList = component.viewModel;
 
     describe('AnnotationList view model', function () {
 
@@ -27,6 +29,7 @@
             });
 
             var instance = new AnnotationList(params);
+            expect(instance instanceof Object).toBe(true);
         });
 
         it('should render annotations returned by annotations api', function 
() {
diff --git a/test/components/breakdown-toggle.js 
b/test/components/breakdown-toggle.js
index ce1a268..f42fa02 100644
--- a/test/components/breakdown-toggle.js
+++ b/test/components/breakdown-toggle.js
@@ -1,4 +1,8 @@
-define(['components/breakdown-toggle/breakdown-toggle', 'knockout'], function 
(component, ko) {
+'use strict';
+define(function (require) {
+    var component = 
require('components/controls/breakdown-toggle/breakdown-toggle'),
+        ko = require('knockout');
+
     var BreakdownToggle = component.viewModel;
 
     describe('BreakdownToggle view model', function () {
@@ -24,4 +28,4 @@
 
         });
     });
-});
\ No newline at end of file
+});
diff --git a/test/components/metric-selector.js 
b/test/components/metric-selector.js
index 6acd889..e65d06e 100644
--- a/test/components/metric-selector.js
+++ b/test/components/metric-selector.js
@@ -1,6 +1,6 @@
 'use strict';
 define(function(require) {
-    var component = require('components/metric-selector/metric-selector'),
+    var component = require('components/selectors/metric/metric'),
         ko = require('knockout');
 
     var MetricSelector = component.viewModel;
diff --git a/test/components/project-selector.js 
b/test/components/project-selector.js
index 3953748..80db05b 100644
--- a/test/components/project-selector.js
+++ b/test/components/project-selector.js
@@ -1,5 +1,7 @@
-define(['components/project-selector/project-selector', 'knockout'], function 
(component, ko) {
-    'use strict';
+'use strict';
+define(function (require) {
+    var component = require('components/selectors/project/project'),
+        ko = require('knockout');
 
     var ProjectSelector = component.viewModel;
 
diff --git a/test/components/tabs-layout.js b/test/components/tabs-layout.js
index 20cca71..069b119 100644
--- a/test/components/tabs-layout.js
+++ b/test/components/tabs-layout.js
@@ -1,6 +1,6 @@
 'use strict';
 define(function(require) {
-    var component = require('components/tabs-layout/tabs-layout'),
+    var component = require('components/layouts/tabs/tabs'),
         configApi = require('apis.config');
 
     var TabsLayout = component.viewModel;
diff --git a/test/components/vega-timeseries.js 
b/test/components/vega-timeseries.js
index 022db70..04e6bd8 100644
--- a/test/components/vega-timeseries.js
+++ b/test/components/vega-timeseries.js
@@ -1,4 +1,8 @@
-define(['components/visualizers/vega-timeseries/vega-timeseries', 'knockout'], 
function(component, ko) {
+'use strict';
+define(function (require) {
+    var component = 
require('components/visualizers/vega-timeseries/vega-timeseries'),
+        ko = require('knockout');
+
     var VegaTimeseries = component.viewModel;
 
     describe('VegaTimeseries view model', function() {
diff --git a/test/components/wikimetrics-layout.js 
b/test/components/wikimetrics-layout.js
index 2f3a52e..e41d528 100644
--- a/test/components/wikimetrics-layout.js
+++ b/test/components/wikimetrics-layout.js
@@ -1,7 +1,7 @@
 'use strict';
 define(function(require) {
 
-    var component = 
require('components/metrics-by-project-layout/metrics-by-project-layout'),
+    var component = 
require('components/layouts/metrics-by-project/metrics-by-project'),
         wikimetricsApi = require('apis.wikimetrics');
 
     var MetricsByProjectLayout = component.viewModel;
diff --git a/test/components/wikimetrics-visualizer.js 
b/test/components/wikimetrics-visualizer.js
index 238e567..260a385 100644
--- a/test/components/wikimetrics-visualizer.js
+++ b/test/components/wikimetrics-visualizer.js
@@ -1,11 +1,11 @@
+'use strict';
 define(function (require) {
-    'use strict';
 
     var component = require('components/visualizers/wikimetrics/wikimetrics'),
         $ = require('jquery'),
         api = require('apis.wikimetrics'),
         ko = require('knockout'),
-        TimeseriesData = require('converters.timeseries');
+        TimeseriesData = require('models.timeseries');
 
     var WikimetricsVisualizer = component.viewModel,
         selectedMetric,
@@ -108,4 +108,4 @@
             ]);
         });
     });
-});
\ No newline at end of file
+});

-- 
To view, visit https://gerrit.wikimedia.org/r/316834
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Id56c4d1dd0bf7787a493d39759ac44878d4610b1
Gerrit-PatchSet: 1
Gerrit-Project: analytics/dashiki
Gerrit-Branch: master
Gerrit-Owner: Milimetric <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to