Madhuvishy has submitted this change and it was merged.

Change subject: Add global default report fields
......................................................................


Add global default report fields

Test plan:
* select metric - see local default
* set global default, select metric, see global default
* select metric, set global default, see change
* set global default, select metric, change metric input, doesn't revert
* change timezone, no change is visible but underlying dates change

Bug: T74117
Change-Id: Ieaf162fe3695a7467bd42ed09ef47c464ae29490
---
M wikimetrics/static/js/knockout.util.js
M wikimetrics/static/js/reportCreate.js
M wikimetrics/templates/forms/metric_configuration.html
M wikimetrics/templates/report.html
4 files changed, 406 insertions(+), 136 deletions(-)

Approvals:
  Madhuvishy: Verified; Looks good to me, approved
  jenkins-bot: Verified



diff --git a/wikimetrics/static/js/knockout.util.js 
b/wikimetrics/static/js/knockout.util.js
index 640cccb..2a11195 100644
--- a/wikimetrics/static/js/knockout.util.js
+++ b/wikimetrics/static/js/knockout.util.js
@@ -1,38 +1,93 @@
+'use strict';
 /**
  * Custom binding that is used as follows:
- * `<section data-bind="metricConfigurationForm: property"></section>`
- * And works as follows:
- *     In the example above, property is a ko.observable or plain property 
that evaluates to some HTML which
- *     should be rendered inside the <section></section>
- *     The binding then sets the context for the section's child elements as 
the same as the current context
+ *
+ * `<section data-bind="metricConfigurationForm: {
+ *      content: property,
+ *      defaults: defaults
+ * }"></section>`
+ *
+ * Parameters
+ *      content  : is a ko.observable or plain property that evaluates to some 
HTML
+ *                 which should be rendered inside the <section></section>
+ *
+ *      defaults : a set of observables that may control the value of the
+ *                 input elements inside the configuration HTML
+ *
+ * This binding passes the current context to the child elements
  */
 ko.bindingHandlers.metricConfigurationForm = {
-    init: function(element, valueAccessor, allBindingsAccessor, viewModel, 
bindingContext){
+    init: function(){
         return {
             controlsDescendantBindings: true
         };
     },
     update: function(element, valueAccessor, allBindingsAccessor, viewModel, 
bindingContext){
-        var unwrapped, childContext;
-        unwrapped = ko.utils.unwrapObservable(valueAccessor());
-        if (unwrapped != null) {
-            $(unwrapped).find(':input').each(function(){
+        var unwrapped = ko.unwrap(valueAccessor()),
+            content = ko.unwrap(unwrapped.content),
+            // must be careful when accessing defaults below, we don't want to 
re-create the form
+            defaults = unwrapped.defaults,
+            childContext = 
bindingContext.createChildContext(bindingContext.$data);
+
+        if (content) {
+            bindingContext.subscriptions = [];
+
+            $(content).find(':input,div.datetimepicker').each(function(){
                 var value = '';
                 var name = $(this).attr('name');
                 if (!name) { return; }
-                
-                if ($(this).is('[type=checkbox]')){
+
+                if (defaults[name] && defaults[name].peek() != null) {
+                    value = (
+                        defaults[name].localDate ?
+                            defaults[name].localDate :
+                            defaults[name]
+                    ).peek();
+                } else if ($(this).is('[type=checkbox]')){
                     value = $(this).is(':checked');
+                // support date time picker containers that don't have an input
+                } else if ($(this).is('.datetimepicker')) {
+                    value = $(this).data('value');
                 } else {
                     value = $(this).val();
                 }
-                bindingContext.$data[name] = ko.observable(value);
+                var inputObservable = ko.observable(value);
+                bindingContext.$data[name] = inputObservable;
+
+                // set up subscriptions to the defaults
+                // This is important to do as subscriptions, otherwise the 
binding will
+                //   think there's a dependency on defaults and run update 
each time a default is set,
+                if (defaults[name]) {
+                    bindingContext.subscriptions.push(
+                        defaults[name].subscribe(function(val){
+                            inputObservable(val);
+                        })
+                    );
+                }
             });
-            $(element).html(unwrapped);
-            childContext = 
bindingContext.createChildContext(bindingContext.$data);
+
+            $(element).html(content);
             ko.applyBindingsToDescendants(childContext, element);
+
+        } else {
+            $(element).html('');
+
+            if (bindingContext.subscriptions) {
+                // if this update is un-selecting this metric, dispose its 
subscriptions
+                // This is important, otherwise de-selected metrics will 
retain their subscriptions and
+                //   continue to receive updates
+                
ko.bindingHandlers.metricConfigurationForm.unsubscribe(bindingContext);
+            }
         }
-    }
+
+        // also clean up subscriptions on parent disposal
+        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
+            
ko.bindingHandlers.metricConfigurationForm.unsubscribe(bindingContext);
+        });
+    },
+    unsubscribe: function (bindingContext) {
+        bindingContext.subscriptions.forEach(function (s) { s.dispose(); });
+    },
 };
 
 /**
@@ -58,3 +113,152 @@
         }
     }
 };
+
+/**
+ * Custom binding used as follows:
+ * `<div data-bind="datetimepicker: {change: newValueFunction, timezone: 
timezone}">
+ *    <input type="text" name="..." data-bind="value: observableValue"/>
+ *  </div>`
+ * And works as follows:
+ *      The change parameter gets a function that receives the new date value 
when it changes
+ *        and does whatever it needs with it.
+ *      The timezone parameter is optional and converts the value to the 
specified timezone
+ *
+ * NOTE: you must wrap this around an existing input field, because the common 
use case
+ *       is to use WTForms to generate the form field with defaults, etc. 
controlled and
+ *       tested in the python code.
+ */
+ko.bindingHandlers.datetimepicker = {
+    init: function(element, valueAccessor, allBindingsAccessor, viewModel, 
bindingContext){
+        var val = valueAccessor(),
+            timezone = val.timezone,
+            zonedDate = val.value,
+            defaultDate = ko.unwrap(zonedDate) || val.defaultDate,
+            inputId = val.inputId,
+            // set up an observable to track the date selected
+            localDate = ko.observable(ko.unwrap(zonedDate)).withPausing(),
+
+            dateFormat = ko.bindingHandlers.datetimepicker.dateFormat,
+            dataFormat = ko.bindingHandlers.datetimepicker.dataFormat,
+
+            // add any boilerplate html needed to make datepicker work
+            container = $('<div class="input-append date"/>');
+
+        // since usually we'll want to update the local date, but usually
+        // we'll only have external access to the zoned date, we need a ref
+        zonedDate.localDate = localDate;
+
+        $(element).append(container);
+
+        container.append(
+            $('<input type="text">')
+                .attr('id', inputId)
+                .attr('data-format', dataFormat)
+        );
+
+        container.append(
+            $('<span class="add-on">' +
+                '<i data-time-icon="icon-time" 
data-date-icon="icon-calendar"></i>' +
+            '</span>')
+        );
+
+        container.datetimepicker({language: 'en'});
+
+        // the datepicker automatically sets the value of all the input 
elements inside
+        // the container to the local formatted date.  To keep our hidden form 
element
+        // free of this glitch, we append it after the container
+        container.after(
+            $('<input type="hidden">')
+                .attr('name', inputId)
+                .attr('data-bind', 'value: zonedDate')
+        );
+
+        // update the local date on date changes, so it can update the computed
+        container.on('changeDate', function (dataWrapper) {
+            localDate(dataWrapper.date);
+        });
+
+        if (defaultDate) {
+            
container.data('datetimepicker').setDate(moment.utc(defaultDate).toDate());
+        }
+
+        // write to the zoned date observable whenever the local date or 
timezone change
+        ko.computed(function () {
+            var local = ko.unwrap(localDate),
+                zone = ko.unwrap(timezone);
+
+            if (!local) {
+                zonedDate(null);
+                return;
+            }
+
+            if (!zone || !zone.value) {
+                return;
+            }
+
+            zonedDate(
+                moment.utc(
+                    // strip out the local time zone by pretending the value 
is in UTC
+                    moment.utc(local).format(dateFormat) + ' '
+                    // then add the timezone back and parse the whole thing as 
a new date
+                    + zone.value
+                ).format(dateFormat)
+            );
+        });
+
+        // if an outsider changes the zoned date, set the date of the picker 
directly
+        var subscription = zonedDate.subscribe(function (zoned) {
+            var zone = ko.unwrap(timezone);
+
+            if (!zone || !zone.value) {
+                return;
+            }
+
+            // do the opposite of above, get the local date from the zoned
+            var local = moment(zoned).add(
+                parseInt(zone.value), 'hours'
+            ).format(dateFormat);
+
+            // because the dom disposal below doesn't work, just ignore these 
errors
+            try {
+                // this may fire multiple times for ghost containers!! :(
+                container.data('datetimepicker').setDate(local);
+                localDate.sneakyUpdate(local);
+            } catch (e) { return; }
+        });
+
+        // set up the binding context on the child hidden input to make sure 
the zoned
+        // date is also available as a form element if used in a normal form
+        var childContext = 
bindingContext.createChildContext(bindingContext.$data);
+        childContext.zonedDate = zonedDate;
+        ko.applyBindingsToDescendants(childContext, element);
+
+        // TODO: for whatever reason, this doesn't fire when the metric is 
de-selected
+        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
+            subscription.dispose();
+        });
+
+        return { controlsDescendantBindings: true };
+    }
+};
+ko.bindingHandlers.datetimepicker.dataFormat = 'yyyy-MM-dd hh:mm:ss';
+ko.bindingHandlers.datetimepicker.dateFormat = 'YYYY-MM-DD HH:mm:ss';
+
+
+// hacky type of observable that can be paused so it doesn't notify subscribers
+// thanks to RP Niemeier: http://stackoverflow.com/a/17984353/180664
+ko.observable.fn.withPausing = function() {
+    this.notifySubscribers = function() {
+        if (!this.pauseNotifications) {
+            ko.subscribable.fn.notifySubscribers.apply(this, arguments);
+        }
+    };
+
+    this.sneakyUpdate = function(newValue) {
+        this.pauseNotifications = true;
+        this(newValue);
+        this.pauseNotifications = false;
+    };
+
+    return this;
+};
diff --git a/wikimetrics/static/js/reportCreate.js 
b/wikimetrics/static/js/reportCreate.js
index 7da18f7..2f9fb46 100644
--- a/wikimetrics/static/js/reportCreate.js
+++ b/wikimetrics/static/js/reportCreate.js
@@ -4,9 +4,56 @@
 var site = site;
 
 $(document).ready(function(){
-    
+    'use strict';
+
+    function setSelected(list){
+        var bareList = ko.utils.unwrapObservable(list);
+        ko.utils.arrayForEach(bareList, function(item){
+            item.selected = ko.observable(false);
+        });
+    }
+
+    function setConfigure(list){
+        var bareList = ko.utils.unwrapObservable(list);
+        ko.utils.arrayForEach(bareList, function(item){
+            item.configure = ko.observable('');
+        });
+    }
+
+    function setAggregationOptions(list){
+        var bareList = ko.utils.unwrapObservable(list);
+        ko.utils.arrayForEach(bareList, function(item){
+            item.individualResults = ko.observable(false);
+            item.aggregateResults = ko.observable(true);
+            item.aggregateSum = ko.observable(true);
+            item.aggregateAverage = ko.observable(false);
+            item.aggregateStandardDeviation = ko.observable(false);
+            item.outputConfigured = ko.computed(function(){
+                return this.individualResults() || (this.aggregateResults() && 
(this.aggregateSum() || this.aggregateAverage() || 
this.aggregateStandardDeviation()));
+            }, item);
+        });
+    }
+
+    function setTabIds(list, prefix){
+        if (!prefix) {
+            prefix = 'should-be-unique';
+        }
+        var bareList = ko.utils.unwrapObservable(list);
+        ko.utils.arrayForEach(bareList, function(item){
+
+            item.tabId = ko.computed(function(){
+                return prefix + '-' + this.id;
+            }, item);
+
+            item.tabIdSelector = ko.computed(function(){
+                return '#' + prefix + '-' + this.id;
+            }, item);
+        });
+    }
+
     var utcTimezone = {name: 'UTC', value: '+00:00'},
         viewModel = {
+
         filter: ko.observable(''),
         cohorts: ko.observableArray([]),
         toggleCohort: function(cohort){
@@ -35,19 +82,25 @@
             {name: 'Pacific Standard Time', value: '-08:00'},
             {name: 'Hawaii Standard Time', value: '-10:00'}
         ]),
-        timezone: ko.observable(utcTimezone), // no default
+        timezone: ko.observable(utcTimezone),
+
+        // global metric defaults, by property
+        defaults: {
+            'start_date': ko.observable(),
+            'end_date': ko.observable(),
+            'timeseries': ko.observable(),
+            'rolling_days': ko.observable(),
+            'include_deleted': ko.observable(),
+        },
 
         metrics: ko.observableArray([]),
         toggleMetric: function(metric){
-            
+
             if (metric) {
                 if (metric.selected()){
                     // fetch form to configure metric with
                     $.get('/metrics/configure/' + metric.name)
-                        .done(site.handleWith(function(configureForm){
-                            metric.configure(configureForm);
-                            enableDateTimePicker(metric);
-                        }))
+                        .done(site.handleWith(metric.configure))
                         .fail(site.failure);
                 } else {
                     metric.configure('');
@@ -55,9 +108,8 @@
             }
             return true;
         },
-        
+
         save: function(formElement){
-            var timezone = this.timezone();
 
             if (site.hasValidationErrors()){
                 site.showWarning('Please configure and click Save 
Configuration for each selected metric.');
@@ -69,7 +121,7 @@
                 site.showWarning('Please select at least one cohort and one 
metric.');
                 return;
             }
-            
+
             var metricsWithoutOutput = {};
             ko.utils.arrayForEach(vm.request().responses(), function(response){
                 if (!response.metric.outputConfigured()){
@@ -77,12 +129,12 @@
                 }
             });
             metricsWithoutOutput = site.keys(metricsWithoutOutput);
-            
+
             if (metricsWithoutOutput.length){
                 site.showWarning(metricsWithoutOutput.join(', ') + ' do not 
have any output selected.');
                 return;
             }
-            
+
             var form = $(formElement);
             var data = ko.toJSON(vm.request().responses);
             data = JSON.parse(data);
@@ -97,13 +149,6 @@
                 delete response.metric.tabIdSelector;
                 delete response.metric.selected;
                 delete response.metric.description;
-                // apply timezone info
-                ko.utils.arrayForEach(response.metric.dateTimeFieldNames, 
function(name) {
-                    response.metric[name] = moment
-                        .utc(response.metric[name] + ' ' + timezone.value)
-                        .format('YYYY-MM-DD HH:mm:ss');
-                });
-                delete response.metric.dateTimeFieldNames;
             });
             data = JSON.stringify(data);
 
@@ -114,27 +159,26 @@
                 }))
                 .fail(site.failure);
         },
-        
-        saveMetricConfiguration: function(formElement){
+
+        validateMetricConfiguration: function(formElement){
             var metric = ko.dataFor(formElement);
             var form = $(formElement);
             var data = ko.toJS(metric);
             delete data.configure;
-            
+
             $.ajax({ type: 'post', url: form.attr('action'), data: data })
                 .done(site.handleWith(function(response){
                     metric.configure(response);
-                    enableDateTimePicker(metric);
                     if (site.hasValidationErrors()){
-                        site.showWarning('The configuration was not all valid. 
 Please check all the metrics below.');
+                        site.showWarning('The configuration was not all valid. 
 Please check below for warnings.');
                     } else {
-                        site.showSuccess('Configuration Saved');
+                        site.showSuccess('Configuration Valid');
                     }
                 }))
                 .fail(site.failure);
         }
     };
-    
+
     // fetch this user's cohorts
     $.get('/cohorts/list/')
         .done(site.handleWith(function(data){
@@ -146,11 +190,11 @@
                     viewModel.cohorts().filter(function(c){
                         return c.id === parseInt(location.hash.substring(1), 
10);
                     })[0].selected(true);
-                } catch(e) {}
+                } catch(e) { return; }
             }
         }))
         .fail(site.failure);
