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">×</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