Ejegg has submitted this change and it was merged.

Change subject: Data retrieval/superclassing.
......................................................................


Data retrieval/superclassing.

Change-Id: I8c685154bf3da6659996a5104dfd30115f9e30d1
---
M src/app/require.config.js
A src/app/widgetBase.js
M src/components/app-content/app-content.js
M src/components/boards/generic-board/generic-board.html
M src/components/nav-bar/nav-bar.html
M src/components/widgets/amt-per-second-chart/amt-per-second-chart.html
M src/components/widgets/amt-per-second-chart/amt-per-second-chart.js
M src/components/widgets/distance-to-goal-chart/distance-to-goal-chart.js
M src/components/widgets/fraud-gauge/fraud-gauge.html
M src/components/widgets/fraud-gauge/fraud-gauge.js
M src/components/widgets/x-by-y/x-by-y.html
M src/components/widgets/x-by-y/x-by-y.js
M src/css/style.css
M widgets/x-by-y.js
14 files changed, 771 insertions(+), 235 deletions(-)

Approvals:
  Ejegg: Looks good to me, approved



diff --git a/src/app/require.config.js b/src/app/require.config.js
index 68a15b5..4b15b77 100644
--- a/src/app/require.config.js
+++ b/src/app/require.config.js
@@ -24,7 +24,8 @@
         'select2':              'bower_modules/select2//select2',
         'c3':                   'bower_modules/c3/c3',
         'numeraljs':            'bower_modules/numeraljs/numeral',