-    
+
     // fetch the list of available metrics
     $.get('/metrics/list/')
         .done(site.handleWith(function(data){
@@ -161,24 +205,24 @@
             viewModel.metrics(data.metrics);
         }))
         .fail(site.failure);
-    
+
     // computed pieces of the viewModel
     viewModel.request = ko.observable({
         recurrent: ko.observable(false),
-        
+
         cohorts: ko.computed(function(){
             return this.cohorts().filter(function(cohort){
                 return cohort.selected();
             });
         }, viewModel).extend({ throttle: 1 }),
-        
+
         metrics: ko.computed(function(){
             return this.metrics().filter(function(metric){
                 return metric.selected();
             });
         }, viewModel).extend({ throttle: 1 })
     });
-    
+
     // second level computed pieces of the viewModel
     viewModel.request().responses = ko.computed(function(){
         var request = this;
@@ -195,10 +239,10 @@
                 ret.push(response);
             });
         });
-        
+
         return ret;
     }, viewModel.request());
-    
+
     viewModel.filteredCohorts = ko.computed(function(){
         if (this.cohorts().length && this.filter().length) {
             var filter = this.filter().toLowerCase();
@@ -209,75 +253,16 @@
         }
         return this.cohorts();
     }, viewModel);
-    
-    function setSelected(list){
-        var bareList = ko.utils.unwrapObservable(list);
-        ko.utils.arrayForEach(bareList, function(item){
-            item.selected = ko.observable(false);
-        });
-    }
-    
-    function setConfigure(list){
-        var bareList = ko.utils.unwrapObservable(list);
-        ko.utils.arrayForEach(bareList, function(item){
-            item.configure = ko.observable('');
-        });
-    }
-    
-    function setAggregationOptions(list){
-        var bareList = ko.utils.unwrapObservable(list);
-        ko.utils.arrayForEach(bareList, function(item){
-            item.individualResults = ko.observable(false);
-            item.aggregateResults = ko.observable(true);
-            item.aggregateSum = ko.observable(true);
-            item.aggregateAverage = ko.observable(false);
-            item.aggregateStandardDeviation = ko.observable(false);
-            item.outputConfigured = ko.computed(function(){
-                return this.individualResults() || (this.aggregateResults() && 
(this.aggregateSum() || this.aggregateAverage() || 
this.aggregateStandardDeviation()));
-            }, item);
-        });
-    }
-    
-    function setTabIds(list, prefix){
-        if (!prefix) {
-            prefix = 'should-be-unique';
-        }
-        var bareList = ko.utils.unwrapObservable(list);
-        ko.utils.arrayForEach(bareList, function(item){
-            
-            item.tabId = ko.computed(function(){
-                return prefix + '-' + this.id;
-            }, item);
-            
-            item.tabIdSelector = ko.computed(function(){
-                return '#' + prefix + '-' + this.id;
-            }, item);
-        });
-    }
-    
-    function enableDateTimePicker(metric){
-        var parentId = metric.tabId();
-        var controls = $('#' + parentId + ' div.datetimepicker');
-        controls.datetimepicker({language: 'en'});
-        // save datetime field names for later use (timezone conversion)
-        metric.dateTimeFieldNames = [];
-        controls.each(function () {
-            metric.dateTimeFieldNames.push($(this).find('input').attr('name'));
-        });
-        // TODO: this might be cleaner if it metric[name] was an observable
-        controls.on('changeDate', function(){
-            var input = $(this).find('input');
-            var name = input.attr('name');
-            metric[name] = input.val();
-        });
-    }
-    
+
     // tabs that are dynamically added won't work - fix by re-initializing
-    $(".sample-result .tabbable").on("click", "a", function(e){
+    $('.sample-result .tabbable').on('click', 'a', function(e){
         e.preventDefault();
         $(this).tab('show');
     });
 
     // apply bindings - this connects the DOM with the view model constructed 
above
     ko.applyBindings(viewModel);
+
+    // make sure any checkboxes in the Pick Defaults section are indeterminate
+    $('#default_include_deleted')[0].indeterminate = true;
 });
diff --git a/wikimetrics/templates/forms/metric_configuration.html 
b/wikimetrics/templates/forms/metric_configuration.html
index 804c98d..560e210 100644
--- a/wikimetrics/templates/forms/metric_configuration.html
+++ b/wikimetrics/templates/forms/metric_configuration.html
@@ -1,4 +1,4 @@
-{% import 'forms/field_validation.html' as validation %}<form 
class="form-horizontal metric-configuration" method="POST" action="{{action}}" 
data-bind="submit: $root.saveMetricConfiguration">
+{% import 'forms/field_validation.html' as validation %}<form 
class="form-horizontal metric-configuration" method="POST" action="{{action}}" 
data-bind="submit: $root.validateMetricConfiguration">
     {% for f in form %}
     {% if f.name != 'csrf_token' %}
     <div class="control-group"