-        'decemberData':         'bower_modules/fakeData/decemberData'
+        'decemberData':         'bower_modules/fakeData/decemberData',
+        'WidgetBase':           'app/widgetBase'
     },
     shim: {
         'bootstrap': {
diff --git a/src/app/widgetBase.js b/src/app/widgetBase.js
new file mode 100644
index 0000000..f1dd56b
--- /dev/null
+++ b/src/app/widgetBase.js
@@ -0,0 +1,215 @@
+define([
+       'jquery',
+       'knockout',
+       'momentjs'
+], function( $, ko, moment ){
+
+       function WidgetBase( params ){
+
+               var self = this;
+
+               self.retrievedResults   = ko.observable();
+               self.queryStringSQL     = ko.observable('This widget hasn\'t 
been set up yet!');
+               self.config                     = params.configuration;
+               self.instanceID                 = params.widgetInstance;
+               self.widgetCode                 = params.widgetCode;
+               self.preDataLoading             = ko.observable(true);
+               self.dataLoading                = ko.observable(!!self.config);
+        self.chartSaved                = ko.observable(!!self.config);
+        self.optionStateChanged = ko.observable(false);
+        self.chartWidth                = ko.observable('900');
+        self.chartHeight               = ko.observable('550');
+        self.chartLoaded               = ko.observable(false);
+
+               self.getChartData = function( qs ){
+            self.dataLoading(true);
+                       return $.ajax({
+                               url: '/data/' + self.widgetCode + '?' + ( qs 
).replace( /\+/g, '%20' ),
+                               success: function ( dataget ) {
+                           self.retrievedResults( dataget.results );
+                           self.queryStringSQL( dataget.sqlQuery );
+                           }
+                       });
+               };
+
+               self.saveWidgetConfig = function(){
+
+                       if( self.instanceID ){
+                               $.ajax({
+                           method: 'PUT',
+                           url: '/widget-instance/' + self.instanceID,
+                           contentType: 'application/json; charset=UTF-8',
+                           data: JSON.stringify({
+                               configuration: self.config,
+                               isShared: false
+                           }),
+                           success: function( data ) {
+                                   self.chartSaved(true);
+                           }
+                       });
+                       } else {
+                               $.ajax({
+                           method: 'POST',
+                           url: '/widget-instance/',
+                           contentType: 'application/json; charset=UTF-8',
+                           data: JSON.stringify({
+                               configuration: self.config,
+                               isShared: false
+                           }),
+                           success: function( data ) {
+                               self.instanceID = data.id;
+                                   self.chartSaved(true);
+                           }
+                       });
+                       }
+
+               };
+
+               self.processData = function(rawdata, timescale){
+
+                       var dailyDataArray                              = 
['Daily Total'],
+                               dailyCountArray                         = 
['Daily Count'],
+                               secondsByHourDonationData       = ['Donations 
Per Second'],
+                               dayObj                                          
= {}, returnObj;
+
+                       switch(timescale){
+                               case 'Year':
+                               case 'Month':
+                                       var monthlyDataArray = ['Monthly 
Total'],
+                                       monthlyCountArray = ['Monthly Count'],
+                                       months = rawdata;
+
+                                       $.each(months, function(i, el){
+                                               
monthlyDataArray.push(el.usd_total);
+                                               
monthlyCountArray.push(el.donations);
+                                       });
+
+                                       returnObj = {
+                                                                               
timescale: timescale,
+                                                                               
monthlyDataArray: monthlyDataArray,
+                                                                               
monthlyCountArray: monthlyCountArray
+                                       };
+                                       return returnObj;
+                               case 'Day':
+                               case 'Hour':
+                                       for (var d = 1; d < 32; d++) {
+                                               dailyDataArray[d] = 0;
+                                               dailyCountArray[d] = 0;
+                                               if (!dayObj[d]) {
+                                                       dayObj[d] = new 
Array(25);
+                                                       dayObj[d][0] = 'Hourly 
Totals';
+                                                       for (var h = 0; h < 24; 
h++) {
+                                                               dayObj[d][h + 
1] = { total: 0, count: 0 };
+                                                               
secondsByHourDonationData[(d - 1) * 24 + h + 1] = 0;
+                                                       }
+                                               }
+                                       }
+
+                                       var dataCount = rawdata.length;
+                                       for (var i = 0; i < dataCount; i++ ) {
+
+                                               var el = rawdata[i],
+                                                               day = el.Day,
+                                                               hour = el.hour,
+                                                               total = 
el.usd_total,
+                                                               runningTotal = 
0;
+
+                                               if(!hour){
+                                                       dayObj[day+1] = { 
total: total, count: el.donations };
+                                               } else {
+                                                       dayObj[day][hour + 1] = 
{ total: total, count: el.donations };
+                                               }
+
+                                               secondsByHourDonationData[(day 
- 1) * 24 + hour + 1] = el.usd_per_second;
+                                               runningTotal += total;
+                                               dailyDataArray[day] += total;
+                                               dailyCountArray[day] += 
el.donations;
+                                       }
+
+                                       returnObj = {
+                                                                               
timescale: timescale,
+                                                                               
dailyDataArray: dailyDataArray,
+                                                                               
dailyCountArray: dailyCountArray,
+                                                                               
secondsByHourDonationData: secondsByHourDonationData,
+                                                                               
dayObj: dayObj
+                                       };
+
+                                       return returnObj;
+                       }
+
+               };
+
+               self.convertToQuery = function( userChoices ){
+
+               var timeBreakout = 'group=' + userChoices.timeBreakout;
+                       //groupStr = timeBreakout + '&group=' + 
userChoices.xSlice;
+
+               // if( userChoices.additionalFilters.length > 0 ){
+
+               //     var filterStr = '$filter=', filterObj = {}, 
haveMultipleSubfilters = [];
+
+               //     $.each( userChoices.additionalFilters, function( el, 
subfilter ){
+               //         var filter = subfilter.substr(0, subfilter.indexOf(' 
'));
+               //         if(!filterObj[ filter ]){
+               //           filterObj[ filter ] = subfilter;
+               //         } else {
+               //           filterObj[ filter ] += ' or ' + subfilter;
+               //           haveMultipleSubfilters.push( filter );
+               //         }
+               //     });
+
+               //     $.each( filterObj, function( el, s ){
+               //         if( haveMultipleSubfilters.indexOf( el ) > -1){
+               //           filterStr += '(' + filterObj[ el ] + ')';
+               //         } else {
+               //           filterStr += filterObj[ el ];
+               //         }
+               //         filterStr += ' and ';
+               //     });
+
+               //     if( filterStr !== '$filter=' ){
+               //         return groupStr + '&' + ( filterStr.slice(0, -5) );
+               //     } else {
+               //         return groupStr;
+               //     }
+               // } else {
+               //     return groupStr;
+               // }
+               return timeBreakout;
+           };
+
+               // Generate chart label arrays for time increment types
+               self.chartLabels = function(type){
+                       var chartLabels;
+                       switch(type){
+                   case 'Year':
+                       chartLabels = ['Year'];
+                       break;
+                   case 'Month':
+                       chartLabels = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 
'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+                       break;
+                   case 'Day':
+                       chartLabels = [ '01', '02', '03', '04', '05', '06', 
'07', '08', '09', '10',
+                                       '11', '12', '13', '14', '15', '16', 
'17', '18', '19', '20',
+                                       '21', '22', '23', '24', '25', '26', 
'27', '28', '29', '30', '31'];
+                       break;
+                   case 'Hour':
+                       chartLabels = [ '00:00', '01:00', '02:00', '03:00', 
'04:00', '05:00', '06:00', '07:00', '08:00', '09:00', '10:00',
+                                       '11:00', '12:00', '13:00', '14:00', 
'15:00', '16:00', '17:00', '18:00', '19:00', '20:00', '21:00',
+                                       '22:00', '23:00'];
+                       break;
+               }
+                       return chartLabels;
+               };
+
+               self.logStateChange = function(n){
+            self.optionStateChanged(n);
+            self.chartSaved(false);
+        };
+
+               return(this);
+       }
+
+       return( WidgetBase );
+
+});
diff --git a/src/components/app-content/app-content.js 
b/src/components/app-content/app-content.js
index 894bd67..dce8604 100644
--- a/src/components/app-content/app-content.js
+++ b/src/components/app-content/app-content.js
@@ -42,6 +42,8 @@
             var pages = ['Library', 'Profile', 'Home'], view = data.target.id;
             if( pages.indexOf(data.target.id) > -1 ){
                 self.displayPage(view);
+            } else if( typeof view !== 'number' ) {
+                self.displayPage($.trim($(data.target).text()));
             } else {
                 $.get('board/' + view, function( bdata ){
                     console.log('get board #', data.target.id);
diff --git a/src/components/boards/generic-board/generic-board.html 
b/src/components/boards/generic-board/generic-board.html
index 9408094..dcdcc9e 100644
--- a/src/components/boards/generic-board/generic-board.html
+++ b/src/components/boards/generic-board/generic-board.html
@@ -20,7 +20,7 @@
     </span>
 
     <div class="row" data-bind="foreach: displayedBoard().widgets">
-        <div data-bind="component: { name: widgetCode, params: { title: 
displayName, description: description, configuration: configuration } }"><span 
data-bind="text: display_name"></span></div>
+        <div data-bind="component: { name: widgetCode, params: { title: 
displayName, description: description, configuration: configuration, 
widgetInstance: id, widgetCode: widgetCode } }"><span data-bind="text: 
display_name"></span></div>
     </div>
 
 </div>
\ No newline at end of file
diff --git a/src/components/nav-bar/nav-bar.html 
b/src/components/nav-bar/nav-bar.html
index 7f356de..b1fb42a 100644
--- a/src/components/nav-bar/nav-bar.html
+++ b/src/components/nav-bar/nav-bar.html
@@ -43,7 +43,7 @@
                                                </ul>
                                        </div>
 
-                                       <div class="mainNavButton" id="Library" 
data-bind="click: $parent.setDisplayPage"><i class="fa fa-plug"></i><span> 
Library</span>
+                                       <div class="mainNavButton" id="Library" 
data-bind="click: $parent.setDisplayPage"><i class="fa fa-plug"></i><span 
id="LibraryLink"> Library</span>
                                        </div>
                                </span>
 
diff --git 
a/src/components/widgets/amt-per-second-chart/amt-per-second-chart.html 
b/src/components/widgets/amt-per-second-chart/amt-per-second-chart.html
index 51150ab..71185d3 100644
--- a/src/components/widgets/amt-per-second-chart/amt-per-second-chart.html
+++ b/src/components/widgets/amt-per-second-chart/amt-per-second-chart.html
@@ -1,7 +1,7 @@
 <div class="col-md-6 lastWidget">
        <div class="panel panel-purple">
                <div class="panel-heading">
-                       <p data-bind="text: title">USD/Second Required 
(Average)</p>
+                       <p>USD/Second Required (Average)</p>
                </div>
                <div class="panel-body">
                        <div class="col-md-12">
diff --git 
a/src/components/widgets/amt-per-second-chart/amt-per-second-chart.js 
b/src/components/widgets/amt-per-second-chart/amt-per-second-chart.js
index 1647056..bf4824b 100644
--- a/src/components/widgets/amt-per-second-chart/amt-per-second-chart.js
+++ b/src/components/widgets/amt-per-second-chart/amt-per-second-chart.js
@@ -2,39 +2,82 @@
     'knockout',
     'text!components/widgets/amt-per-second-chart/amt-per-second-chart.html',
     'c3',
-    'numeraljs'
-], function( ko, template, c3, numeral ){
+    'numeraljs',
+    'momentjs'
+], function( ko, template, c3, numeral, moment ){
 
 
     function AmtPerSecondChartViewModel( params ){
 
         var self = this;
 
-        self.title = ko.observable(params.title);
+        //TODO: make dayObj (and other params) come from data
+        self.dayObj = [];
+
+        self.loadData = function ( decemberData, timestamp ) {
+                       var runningTotal = 0,
+                               currentDate = new Date(),
+                               timeFormat = 'dddd, MMMM Do YYYY, h:mm:ss a';
+
+                       currentDate.setTime( timestamp );
+                       self.displayDate( moment( currentDate ).format( 
timeFormat ) );
+                       self.lastDataPoint.day = currentDate.getUTCDate();
+                       self.lastDataPoint.hour = currentDate.getUTCHours();
+
+                       for (var d = 1; d < 32; d++) {
+                               self.dailyDataArray[d] = 0;
+                               self.dailyCountArray[d] = 0;
+                               if (!self.dayObj[d]) {
+                                       self.dayObj[d] = new Array(25);
+                                       self.dayObj[d][0] = 'Hourly Totals';
+                                       for (var h = 0; h < 24; h++) {
+                                               self.dayObj[d][h + 1] = { 
total: 0, count: 0 };
+                                               
self.secondsByHourDonationData[(d - 1) * 24 + h + 1] = 0;
+                                       }
+                               }
+                       }
+
+                       var dataCount = decemberData.length;
+                       for (var i = 0; i < dataCount; i++ ) {
+
+                               var el = decemberData[i],
+                                               day = el.day,
+                                               hour = el.hour,
+                                               total = el.usd_total;
+                               self.dayObj[day][hour + 1] = { total: total, 
count: el.donations };
+
+                               self.secondsByHourDonationData[(day - 1) * 24 + 
hour + 1] = el.usd_per_second;
+                               runningTotal += total;
+                               self.dailyDataArray[day] += total;
+                               self.dailyCountArray[day] += el.donations;
+                       }
+
+                       self.raised(runningTotal);
+               };
 
                self.makeChart = function() {
-                       if ( params.dayObj.length < 2 ) {
+                       if ( self.dayObj.length < 2 ) {
                                return;
                        }
-                       var numPoints = ( params.lastDataPoint.day - 1 ) * 24 + 
params.lastDataPoint.hour + 1,
+                       var numPoints = ( self.lastDataPoint.day - 1 ) * 24 + 
self.lastDataPoint.hour + 1,
                                xs = new Array( numPoints + 2 ), // label, data 
to date, final point
                                index = 0,
-                               remainingNeeded = params.goal();
+                               remainingNeeded = self.goal();
                        xs[0] = 'x1';
 
                        self.needPerSecond =  new Array( numPoints + 2 );
                        self.needPerSecond[0] = 'Needed Per Second';
 
                        // secondsByHourDonationData already has a label in [0]
-                       self.gotPerSecond = 
params.secondsByHourDonationData.slice( 0, numPoints + 1 );
+                       self.gotPerSecond = 
self.secondsByHourDonationData.slice( 0, numPoints + 1 );
 
-                       for( var d = 1; d < params.dayObj.length; d++ ) {
+                       for( var d = 1; d < self.dayObj.length; d++ ) {
                                for ( var h = 0; h < 24; h++ ) {
                                        index = ( d - 1 ) * 24 + h + 1;
                                        if ( index > numPoints + 1 ) {
                                                break;
                                        }
-                                       remainingNeeded = remainingNeeded - 
params.dayObj[d][h + 1].total;
+                                       remainingNeeded = remainingNeeded - 
self.dayObj[d][h + 1].total;
                                        if ( remainingNeeded < 0 ) {
                                                remainingNeeded = 0;
                                        }
@@ -98,7 +141,6 @@
                                }
                        } );
                };
-               params.dataChanged.subscribe(self.makeChart);
                self.makeChart();
     }
 
diff --git 
a/src/components/widgets/distance-to-goal-chart/distance-to-goal-chart.js 
b/src/components/widgets/distance-to-goal-chart/distance-to-goal-chart.js
index 68cc542..bd52553 100644
--- a/src/components/widgets/distance-to-goal-chart/distance-to-goal-chart.js
+++ b/src/components/widgets/distance-to-goal-chart/distance-to-goal-chart.js
@@ -1,25 +1,79 @@
 define( [
     'knockout',
     
'text!components/widgets/distance-to-goal-chart/distance-to-goal-chart.html',
-    'c3'
-], function( ko, template, c3 ){
+    'c3',
+    'momentjs'
+], function( ko, template, c3, moment ){
 
 
     function DistanceToGoalChartViewModel( params ){
 
         var self = this;
+        self.goal = ko.observable('20,000,000');
+        self.dailyDataArray = [ 'Daily Total' ];
+
+        self.loadData = function ( decemberData, timestamp ) {
+                       var runningTotal = 0,
+                               currentDate = new Date(),
+                       timeFormat = 'dddd, MMMM Do YYYY, h:mm:ss a';
+                       currentDate.setTime( timestamp );
+                       self.displayDate( moment( currentDate ).format( 
timeFormat ) );
+                       self.lastDataPoint.day = currentDate.getUTCDate();
+                       self.lastDataPoint.hour = currentDate.getUTCHours();
+
+                       for (var d = 1; d < 32; d++) {
+                               self.dailyDataArray[d] = 0;
+                               self.dailyCountArray[d] = 0;
+                               if (!self.dayObj[d]) {
+                                       self.dayObj[d] = new Array(25);
+                                       self.dayObj[d][0] = 'Hourly Totals';
+                                       for (var h = 0; h < 24; h++) {
+                                               self.dayObj[d][h + 1] = { 
total: 0, count: 0 };
+                                               
self.secondsByHourDonationData[(d - 1) * 24 + h + 1] = 0;
+                                       }
+                               }
+                       }
+
+                       var dataCount = decemberData.length;
+                       for (var i = 0; i < dataCount; i++ ) {
+
+                               var el = decemberData[i],
+                                               day = el.day,
+                                               hour = el.hour,
+                                               total = el.usd_total;
+                               self.dayObj[day][hour + 1] = { total: total, 
count: el.donations };
+
+                               self.secondsByHourDonationData[(day - 1) * 24 + 
hour + 1] = el.usd_per_second;
+                               runningTotal += total;
+                               self.dailyDataArray[day] += total;
+                               self.dailyCountArray[day] += el.donations;
+                       }
+
+                       //self.makeCharts();
+
+                       self.raised(runningTotal);
+               };
+
+        // $.get('data/big-english', function(dtgData){
+
+        //     console.log('dtgData', dtgData);
+
+        //     self.dailyDataArray;
+
+
+        // });
 
         self.title = ko.observable(params.title);
                self.makeCharts = function() {
-                       if ( params.dailyDataArray.length < 2 ) {
+                       if ( self.dailyDataArray.length < 2 ) {
                                return;
                        }
-                       self.goal = ko.observable(params.goal);
+                       self.goal = ko.observable(self.goal);
 
-                       self.updatedGoal = params.goal();
+                       self.updatedGoal = self.goal();
                        self.neededArray = ['Needed'];
-                       for(var d = 1; d < params.dailyDataArray.length; d++) {
-                               self.updatedGoal = self.updatedGoal - 
params.dailyDataArray[d];
+                       for(var d = 1; d < self.dailyDataArray.length; d++) {
+                               self.updatedGoal = self.updatedGoal - 
self.dailyDataArray[d];
                                self.neededArray[d] = self.updatedGoal >= 0 ? 
self.updatedGoal : 0;
                        }
 
@@ -60,10 +114,8 @@
                                }
                        });
                };
-               params.dataChanged.subscribe(function() {
-                       self.makeCharts();
-               });
-               self.makeCharts();
+
+               //self.makeCharts();
        }
     return { viewModel: DistanceToGoalChartViewModel, template: template };
 
diff --git a/src/components/widgets/fraud-gauge/fraud-gauge.html 
b/src/components/widgets/fraud-gauge/fraud-gauge.html
index ee4c49a..e3b9fed 100644
--- a/src/components/widgets/fraud-gauge/fraud-gauge.html
+++ b/src/components/widgets/fraud-gauge/fraud-gauge.html
@@ -1,4 +1,5 @@
 <div class="row">
+    <div data-bind="attr: {class: columnSize}">
     <div class="panel panel-purple" id="fraudGaugeWidget">
         <div class="panel-heading">
             <span data-bind='text: title'></span>
@@ -10,7 +11,7 @@
         <div class="panel-body">
 
             <div class="row">
-                <div class="col-md-12 gauge">
+                <div class="gauge">
 
                     <div class="row">
                         <h3 class="text-center" data-bind="visible: 
gaugeIsSetUp(), text: selectedTimePeriod"></h3>
@@ -126,6 +127,6 @@
             </div>
         </div>
 
-
+    </div>
     </div>
 </div>
diff --git a/src/components/widgets/fraud-gauge/fraud-gauge.js 
b/src/components/widgets/fraud-gauge/fraud-gauge.js
index c3b7824..8fca0bd 100644
--- a/src/components/widgets/fraud-gauge/fraud-gauge.js
+++ b/src/components/widgets/fraud-gauge/fraud-gauge.js
@@ -33,6 +33,8 @@
     var self = this;
     self.filters = ko.observableArray();
     self.title = ko.observable(params.title);
+    console.log('fraud widget params: ', params);
+    self.columnSize = 'col-md-' + params.configuration.width + ' fraudGauge';
 
     $.get( 'metadata/fraud-gauge', function(reqData){
       self.data = reqData;
diff --git a/src/components/widgets/x-by-y/x-by-y.html 
b/src/components/widgets/x-by-y/x-by-y.html
index 0289139..3229239 100644
--- a/src/components/widgets/x-by-y/x-by-y.html
+++ b/src/components/widgets/x-by-y/x-by-y.html
@@ -1,16 +1,22 @@
 <div class="row">
+
+       <span data-bind="if: preDataLoading">
+       <div class="loadingWidget">
+               <img src="/images/catloader.gif" />
+               <h3 class="text-center">Loading.....</h3>
+        </div>
+    </span>
+
        <div class="panel panel-purple">
         <div class="panel-heading">
                <div class="btn-group btn-group-xs pull-right">
                        <button type="button" class="btn btn-default 
dropdown-toggle" data-toggle="modal" data-target="#XYsqlModal"><i class="fa 
fa-database"></i></button>
-                       <button type="button" id="savedCharts" class="btn 
btn-default dropdown-toggle" data-toggle="dropdown" 
data-target="#XYsavedChart"><i class="fa fa-area-chart"></i> Saved 
charts...</button>
+                       <!-- <button type="button" id="savedCharts" class="btn 
btn-default dropdown-toggle" data-toggle="dropdown" 
data-target="#XYsavedChart"><i class="fa fa-area-chart"></i> Saved 
charts...</button>
                        <ul class="dropdown-menu" role="menu" 
aria-labelledby="savedCharts" id="savedChartsMenu" data-bind="foreach: 
presetTitles, style: { width: '300px'}">
                                        <li data-bind="text: $data"></li>
-                               </ul>
+                               </ul> -->
                </div>
         </div>
-
-
 
         <div class="panel-body">
             <div class="row-fluid">
@@ -24,19 +30,19 @@
                                                        <select 
data-bind="options: ySlices, value: showSlice"></select>
                                                        <hr>
                                                    </div>
-                                                   <div class="row-fluid">
+                                                   <!-- <div class="row-fluid">
                                                        <h4>By:</h4>
                                                        <select 
data-bind="options: xSlices, value: bySlice"></select>
                                                        <hr>
-                                               </div>
+                                               </div> -->
 
                                                <div class="row-fluid">
                                                                <h4>Starting 
time range:</h4><br>
-                                                                       <select 
id="startingTimeRange" placeholder="Range..." data-bind="options: 
timeChoices"></select>
-                                                                       <hr>
+                                                                       <select 
id="startingTimeRange" placeholder="Range..." data-bind="options: timeChoices, 
value: timeChoice"></select>
+                                                                       <!-- 
<hr> -->
                                                </div>
 
-                                                       <div class="row-fluid">
+                                                       <!-- <div 
class="row-fluid">
                                                                <label 
for="selectXYFilters">Additional filters:</label><br>
                                                                <span 
data-bind="foreach: groupChoices">
                                                                        <div 
class="panel panel-default xyGroupOption">
@@ -55,7 +61,7 @@
                                                                                
        </div>
                                                                                
</div>
                                                                        </span>
-                                               </div>
+                                               </div> -->
                                            </form>
                                </div>
 
@@ -65,7 +71,7 @@
                                <button class="btn btn-block btn-col btn-info" 
data-bind="click: submitXY"><i class="fa fa-area-chart"></i> Preview chart 
</button>
                        </div>
                        <div class="row">
-                               <button class="btn btn-block btn-col 
btn-danger" data-bind="visible: !chartSaved() && optionStateChanged, click: 
saveXY"><i class="fa fa-save"></i> Save chart </button>
+                               <button class="btn btn-block btn-col 
btn-danger" data-bind="visible: !chartSaved() && optionStateChanged, click: 
saveWidgetConfig"><i class="fa fa-save"></i> Save chart </button>
                        </div>
                        <div class="row">
                                <button class="btn btn-block btn-col 
btn-success" data-bind="visible: chartSaved"><i class="fa 
fa-check-circle-o"></i> Chart saved. </button>
@@ -76,8 +82,8 @@
                    <div class="col-md-9 col-sm-12">
 
                        <div class="row" id="specifiedXYchart">
-                               <div class="col-md-10">
-                                   <span data-bind="if: !xyIsSetUp()">
+                               <div class="col-md-12">
+                                   <span data-bind="if: !config">
                                        <div class="row-fluid alert 
alert-danger" data-bind="style: { width: '100%', overflow: 'hidden'}">
                                                <div class="col-md-1">
                                                        <h1><i class="fa 
fa-gears"></i></h1>
@@ -87,8 +93,10 @@
                                                        <p>Choose display 
options to see the chart.</p>
                                                    </div>
                                                </div>
-                                   </span>
-                                       <div class="row-fluid" data-bind="if: 
xyIsSetUp">
+                                           </span>
+
+
+                                       <div class="row-fluid" data-bind="if: 
chartLoaded">
                                            <h1 data-bind="text: title"></h1>
                                            <h4 data-bind="visible: 
chosenFilters > 0">Narrowed by:
                                                    <span data-bind="foreach: 
chosenFilters">
@@ -96,15 +104,15 @@
                                                    </span>
                                                </h4>
                                        </div>
-                                   <canvas id='x-by-yChart' height="550" 
width="900"></canvas>
+                                   <div id='x-by-yChart' data-bind="attr: { 
width: chartWidth, height: chartHeight }"></div>
                                </div>
 
-                               <div class="col-md-2 pull-right">
+                               <!-- <div class="col-md-2 pull-right">
                                        <button class="btn-info"><i class="fa 
fa-bar-chart"></i></button>
                                        <button class="btn-info"><i class="fa 
fa-line-chart"></i></button>
                                        <button class="btn-info"><i class="fa 
fa-pie-chart"></i></button>
                                        <button class="btn-info"><i class="fa 
fa-table"></i></button>
-                               </div>
+                               </div> -->
                        </div>
                    </div>
 
@@ -146,7 +154,19 @@
                 <button type="button" class="close" data-dismiss="modal"><span 
aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
                 <h4 class="modal-title">Fraud Gauge SQL:</h4>
             </div>
-            <div class="modal-body" data-bind="text: queryStringXYsql"></div>
+            <div class="modal-body" data-bind="text: queryStringSQL"></div>
+        </div><!-- /.modal-content -->
+    </div>
+</div>
+
+<div class="modal fade" id="loadingModal">
+       <div class="modal-dialog">
+        <div class="modal-content">
+            <h3 class="modal-body">Chart Loading....</h3>
+            <div class="progress">
+                         <div class="progress-bar progress-bar-success 
progress-bar-striped active" role="progressbar" aria-valuenow="100" 
aria-valuemin="0" aria-valuemax="100" style="width: 100%">
+                         </div>
+                       </div>
         </div><!-- /.modal-content -->
     </div>
 </div>
\ No newline at end of file
diff --git a/src/components/widgets/x-by-y/x-by-y.js 
b/src/components/widgets/x-by-y/x-by-y.js
index 257c24a..44dcffe 100644
--- a/src/components/widgets/x-by-y/x-by-y.js
+++ b/src/components/widgets/x-by-y/x-by-y.js
@@ -3,194 +3,369 @@
     'text!components/widgets/x-by-y/x-by-y.html',
     'momentjs',
     'numeraljs',
-    'chartjs',
-    'select2'
-], function( ko, template, moment, numeral, Chart, select2 ){
-
+    'c3',
+    'select2',
+    'WidgetBase'
+], function( ko, template, moment, numeral, c3, select2, WidgetBase ){
 
     function XByYChartViewModel( params ){
 
+        WidgetBase.call( this, params );
         var self = this;
 
-        self.xyIsSetUp = ko.observable(false);
-        self.chartWidth = ko.observable('900');
-        self.chartHeight = ko.observable('550');
-        self.showSlice = ko.observable();
-        self.bySlice = ko.observable();
-        self.timeChoice = ko.observable();
+        var chartDataCall = 
self.getChartData(params.configuration.queryString);
 
-        self.queryStringXYsql = ko.observable('This widget hasn\'t been set up 
yet!');
-        self.queryRequest = {};
-        self.chosenFilters = ko.observableArray();
-        self.subChoices = ko.observableArray();
+        $.when( chartDataCall ).then( function( dataArray ){
+            self.retrievedResults(dataArray.results);
+            self.dataLoading(false);
+            self.preDataLoading(false);
 
-        self.chartSaved = ko.observable(false);
-        self.optionStateChanged = ko.observable(false);
-        self.logStateChange = function(n){
-            self.optionStateChanged(n);
-            self.chartSaved(false);
-        };
+            self.chartData = self.processData(self.retrievedResults(), 
params.configuration.timeBreakout);
 
-        self.title = ko.computed(function(){
-               return self.showSlice() + ' by ' + self.bySlice();
+            self.makeChart(self.chartData);
         });
 
-        self.showPanelBody = function(area){
-            $('#'+area+'body').toggleClass('hide');
-        };
+            self.showSlice = ko.observable();
+            self.bySlice = ko.observable();
+            self.timeChoice = ko.observable();
+            self.queryRequest = {};
+            self.queryString = '';
+            self.chosenFilters = ko.observableArray();
+            self.subChoices = ko.observableArray();
+            self.chartWidth(950);
 
-        //saved charts
-        //TODO: these will trigger a saved set of parameters to draw the chart 
with.
-        self.presetTitles = ko.observableArray([
-               'Donations During Big English 2014',
-               'Donations for Fiscal Year 2014'
-        ]);
-        ///////
+            self.title = ko.computed(function(){
+               return self.showSlice(); //+ ' by ' + self.bySlice();
+            });
 
-        self.ySlices = ko.observableArray([
-               'Donations',
-               'Failed Donations'
-        ]);
+            self.makeChart = function(data){
 
-        self.xSlices = ko.observableArray();
+                self.chartLoaded(true);
 
-        self.timeChoices = ko.observableArray();
+                self.monthlyChart = function(d,i){
 
-        self.groupChoices = ko.observableArray();
+                    var monthNamesArray = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 
'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
 
-        //populate user choices dynamically
-        self.populateChoices = (function(){
-            //populate y slices
-            //TODO: this one will be user-defined, and developed in full later
-            //since right now the specifics are a bit obscured.
-            $.get( 'metadata/x-by-y', function(reqData){
-              self.metadata = reqData;
+                    return {
+                        bindto: '#x-by-yChart',
+                        size: {
+                            height: 450,
+                            width: window.width
+                        },
+                        zoom: { enabled: true },
+                        data: {
+                            columns: [ data.monthlyCountArray, 
data.monthlyDataArray ],
+                            type: 'bar',
+                            colors: { 'Monthly Total': 'rgb(92,184,92)', 
'Monthly Count': '#f0ad4e' },
+                            axes: {
+                                'Monthly Total': 'y',
+                                'Monthly Count': 'y2'
+                            }
+                        },
+                        grid: {
+                            x: {
+                                show: true
+                            },
+                            y: {
+                                show: true
+                            }
+                        },
+                        axis: {
+                            x: {
+                                tick: {
+                                    format: function(x){ return 
monthNamesArray[x]; }
+                                }
+                            },
+                            y: {
+                                tick: {
+                                    format: function(x){ return 
numeral(x).format('$0,0'); }
+                                }
+                            },
+                            y2: {
+                                tick: {
+                                    format: function(x){ return 
numeral(x).format('0,0'); }
+                                },
+                                show: true
+                            }
+                        },
+                        tooltip: {
+                            format: {
+                                title: function (d) {
+                                    return monthNamesArray[d];
+                                },
+                                value: function (value, ratio, id) {
+                                    var display;
+                                    if(id === 'Monthly Total'){
+                                        display = 
numeral(value).format('$0,0');
+                                    } else {
+                                        display = numeral(value).format('0,0');
+                                    }
+                                    return display;
+                                }
+                            }
+                        },
+                        bar: {
+                            width: {
+                                ratio: 0.5
+                            }
+                        }
+                    };
+                };
+
+                self.hourlyChart = function(d,i){
+                    var hourlyData = data.dayObj[d.x + 1 ],
+                        hourlyCountArray = ['Hourly Count'],
+                        hourlyTotalArray = ['Hourly Total'];
+                    for(var j=1; j<25; j++){
+                        hourlyCountArray.push(hourlyData[j].count);
+                        hourlyTotalArray.push(hourlyData[j].total);
+                    }
+                    return {
+                        bindto: '#x-by-yChart',
+                        size: {
+                            height: 450,
+                            width: window.width
+                        },
+                        zoom: { enabled: true },
+                        data: {
+                            columns: [ hourlyTotalArray, hourlyCountArray ],
+                            type: 'bar',
+                            colors: { 'Hourly Total': 'rgb(92,184,92)', 
'Hourly Count': '#f0ad4e' },
+                            onclick: function (d, i) { 
c3.generate(self.dailyChart()); },
+                            axes: {
+                                'Hourly Total': 'y',
+                                'Hourly Count': 'y2'
+                            }
+                        },
+                        grid: {
+                            x: {
+                                show: true
+                            },
+                            y: {
+                                show: true
+                            }
+                        },
+                        axis: {
+                            x: {
+                                label: {
+                                    text: 'December ' + ( d.x + 1 ),
+                                    position: 'outer-left'
+                                },
+                                tick: {
+                                    format: function(x){ return x + ':00'; }
+                                }
+                            },
+                            y: {
+                                tick: {
+                                    format: function(x){ return 
numeral(x).format('$0,0'); }
+                                }
+                            },
+                            y2: {
+                                tick: {
+                                    format: function(x){ return 
numeral(x).format('0,0'); }
+                                },
+                                show: true
+                            }
+                        },
+                        tooltip: {
+                            format: {
+                                title: function (d) { return 'Hour ' + d; },
+                                value: function (value, ratio, id) {
+                                    var display;
+                                    if(id === 'Hourly Total'){
+                                        display = 
numeral(value).format('$0,0');
+                                    } else {
+                                        display = numeral(value).format('0,0');
+                                    }
+                                    return display;
+                                }
+                            }
+                        },
+                        bar: {
+                            width: {
+                                ratio: 0.5
+                            }
+                        }
+                    };
+                };
+
+                self.dailyChart = function(d,i){
+                    return {
+                        bindto: '#x-by-yChart',
+                        size: {
+                            height: 450,
+                            width: window.width
+                        },
+                        zoom: { enabled: true },
+                        data: {
+                            columns: [ data.dailyDataArray, 
data.dailyCountArray ],
+                            type: 'bar',
+                            colors: { 'Daily Total': 'rgb(49,176,213)', 'Daily 
Count': '#f0ad4e' },
+                            onclick: function (d, i) {
+                                self.xByYChart = 
c3.generate(self.hourlyChart(d,i));
+                            },
+                            axes: {
+                                'Daily Total': 'y',
+                                'Daily Count': 'y2'
+                            }
+                        },
+                        grid: {
+                            x: {
+                                show: true
+                            },
+                            y: {
+                                show: true
+                            }
+                        },
+                        axis: {
+                            x: {
+                                tick: {
+                                    format: function(x){ return 'Dec ' + 
(x+1); }
+                                }
+                            },
+                            y: {
+                                tick: {
+                                    format: function(x){ return 
numeral(x).format('$0,0'); }
+                                }
+                            },
+                            y2: {
+                                tick: {
+                                    format: function(x){ return 
numeral(x).format('0,0'); }
+                                },
+                                show: true
+                            }
+                        },
+                        tooltip: {
+                            format: {
+                                title: function (d) { return 'Day ' + (d+1); },
+                                value: function (value, ratio, id) {
+                                    var display;
+                                    if(id === 'Daily Total'){
+                                        display = 
numeral(value).format('$0,0');
+                                    } else {
+                                        display = numeral(value).format('0,0');
+                                    }
+                                    return display;
+                                }
+                            }
+                        },
+                        bar: {
+                            width: {
+                                ratio: 0.5
+                            }
+                        }
+                    };
+            };
 
 
-                var xArray = [], timeArray = [], groupArray = [];
-                $.each(self.metadata.filters, function(prop, obj){
+                switch(data.timescale){
+                    case 'Year':
+                    case 'Month':
+                        self.xByYChart = c3.generate(self.monthlyChart());
+                        break;
+                    case 'Day':
+                        self.xByYChart = c3.generate(self.dailyChart());
+                        break;
+                    case 'Hour':
+                        self.xByYChart = c3.generate(self.hourlyChart());
+                        break;
+                }
+            };
 
-                    if(obj.type !== 'number' || prop === 'Amount'){
+            if(params.configuration){
+                self.chartSaved(true);
+                //self.makeChart(self.retrievedResults());
 
-                        if(obj.canGroup){
-                            if(obj.values){
-                                groupArray.push({ 'name': prop, 'choices': 
obj.values });
+            } else {
+                self.chartSaved(false);
+            }
+
+            self.showPanelBody = function(area){
+                $('#'+area+'body').toggleClass('hide');
+            };
+
+            //saved charts
+            //TODO: these will trigger a saved set of parameters to draw the 
chart with.
+            self.presetTitles = ko.observableArray([
+                'This does not work yet.',
+               'Donations During Big English 2014',
+               'Donations for Fiscal Year 2014'
+            ]);
+            ///////
+
+            self.ySlices = ko.observableArray([
+               'Donations'
+               //'Failed Donations'
+            ]);
+
+            self.xSlices = ko.observableArray();
+            self.timeChoices = ko.observableArray();
+            self.groupChoices = ko.observableArray();
+
+            //populate user choices dynamically
+            self.populateChoices = (function(){
+                //populate y slices
+                $.get( 'metadata/x-by-y', function(reqData){
+                    self.metadata = reqData;
+
+                    var xArray = [], timeArray = ['Year', 'Month', 'Day'], 
groupArray = [];
+
+                    $.each(self.metadata.filters, function(prop, obj){
+
+                        if(obj.type !== 'number' || prop === 'Amount'){
+
+                            if(obj.canGroup){
+                                if(obj.values){
+                                    groupArray.push({ 'name': prop, 'choices': 
obj.values });
+                                }
+
+                                $('select #'+prop).select2();
+
+                                //TODO: later this will do something 
different/more specific.
+                                xArray.push(prop);
                             }
 
-                            $('select #'+prop).select2();
-
-                            //TODO: later this will do something 
different/more specific.
-                            xArray.push(prop);
                         }
+                    });
+                    self.xSlices(xArray);
+                    self.timeChoices(timeArray);
+                    self.groupChoices(groupArray);
 
-
-                    } else {
-                        timeArray.push(prop);
-                    }
-                });
-                self.xSlices(xArray);
-                self.timeChoices(timeArray);
-                self.groupChoices(groupArray);
-
-            });
-
-        })();
-
-        self.convertToQuery = function( userChoices ){
-            //y slice
-            //right now this doesn't matter because it's always 'donations'
-
-            var groupStr = 'group=' + userChoices.xSlice;
-
-            //additional filters:
-            if( userChoices.additionalFilters.length > 0 ){
-
-                var filterStr = '$filter=';
-
-                var filterObj = {}, haveMultipleSubfilters = [];
-                $.each( userChoices.additionalFilters, function(el, subfilter){
-                    var filter = subfilter.substr(0, subfilter.indexOf(' '));
-                    if(!filterObj[filter]){
-                      filterObj[filter] = subfilter;
-                    } else {
-                      filterObj[filter] += ' or ' + subfilter;
-                      haveMultipleSubfilters.push(filter);
-                    }
                 });
 
-                $.each( filterObj, function(el, s){
-                    if( haveMultipleSubfilters.indexOf(el) > -1){
-                      filterStr += '(' + filterObj[el] + ')';
-                    } else {
-                      filterStr += filterObj[el];
-                    }
-                    filterStr += ' and ';
+            })();
+
+            self.submitXY = function(){
+
+                $('#loadingModal').modal('show');
+                self.queryRequest.ySlice = self.showSlice();
+                //self.queryRequest.xSlice = self.bySlice();
+                //self.queryRequest.additionalFilters = self.chosenFilters();
+                self.queryRequest.timeBreakout = self.timeChoice();
+
+                self.queryString         = 
self.convertToQuery(self.queryRequest);
+                self.config.queryString  = self.queryString;
+                self.config.timeBreakout = self.queryRequest.timeBreakout;
+                self.config.chartData    = self.chartData;
+
+                var chartDataCall = self.getChartData(self.queryString);
+
+                $.when( chartDataCall ).then( function( dataArray ){
+                    self.retrievedResults(dataArray.results);
+                    self.dataLoading(false);
+
+                    self.chartData = self.processData(self.retrievedResults(), 
self.timeChoice());
+
+                    self.makeChart(self.chartData);
+                    $('#loadingModal').modal('hide');
+
+                    self.chartSaved(false);
                 });
 
-                //cut off last AND
-                if( filterStr !== '$filter=' ){
-                    return groupStr + '&' + (filterStr.slice(0, -5));
-                } else {
-                    return groupStr;
-                }
-            } else {
-                return groupStr;
-            }
-        };
 
-        self.saveXY = function(){
-            //TODO: save it in the user profile
-            self.chartSaved(true);
-        };
+            };
 
-        self.submitXY = function(){
-
-            //here is an example query string for grabbing Big English 
countries by day for Dec:
-            // 
http://localhost:8080/data/x-by-y?group=Day&group=Country&$filter=DT gt
-            //'2014-12-01T00:00:00Z' and Country eq 'US' or Country eq 'CA' or 
Country eq 'NZ'
-            //or Country eq 'AU' or Country eq 'GB'
-
-            //get all the choices into a queryRequest object
-            self.queryRequest.ySlice = self.showSlice();
-            self.queryRequest.xSlice = self.bySlice();
-            self.queryRequest.additionalFilters = self.chosenFilters();
-            var queryString = self.convertToQuery(self.queryRequest);
-
-            $.get( '/data/x-by-y?' + (queryString).replace(
-          /\+/g, '%20' ), function ( dataget ) {
-                console.log('dataget: ', dataget);
-            });
-
-               self.fakeData = {
-                   labels: ['January', 'February', 'March', 'April', 'May', 
'June', 'July'],
-                   datasets: [
-                       {
-                           label: 'My First dataset',
-                           fillColor: 'rgba(220,220,220,0.2)',
-                           strokeColor: 'rgba(220,220,220,1)',
-                           pointColor: 'rgba(220,220,220,1)',
-                           pointStrokeColor: '#fff',
-                           pointHighlightFill: '#fff',
-                           pointHighlightStroke: 'rgba(220,220,220,1)',
-                           data: [65, 59, 80, 81, 56, 55, 40]
-                       },
-                       {
-                           label: 'My Second dataset',
-                           fillColor: 'rgba(151,187,205,0.2)',
-                           strokeColor: 'rgba(151,187,205,1)',
-                           pointColor: 'rgba(151,187,205,1)',
-                           pointStrokeColor: '#fff',
-                           pointHighlightFill: '#fff',
-                           pointHighlightStroke: 'rgba(151,187,205,1)',
-                           data: [28, 48, 40, 19, 86, 27, 90]
-                       }
-                   ]
-               };
-               var ctx = $('#x-by-yChart').get(0).getContext('2d');
-               self.fakeChart = new Chart(ctx).Line(self.fakeData);
-
-               self.xyIsSetUp(true);
-        };
+            return(this);
 
     }
 
diff --git a/src/css/style.css b/src/css/style.css
index f11f16f..f0a0982 100644
--- a/src/css/style.css
+++ b/src/css/style.css
@@ -232,6 +232,10 @@
 
 /* WIDGET BASE STYLINGS */
 
+.alert {
+    overflow: hidden;
+}
+
 .factbox {
     width: 100%;
     padding: 7px;
@@ -276,6 +280,10 @@
 }
 
 /* Gauges */
+
+.fraudGauge {
+    padding: 0;
+}
 
 #fraudGaugeWidget .panel-body {
     padding: 0;
@@ -403,6 +411,10 @@
     overflow: hidden;
 }
 
+#loadingModal .modal-content {
+    padding: 10px;
+}
+
 .navbar-fixed-top {
     height: 70px;
 }
@@ -513,21 +525,30 @@
     padding: 0 9px 0 0;
 }
 
+.loading, .loadingWidget {
+    background-color: #5bc0de;
+    opacity: .85;
+    border-radius: 25px;
+    padding: 37px;
+    color: #ffffff;
+    z-index: 99999;
+    font-size: 110%;
+    height: 192px;
+    width: 192px;
+}
+
 .loading {
     position: fixed;
     top: 360px;
     left: 50%;
     margin-top: -96px;
     margin-left: -96px;
-    background-color: #5bc0de;
-    opacity: .85;
-    border-radius: 25px;
-    width: 192px;
-    height: 192px;
-    padding: 37px;
-    color: #ffffff;
-    z-index: 99999;
-    font-size: 110%;
+}
+
+.loadingWidget {
+    position: absolute;
+    display: block;
+    margin: 10% 43%;
 }
 
 .loading div {
diff --git a/widgets/x-by-y.js b/widgets/x-by-y.js
index cc1715d..98d8afb 100644
--- a/widgets/x-by-y.js
+++ b/widgets/x-by-y.js
@@ -145,28 +145,33 @@
                        values: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ],
                        labels: [ 'Completed', 'Pending', 'Cancelled', 
'Failed', 'In Progress', 'Overdue', 'Settled', 'Paid', 'Refunded' ],
                        canGroup: true
-               }
-       },
-       defaultFilter: {
-               type: 'and',
-               left: {
-                       type: 'eq',
-                       left: { type: 'property', name: 'Year' },
-                       right: { type: 'literal', value: '2014' }
                },
-               right: {
-                       type: 'and',
-                       left: {
-                               type: 'eq',
-                               left: { type: 'property', name: 'Month' },
-                               right: { type: 'literal', value: '12' }
-                       },
-                       right: {
-                               type: 'lt',
-                               left: { type: 'property', name: 'Amount' },
-                               right: { type: 'literal', value: '5000' }
-                       }
+               YearsAgo: {
+                       table: 'cc',
+                       column : 'receive_date',
+                       func: 'timestampdiff(YEAR, [[COL]], utc_timestamp())',
+                       display: 'Years ago',
+                       type: 'number',
+                       min: 0,
+                       max: 12
+               },
+               MonthsAgo: {
+                       table: 'cc',
+                       column : 'receive_date',
+                       func: 'timestampdiff(MONTH, [[COL]], utc_timestamp())',
+                       display: 'Months ago',
+                       type: 'number',
+                       min: 0,
+                       max: 10000
+               },
+               DaysAgo: {
+                       table: 'cc',
+                       column : 'receive_date',
+                       func: 'timestampdiff(DAY, [[COL]], utc_timestamp())',
+                       display: 'Days ago',
+                       type: 'number',
+                       min: 0,
+                       max: 10000
                }
-       },
-       defaultGroup: ['Day']
+       }
 };

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I8c685154bf3da6659996a5104dfd30115f9e30d1
Gerrit-PatchSet: 15
Gerrit-Project: wikimedia/fundraising/dash
Gerrit-Branch: master
Gerrit-Owner: Ssmith <ssm...@wikimedia.org>
Gerrit-Reviewer: Ejegg <eeggles...@wikimedia.org>
Gerrit-Reviewer: Ssmith <ssm...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to