@@ -12,12 +12,19 @@
                 {% elif f.type == 'DateField' %}
                     {{ f(**{'type':'date', 'data-bind':'value: '+f.name}) }}
                 {% elif f.type == 'BetterDateTimeField' %}
-                    <div class="input-append date datetimepicker" 
title="{{f.description}}">
-                        {{ f(**{'type':'text', 'data-bind':'value: '+f.name, 
'data-format':'yyyy-MM-dd hh:mm:ss'}) }}
-                        <span class="add-on">
-                            <i data-time-icon="icon-time" 
data-date-icon="icon-calendar">
-                            </i>
-                        </span>
+
+                    {# The name and data-value attributes are needed so the
+                        metric configurator binding can extract observables
+                    #}
+                    <div title="{{f.description}}" class="datetimepicker"
+                         name="{{f.name}}"
+                         data-value="{{f.data}}"
+                         data-bind="datetimepicker: {
+                             timezone: $root.timezone,
+                             value: {{f.name}},
+                             inputId: '{{f.name}}',
+                             defaultDate: '{{f.data}}'
+                         }">
                     </div>
                 {% else %}
                     {{ f(**{'data-bind':'value: '+f.name}) }}
diff --git a/wikimetrics/templates/report.html 
b/wikimetrics/templates/report.html
index 1641534..c561261 100644
--- a/wikimetrics/templates/report.html
+++ b/wikimetrics/templates/report.html
@@ -6,19 +6,19 @@
 <label class="checkbox">
     <p><input type="checkbox"  data-bind="checked: request().recurrent"/>
     Make this a <b>Public Scheduled </b> Report.  This means that
-       it will run daily and compute results for each day it runs.
-       <p>
+    it will run daily and compute results for each day it runs.
+    <p>
     <p><small>Checking this box makes the results of this report 
<b>publicly</b> accessible.
-       There will be no way to stop the report once it starts.  When using 
this feature,
-        make sure you understand these caveats or contact
-        <a href mailto=wikimetr...@lists.wikimedia.org> 
wikimetr...@lists.wikimedia.org</a> for help.
+    There will be no way to stop the report once it starts.  When using this 
feature,
+     make sure you understand these caveats or contact
+     <a href mailto=wikimetr...@lists.wikimedia.org> 
wikimetr...@lists.wikimedia.org</a> for help.
  </small>
  </p>
 </label>
 </div>
 <div class="well well-small pick-cohorts">
     <div class="form-inline">
-               <label class="control-label"> <h4>Pick Cohorts</h4> </label> 
&nbsp;
+            <label class="control-label"> <h4>Pick Cohorts</h4> </label> &nbsp;
             <input type="text"  placeholder="type to filter" data-bind="value: 
filter, valueUpdate:'afterkeydown'"/>
     </div>
     <div class="cohorts">
@@ -32,14 +32,83 @@
         </ul>
     </div>
 </div>
-<div class="well well-small pick-timezone">
-    <div class="form-inline">
-        <label class="control-label"> <h4>Pick Timezone</h4> </label> &nbsp;
-        <select data-bind="options: availableTimezones,
-             optionsText: function (item) {
-                 return item.value + ' ' + item.name;
-             },
-             value:timezone"></select>
+<div class="well well-small pick-defaults">
+    <h4>Pick Defaults</h4>
+
+    <p>For reports with multiple metrics, set defaults for common parameters 
here.
+    Leave a field blank to use the metric's defaults instead.</p>
+
+    <br/>
+    <div class="form-horizontal">
+        <div class="control-group">
+            <label for="default_timezone" 
class="control-label">Timezone</label>
+            <div class="controls">
+                <select name="default_timezone" id="default_timezone"
+                        data-bind="
+                            options: availableTimezones,
+                            optionsText: function (item) {
+                                return item.value + ' ' + item.name;
+                            },
+                            value: timezone"></select>
+            </div>
+        </div>
+
+        <!-- ko ifnot: $root.request().recurrent -->
+
+        <div class="control-group">
+            <label class="control-label" for="default_start_date">Start 
Date</label>
+            <div class="controls">
+                <div data-bind="datetimepicker: {
+                                    timezone: timezone,
+                                    value: defaults.start_date,
+                                    inputId: 'default_start_date'
+                                }">
+                </div>
+            </div>
+        </div>
+
+        <div class="control-group">
+            <label class="control-label" for="default_end_date">End Date / As 
of Date</label>
+            <div class="controls">
+                <div data-bind="datetimepicker: {
+                                    timezone: timezone,
+                                    value: defaults.end_date,
+                                    inputId: 'default_end_date'
+                                }">
+                </div>
+            </div>
+        </div>
+
+        <div class="control-group">
+            <label class="control-label" for="default_timeseries">Time Series 
by</label>
+            <div class="controls" title="Report results by year, month, day, 
or hour">
+                <select data-bind="value: defaults.timeseries"
+                        id="default_timeseries" name="default_timeseries">
+                    <option selected="" value="">(use metric default)</option>
+                    <option value="none">none</option>
+                    <option value="hour">hour</option>
+                    <option value="day">day</option>
+                    <option value="month">month</option>
+                    <option value="year">year</option>
+                </select>
+            </div>
+        </div>
+
+        <div class="control-group">
+            <label class="control-label" for="default_rolling_days">Rolling 
Days</label>
+            <div class="controls">
+                    <input data-bind="value: defaults.rolling_days" 
id="default_rolling_days" name="default_rolling_days" type="text">
+            </div>
+        </div>
+
+        <div class="control-group">
+            <label class="control-label" for="default_include_deleted">Include 
Deleted</label>
+            <div class="controls" title="Count revisions made on deleted 
pages">
+                    <input checked="" data-bind="checked: 
defaults.include_deleted" id="default_include_deleted" 
name="default_include_deleted" type="checkbox" value="y">
+            </div>
+        </div>
+
+        <!-- /ko -->
     </div>
 </div>
 <div class="well well-small pick-metrics">
@@ -58,7 +127,12 @@
                     <span data-bind="text: description">
                     </span>
                 </label>
-                <div class="configure-metric-form" 
data-bind="metricConfigurationForm: configure, attr: {id: tabId() + 
'-configure'}">
+                <div class="configure-metric-form"
+                     data-bind="metricConfigurationForm: {
+                                    content: configure,
+                                    defaults: $root.defaults
+                                },
+                                attr: {id: tabId() + '-configure'}">
                 </div>
             </div>
         </div>

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

Gerrit-MessageType: merged
Gerrit-Change-Id: Ieaf162fe3695a7467bd42ed09ef47c464ae29490
Gerrit-PatchSet: 13
Gerrit-Project: analytics/wikimetrics
Gerrit-Branch: master
Gerrit-Owner: Milimetric <dandree...@wikimedia.org>
Gerrit-Reviewer: Madhuvishy <mviswanat...@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