View | Details | Raw Unified | Return to bug 62166
Collapse All | Expand All

(-)bin/report-template/content/js/customGraph.js.fmkr (+160 lines)
Line 0 Link Here
1
/*
2
   Licensed to the Apache Software Foundation (ASF) under one or more
3
   contributor license agreements.  See the NOTICE file distributed with
4
   this work for additional information regarding copyright ownership.
5
   The ASF licenses this file to You under the Apache License, Version 2.0
6
   (the "License"); you may not use this file except in compliance with
7
   the License.  You may obtain a copy of the License at
8
9
       http://www.apache.org/licenses/LICENSE-2.0
10
11
   Unless required by applicable law or agreed to in writing, software
12
   distributed under the License is distributed on an "AS IS" BASIS,
13
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
   See the License for the specific language governing permissions and
15
   limitations under the License.
16
*/
17
18
19
    
20
$(document).ready(function() {
21
    $(".click-title").mouseenter( function(    e){
22
        e.preventDefault();
23
        this.style.cursor="pointer";
24
    });
25
    $(".click-title").mousedown( function(event){
26
        event.preventDefault();
27
    });
28
    
29
    <#list customsGraphsData?keys as key> 
30
    try{
31
        refresh${key}(true);
32
    } catch(e){
33
        console.log(e);
34
    }<#break> <#-- Stop after the first iteration in purpose to show the first graph only -->
35
    </#list>
36
    
37
    $(".portlet-header").css("cursor", "auto");
38
});
39
40
<#list customsGraphsData?keys as key>
41
var response${key}Infos = {
42
    data: ${customsGraphsData[key]},
43
    getOptions: function(){
44
        return {
45
            series: {
46
                lines: {
47
                    show: true
48
                },
49
                points: {
50
                    show: true
51
                }
52
            },
53
            xaxis: {
54
                mode: "time",
55
                timeformat: getTimeFormat(this.data.result.granularity),
56
                axisLabel: '${graphConfigurations[key].getProperties()["set_X_Axis"]!"Default X Axis Title"}',
57
                axisLabelUseCanvas: true,
58
                axisLabelFontSizePixels: 12,
59
                axisLabelFontFamily: 'Verdana, Arial',
60
                axisLabelPadding: 20,
61
            },
62
            yaxis: {
63
                axisLabel: '${graphConfigurations[key].getProperties()["set_Y_Axis"]!"Default Y Axis Title"}',
64
                axisLabelUseCanvas: true,
65
                axisLabelFontSizePixels: 12,
66
                axisLabelFontFamily: 'Verdana, Arial',
67
                axisLabelPadding: 20,
68
            },
69
            legend: {
70
                noColumns: 2,
71
                show: true,
72
                container: '#legendResponse${key}'
73
            },
74
            selection: {
75
                mode: 'xy'
76
            },
77
            grid: {
78
                hoverable: true // IMPORTANT! this is needed for tooltip to
79
                                // work
80
            },
81
            tooltip: true,
82
            tooltipOpts: {
83
                content: "%s : at %x ${graphConfigurations[key].getProperties()["setContentMessage"]!"Default content message"} %y"
84
            }
85
        };
86
    },
87
    createGraph: function() {
88
        var data = this.data;
89
        var dataset = prepareData(data.result.series, $("#choicesResponse${key}"));
90
        var options = this.getOptions();
91
        prepareOptions(options, data);
92
        $.plot($("#flotResponse${key}"), dataset, options);
93
        // setup overview
94
        $.plot($("#overviewResponse${key}"), dataset, prepareOverviewOptions(options));
95
    }
96
};
97
98
// Response Custom Graph
99
function refresh${key}(fixTimestamps) {
100
    var infos = response${key}Infos;
101
    prepareSeries(infos.data);
102
    if(fixTimestamps) {
103
        fixTimeStamps(infos.data.result.series, ${(timeZoneOffset?c)!0});
104
    }
105
    if(isGraph($("#flotResponse${key}"))){
106
        infos.createGraph();
107
    }else{
108
        var choiceContainer = $("#choicesResponse${key}");
109
        createLegend(choiceContainer, infos);
110
        infos.createGraph();
111
        setGraphZoomable("#flotResponse${key}", "#overviewResponse${key}");
112
        $('#footerResponse${key} .legendColorBox > div').each(function(i){
113
            $(this).clone().prependTo(choiceContainer.find("li").eq(i));
114
        });
115
    }
116
};
117
    </#list>
118
119
function collapse(elem, collapsed){
120
    if(collapsed){
121
        $(elem).parent().find(".fa-chevron-up").removeClass("fa-chevron-up").addClass("fa-chevron-down");
122
    }else{
123
        $(elem).parent().find(".fa-chevron-down").removeClass("fa-chevron-down").addClass("fa-chevron-up");
124
    
125
    <#assign loopCount = 0>
126
    <#list customsGraphsData?keys as key>
127
    <#if loopCount == 0>
128
    if(elem.id == "bodyResponse${key}"){
129
    <#else>
130
    else if(elem.id == "bodyResponse${key}"){
131
    </#if>
132
        if (isGraph($(elem).find('.flot-chart-content')) == false) {
133
            refresh${key}(true);
134
        }
135
            document.location.href="#${key}";
136
        }
137
    </#list>
138
    }
139
}
140
141
function toggleAll(id, checked){
142
    var placeholder = document.getElementById(id);
143
    var cases = $(placeholder).find(':checkbox');
144
    cases.prop('checked', checked);
145
    $(cases).parent().children().children().toggleClass("legend-disabled", !checked);
146
    var choiceContainer;
147
    
148
    <#assign loopCount = 0>
149
    <#list customsGraphsData?keys as key>
150
    <#if loopCount == 0>
151
    if(id == "choicesResponse${key}"){
152
    <#else>
153
    else if(id == "choicesResponse${key}"){
154
    </#if>
155
        choiceContainer = $("#choicesResponse${key}");
156
        refresh${key}(false);
157
    }
158
    <#assign loopCount++>    
159
    </#list>
160
}
(-)bin/report-template/content/js/dashboard-commons.js (-181 lines)
Lines 1-181 Link Here
1
/*
2
   Licensed to the Apache Software Foundation (ASF) under one or more
3
   contributor license agreements.  See the NOTICE file distributed with
4
   this work for additional information regarding copyright ownership.
5
   The ASF licenses this file to You under the Apache License, Version 2.0
6
   (the "License"); you may not use this file except in compliance with
7
   the License.  You may obtain a copy of the License at
8
9
       http://www.apache.org/licenses/LICENSE-2.0
10
11
   Unless required by applicable law or agreed to in writing, software
12
   distributed under the License is distributed on an "AS IS" BASIS,
13
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
   See the License for the specific language governing permissions and
15
   limitations under the License.
16
*/
17
var DAY_MS   = 86400000;
18
var HOUR_MS  =  3600000;
19
var MINUTE_MS  =    60000;
20
21
/**
22
 * From https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math/round
23
 * Licensed under https://creativecommons.org/licenses/by-sa/2.5/
24
 */
25
// Closure
26
(function() {
27
  /**
28
   * Decimal adjustment of a number.
29
   *
30
   * @param {String}  type  The type of adjustment.
31
   * @param {Number}  value The number.
32
   * @param {Integer} exp   The exponent (the 10 logarithm of the adjustment base).
33
   * @returns {Number} The adjusted value.
34
   */
35
  function decimalAdjust(type, value, exp) {
36
    // If the exp is undefined or zero...
37
    if (typeof exp === 'undefined' || +exp === 0) {
38
      return Math[type](value);
39
    }
40
    value = +value;
41
    exp = +exp;
42
    // If the value is not a number or the exp is not an integer...
43
    if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
44
      return NaN;
45
    }
46
    // Shift
47
    value = value.toString().split('e');
48
    value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
49
    // Shift back
50
    value = value.toString().split('e');
51
    return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
52
  }
53
54
  // Decimal round
55
  if (!Math.round10) {
56
    Math.round10 = function(value, exp) {
57
      return decimalAdjust('round', value, exp);
58
    };
59
  }
60
  // Decimal floor
61
  if (!Math.floor10) {
62
    Math.floor10 = function(value, exp) {
63
      return decimalAdjust('floor', value, exp);
64
    };
65
  }
66
  // Decimal ceil
67
  if (!Math.ceil10) {
68
    Math.ceil10 = function(value, exp) {
69
      return decimalAdjust('ceil', value, exp);
70
    };
71
  }
72
})();
73
74
/*
75
 * Suffixes the specified value with a unit
76
 * The spaced argument defines whether a space character is introduced.
77
 */
78
function formatUnit(value, unit, spaced){
79
    return spaced ? value + " " + unit : value + unit;
80
}
81
82
/*
83
 * Gets a string representing the specified duration in milliseconds.
84
 * 
85
 * E.g : duration = 20000100, returns "45 min 20 sec 100 ms"
86
 */
87
function formatDuration(duration, spaced) {
88
    var type = $.type(duration);
89
    if (type === "string")
90
        return duration;
91
92
    // Calculate each part of the string
93
    var days = Math.floor(duration / 86400000); // 1000 * 60 * 60 * 24 = 1 day
94
    duration %= 8640000;
95
96
    var hours = Math.floor(duration / 3600000); // 1000 * 60 *60 = 1 hour
97
    duration %= 3600000;
98
99
    var minutes = Math.floor(duration / 60000); // 1000 * 60 = 1 minute
100
    duration %= 60000;
101
102
    var seconds = Math.floor(duration / 1000); // 1 second
103
    duration %= 1000;
104
105
    // Add non zero part.
106
    var formatArray = [];
107
    if (days > 0)
108
        formatArray.push(formatUnit(days, " day(s)", spaced));
109
110
    if (hours > 0)
111
        formatArray.push(formatUnit(hours, " hour(s)", spaced));
112
113
    if (minutes > 0)
114
        formatArray.push(formatUnit(minutes," min", spaced));
115
116
    if (seconds > 0)
117
        formatArray.push(formatUnit(seconds, " sec", spaced));
118
119
    if (duration > 0)
120
        formatArray.push(formatUnit(duration, " ms", spaced));
121
122
    // Build the string
123
    return formatArray.join(" ");
124
}
125
126
/*
127
 * Gets axis label for the specified granularity
128
 */
129
function getElapsedTimeLabel(granularity) {
130
    return "Elapsed Time (granularity: " + formatDuration(granularity) + ")";
131
}
132
133
/*
134
 * Gets time format based on granularity
135
 */
136
function getTimeFormat(granularity) {
137
    if (granularity >= DAY_MS) {
138
        return "%y/%m/%d"; 
139
    } else if (granularity >= HOUR_MS) {
140
        return "%m/%d %H"; 
141
    } else if (granularity >= MINUTE_MS) {
142
        return "%d %H:%M";
143
    } else {
144
        return "%H:%M:%S";
145
    }
146
}
147
148
149
/*
150
 * Gets axis label for the specified granularity
151
 */
152
function getConnectTimeLabel(granularity) {
153
    return "Connect Time (granularity: " + formatDuration(granularity) + ")";
154
}
155
156
//Get the property value of an object using the specified key
157
//Returns the property value if all properties in the key exist; undefined
158
//otherwise.
159
function getProperty(key, obj) {
160
    return key.split('.').reduce(function(prop, subprop){
161
        return prop && prop[subprop];
162
    }, obj);
163
}
164
165
/*
166
 * Removes quotes from the specified string 
167
 */
168
function unquote(str, quoteChar) {
169
    quoteChar = quoteChar || '"';
170
    if (str.length > 0 && str[0] === quoteChar && str[str.length - 1] === quoteChar)
171
        return str.slice(1, str.length - 1);
172
    else
173
        return str;
174
};
175
176
/*
177
 * This comparison function evaluates abscissas to sort array of coordinates.
178
 */
179
function compareByXCoordinate(coord1, coord2) {
180
    return coord2[0] - coord1[0];
181
}
(-)bin/report-template/content/js/dashboard-commons.js.fmkr (+461 lines)
Line 0 Link Here
1
/*
2
   Licensed to the Apache Software Foundation (ASF) under one or more
3
   contributor license agreements.  See the NOTICE file distributed with
4
   this work for additional information regarding copyright ownership.
5
   The ASF licenses this file to You under the Apache License, Version 2.0
6
   (the "License"); you may not use this file except in compliance with
7
   the License.  You may obtain a copy of the License at
8
       http://www.apache.org/licenses/LICENSE-2.0
9
   Unless required by applicable law or agreed to in writing, software
10
   distributed under the License is distributed on an "AS IS" BASIS,
11
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
   See the License for the specific language governing permissions and
13
   limitations under the License.
14
*/
15
var DAY_MS   = 86400000;
16
var HOUR_MS  =  3600000;
17
var MINUTE_MS  =    60000;
18
19
/**
20
 * From https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math/round
21
 * Licensed under https://creativecommons.org/licenses/by-sa/2.5/
22
 */
23
// Closure
24
(function() {
25
  /**
26
   * Decimal adjustment of a number.
27
   *
28
   * @param {String}  type  The type of adjustment.
29
   * @param {Number}  value The number.
30
   * @param {Integer} exp   The exponent (the 10 logarithm of the adjustment base).
31
   * @returns {Number} The adjusted value.
32
   */
33
  function decimalAdjust(type, value, exp) {
34
    // If the exp is undefined or zero...
35
    if (typeof exp === 'undefined' || +exp === 0) {
36
      return Math[type](value);
37
    }
38
    value = +value;
39
    exp = +exp;
40
    // If the value is not a number or the exp is not an integer...
41
    if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
42
      return NaN;
43
    }
44
    // Shift
45
    value = value.toString().split('e');
46
    value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
47
    // Shift back
48
    value = value.toString().split('e');
49
    return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
50
  }
51
52
  // Decimal round
53
  if (!Math.round10) {
54
    Math.round10 = function(value, exp) {
55
      return decimalAdjust('round', value, exp);
56
    };
57
  }
58
  // Decimal floor
59
  if (!Math.floor10) {
60
    Math.floor10 = function(value, exp) {
61
      return decimalAdjust('floor', value, exp);
62
    };
63
  }
64
  // Decimal ceil
65
  if (!Math.ceil10) {
66
    Math.ceil10 = function(value, exp) {
67
      return decimalAdjust('ceil', value, exp);
68
    };
69
  }
70
})();
71
72
/*
73
 * Suffixes the specified value with a unit
74
 * The spaced argument defines whether a space character is introduced.
75
 */
76
function formatUnit(value, unit, spaced){
77
    return spaced ? value + " " + unit : value + unit;
78
}
79
80
/*
81
 * Gets a string representing the specified duration in milliseconds.
82
 * 
83
 * E.g : duration = 20000100, returns "45 min 20 sec 100 ms"
84
 */
85
function formatDuration(duration, spaced) {
86
    var type = $.type(duration);
87
    if (type === "string")
88
        return duration;
89
90
    // Calculate each part of the string
91
    var days = Math.floor(duration / 86400000); // 1000 * 60 * 60 * 24 = 1 day
92
    duration %= 8640000;
93
94
    var hours = Math.floor(duration / 3600000); // 1000 * 60 *60 = 1 hour
95
    duration %= 3600000;
96
97
    var minutes = Math.floor(duration / 60000); // 1000 * 60 = 1 minute
98
    duration %= 60000;
99
100
    var seconds = Math.floor(duration / 1000); // 1 second
101
    duration %= 1000;
102
103
    // Add non zero part.
104
    var formatArray = [];
105
    if (days > 0)
106
        formatArray.push(formatUnit(days, " day(s)", spaced));
107
108
    if (hours > 0)
109
        formatArray.push(formatUnit(hours, " hour(s)", spaced));
110
111
    if (minutes > 0)
112
        formatArray.push(formatUnit(minutes," min", spaced));
113
114
    if (seconds > 0)
115
        formatArray.push(formatUnit(seconds, " sec", spaced));
116
117
    if (duration > 0)
118
        formatArray.push(formatUnit(duration, " ms", spaced));
119
120
    // Build the string
121
    return formatArray.join(" ");
122
}
123
124
/*
125
 * Gets axis label for the specified granularity
126
 */
127
function getElapsedTimeLabel(granularity) {
128
    return "Elapsed Time (granularity: " + formatDuration(granularity) + ")";
129
}
130
131
/*
132
 * Gets time format based on granularity
133
 */
134
function getTimeFormat(granularity) {
135
    if (granularity >= DAY_MS) {
136
        return "%y/%m/%d"; 
137
    } else if (granularity >= HOUR_MS) {
138
        return "%m/%d %H"; 
139
    } else if (granularity >= MINUTE_MS) {
140
        return "%d %H:%M";
141
    } else {
142
        return "%H:%M:%S";
143
    }
144
}
145
146
/*
147
 * Gets axis label for the specified granularity
148
 */
149
function getConnectTimeLabel(granularity) {
150
    return "Connect Time (granularity: " + formatDuration(granularity) + ")";
151
}
152
153
//Get the property value of an object using the specified key
154
//Returns the property value if all properties in the key exist; undefined
155
//otherwise.
156
function getProperty(key, obj) {
157
    return key.split('.').reduce(function(prop, subprop){
158
        return prop && prop[subprop];
159
    }, obj);
160
}
161
162
/*
163
 * Removes quotes from the specified string 
164
 */
165
function unquote(str, quoteChar) {
166
    quoteChar = quoteChar || '"';
167
    if (str.length > 0 && str[0] === quoteChar && str[str.length - 1] === quoteChar)
168
        return str.slice(1, str.length - 1);
169
    else
170
        return str;
171
};
172
173
/*
174
 * This comparison function evaluates abscissas to sort array of coordinates.
175
 */
176
function compareByXCoordinate(coord1, coord2) {
177
    return coord2[0] - coord1[0];
178
}
179
180
181
/*
182
 * Followings functions and variables are used to generate statics graphs
183
 * AND dynamics graphs (if you have the plugin)
184
 */
185
186
var showControllersOnly = ${showControllersOnly?c!"false"};
187
var seriesFilter = ${seriesFilter!"undefined"};
188
var filtersOnlySampleSeries = ${filtersOnlySampleSeries?c!"false"};
189
190
// Fixes time stamps
191
function fixTimeStamps(series, offset){
192
    $.each(series, function(index, item) {
193
        $.each(item.data, function(index, coord) {
194
            coord[0] += offset;
195
        });
196
    });
197
}
198
199
// Check if the specified jquery object is a graph
200
function isGraph(object){
201
    return object.data('plot') !== undefined;
202
}
203
204
// Collapse
205
$(function() {
206
        $('.collapse').on('shown.bs.collapse', function(){
207
            collapse(this, false);
208
        }).on('hidden.bs.collapse', function(){
209
            collapse(this, true);
210
        });
211
});
212
213
$(function() {
214
    $(".glyphicon").mousedown( function(event){
215
        var tmp = $('.in:not(ul)');
216
        tmp.parent().parent().parent().find(".fa-chevron-up").removeClass("fa-chevron-down").addClass("fa-chevron-down");
217
        tmp.removeClass("in");
218
        tmp.addClass("out");
219
    });
220
});
221
222
/**
223
 * Export graph to a PNG
224
 */
225
function exportToPNG(graphName, target) {
226
    var plot = $("#"+graphName).data('plot');
227
    var flotCanvas = plot.getCanvas();
228
    var image = flotCanvas.toDataURL();
229
    image = image.replace("image/png", "image/octet-stream");
230
    
231
    var downloadAttrSupported = ("download" in document.createElement("a"));
232
    if(downloadAttrSupported === true) {
233
        target.download = graphName + ".png";
234
        target.href = image;
235
    }
236
    else {
237
        document.location.href = image;
238
    }
239
}
240
241
// Override the specified graph options to fit the requirements of an overview
242
function prepareOverviewOptions(graphOptions){
243
    var overviewOptions = {
244
        series: {
245
            shadowSize: 0,
246
            lines: {
247
                lineWidth: 1
248
            },
249
            points: {
250
                // Show points on overview only when linked graph does not show
251
                // lines
252
                show: getProperty('series.lines.show', graphOptions) == false,
253
                radius : 1
254
            }
255
        },
256
        xaxis: {
257
            ticks: 2,
258
            axisLabel: null
259
        },
260
        yaxis: {
261
            ticks: 2,
262
            axisLabel: null
263
        },
264
        legend: {
265
            show: false,
266
            container: null
267
        },
268
        grid: {
269
            hoverable: false
270
        },
271
        tooltip: false
272
    };
273
    return $.extend(true, {}, graphOptions, overviewOptions);
274
}
275
276
function prepareOptions(options, data) {
277
    options.canvas = true;
278
    var extraOptions = data.extraOptions;
279
    if(extraOptions !== undefined){
280
        var xOffset = options.xaxis.mode === "time" ? ${(timeZoneOffset?c)!0} : 0;
281
        var yOffset = options.yaxis.mode === "time" ? ${(timeZoneOffset?c)!0} : 0;
282
283
        if(!isNaN(extraOptions.minX))
284
        	options.xaxis.min = parseFloat(extraOptions.minX) + xOffset;
285
        
286
        if(!isNaN(extraOptions.maxX))
287
        	options.xaxis.max = parseFloat(extraOptions.maxX) + xOffset;
288
        
289
        if(!isNaN(extraOptions.minY))
290
        	options.yaxis.min = parseFloat(extraOptions.minY) + yOffset;
291
        
292
        if(!isNaN(extraOptions.maxY))
293
        	options.yaxis.max = parseFloat(extraOptions.maxY) + yOffset;
294
    }
295
}
296
297
// Filter, mark series and sort data
298
/**
299
 * @param data
300
 * @param noMatchColor if defined and true, series.color are not matched with index
301
 */
302
function prepareSeries(data, noMatchColor){
303
    var result = data.result;
304
305
    // Keep only series when needed
306
    if(seriesFilter && (!filtersOnlySampleSeries || result.supportsControllersDiscrimination)){
307
        // Insensitive case matching
308
        var regexp = new RegExp(seriesFilter, 'i');
309
        result.series = $.grep(result.series, function(series, index){
310
            return regexp.test(series.label);
311
        });
312
    }
313
314
    // Keep only controllers series when supported and needed
315
    if(result.supportsControllersDiscrimination && showControllersOnly){
316
        result.series = $.grep(result.series, function(series, index){
317
            return series.isController;
318
        });
319
    }
320
321
    // Sort data and mark series
322
    $.each(result.series, function(index, series) {
323
        series.data.sort(compareByXCoordinate);
324
        if(!(noMatchColor && noMatchColor===true)) {
325
	        series.color = index;
326
	    }
327
    });
328
}
329
330
// Set the zoom on the specified plot object
331
function zoomPlot(plot, xmin, xmax, ymin, ymax){
332
    var axes = plot.getAxes();
333
    // Override axes min and max options
334
    $.extend(true, axes, {
335
        xaxis: {
336
            options : { min: xmin, max: xmax }
337
        },
338
        yaxis: {
339
            options : { min: ymin, max: ymax }
340
        }
341
    });
342
343
    // Redraw the plot
344
    plot.setupGrid();
345
    plot.draw();
346
}
347
348
// Prepares DOM items to add zoom function on the specified graph
349
function setGraphZoomable(graphSelector, overviewSelector){
350
    var graph = $(graphSelector);
351
    var overview = $(overviewSelector);
352
353
    // Ignore mouse down event
354
    graph.bind("mousedown", function() { return false; });
355
    overview.bind("mousedown", function() { return false; });
356
357
    // Zoom on selection
358
    graph.bind("plotselected", function (event, ranges) {
359
        // clamp the zooming to prevent infinite zoom
360
        if (ranges.xaxis.to - ranges.xaxis.from < 0.00001) {
361
            ranges.xaxis.to = ranges.xaxis.from + 0.00001;
362
        }
363
        if (ranges.yaxis.to - ranges.yaxis.from < 0.00001) {
364
            ranges.yaxis.to = ranges.yaxis.from + 0.00001;
365
        }
366
367
        // Do the zooming
368
        var plot = graph.data('plot');
369
        zoomPlot(plot, ranges.xaxis.from, ranges.xaxis.to, ranges.yaxis.from, ranges.yaxis.to);
370
        plot.clearSelection();
371
372
        // Synchronize overview selection
373
        overview.data('plot').setSelection(ranges, true);
374
    });
375
376
    // Zoom linked graph on overview selection
377
    overview.bind("plotselected", function (event, ranges) {
378
        graph.data('plot').setSelection(ranges);
379
    });
380
381
    // Reset linked graph zoom when reseting overview selection
382
    overview.bind("plotunselected", function () {
383
        var overviewAxes = overview.data('plot').getAxes();
384
        zoomPlot(graph.data('plot'), overviewAxes.xaxis.min, overviewAxes.xaxis.max, overviewAxes.yaxis.min, overviewAxes.yaxis.max);
385
    });
386
}
387
388
// Prepares data to be consumed by plot plugins
389
function prepareData(series, choiceContainer, customizeSeries){
390
    var datasets = [];
391
392
    // Add only selected series to the data set
393
    choiceContainer.find("input:checked").each(function (index, item) {
394
        var key = $(item).attr("name");
395
        var i = 0;
396
        var size = series.length;
397
        while(i < size && series[i].label != key)
398
            i++;
399
        if(i < size){
400
            var currentSeries = series[i];
401
            datasets.push(currentSeries);
402
            if(customizeSeries)
403
                customizeSeries(currentSeries);
404
        }
405
    });
406
    return datasets;
407
}
408
409
/*
410
 * Ignore case comparator
411
 */
412
function sortAlphaCaseless(a,b){
413
    return a.toLowerCase() > b.toLowerCase() ? 1 : -1;
414
};
415
416
function createLegend(choiceContainer, infos) {
417
    // Sort series by name
418
    var keys = [];
419
    $.each(infos.data.result.series, function(index, series){
420
        keys.push(series.label);
421
    });
422
    keys.sort(sortAlphaCaseless);
423
424
    // Create list of series with support of activation/deactivation
425
    $.each(keys, function(index, key) {
426
        var id = choiceContainer.attr('id') + index;
427
        $('<li />')
428
            .append($('<input id="' + id + '" name="' + key + '" type="checkbox" checked="checked" hidden />'))
429
            .append($('<label />', { 'text': key , 'for': id }))
430
            .appendTo(choiceContainer);
431
    });
432
    choiceContainer.find("label").click( function(){
433
        if (this.style.color !== "rgb(129, 129, 129)" ){
434
            this.style.color="#818181";
435
        }else {
436
            this.style.color="black";
437
        }
438
        $(this).parent().children().children().toggleClass("legend-disabled");
439
    });
440
    choiceContainer.find("label").mousedown( function(event){
441
        event.preventDefault();
442
    });
443
    choiceContainer.find("label").mouseenter(function(){
444
        this.style.cursor="pointer";
445
    });
446
447
    // Recreate graphe on series activation toggle
448
    choiceContainer.find("input").click(function(){
449
        infos.createGraph();
450
    });
451
}
452
453
// Unchecks all boxes for "Hide all samples" functionality
454
function uncheckAll(id){
455
    toggleAll(id, false);
456
}
457
458
// Checks all boxes for "Show all samples" functionality
459
function checkAll(id){
460
    toggleAll(id, true);
461
}
(-)bin/report-template/content/js/graph.js.fmkr (-281 / +8 lines)
Lines 34-227 Link Here
34
    try{
34
    try{
35
        refreshResponseTimePercentiles();
35
        refreshResponseTimePercentiles();
36
    } catch(e){}
36
    } catch(e){}
37
    $(".portlet-header").css("cursor", "auto");
38
});
37
});
39
38
40
var showControllersOnly = ${showControllersOnly?c!"false"};
41
var seriesFilter = ${seriesFilter!"undefined"};
42
var filtersOnlySampleSeries = ${filtersOnlySampleSeries?c!"false"};
43
44
// Fixes time stamps
45
function fixTimeStamps(series, offset){
46
    $.each(series, function(index, item) {
47
        $.each(item.data, function(index, coord) {
48
            coord[0] += offset;
49
        });
50
    });
51
}
52
53
// Check if the specified jquery object is a graph
54
function isGraph(object){
55
    return object.data('plot') !== undefined;
56
}
57
58
/**
59
 * Export graph to a PNG
60
 */
61
function exportToPNG(graphName, target) {
62
    var plot = $("#"+graphName).data('plot');
63
    var flotCanvas = plot.getCanvas();
64
    var image = flotCanvas.toDataURL();
65
    image = image.replace("image/png", "image/octet-stream");
66
    
67
    var downloadAttrSupported = ("download" in document.createElement("a"));
68
    if(downloadAttrSupported === true) {
69
        target.download = graphName + ".png";
70
        target.href = image;
71
    }
72
    else {
73
        document.location.href = image;
74
    }
75
    
76
}
77
78
// Override the specified graph options to fit the requirements of an overview
79
function prepareOverviewOptions(graphOptions){
80
    var overviewOptions = {
81
        series: {
82
            shadowSize: 0,
83
            lines: {
84
                lineWidth: 1
85
            },
86
            points: {
87
                // Show points on overview only when linked graph does not show
88
                // lines
89
                show: getProperty('series.lines.show', graphOptions) == false,
90
                radius : 1
91
            }
92
        },
93
        xaxis: {
94
            ticks: 2,
95
            axisLabel: null
96
        },
97
        yaxis: {
98
            ticks: 2,
99
            axisLabel: null
100
        },
101
        legend: {
102
            show: false,
103
            container: null
104
        },
105
        grid: {
106
            hoverable: false
107
        },
108
        tooltip: false
109
    };
110
    return $.extend(true, {}, graphOptions, overviewOptions);
111
}
112
113
// Force axes boundaries using graph extra options
114
function prepareOptions(options, data) {
115
    options.canvas = true;
116
    var extraOptions = data.extraOptions;
117
    if(extraOptions !== undefined){
118
        var xOffset = options.xaxis.mode === "time" ? ${(timeZoneOffset?c)!0} : 0;
119
        var yOffset = options.yaxis.mode === "time" ? ${(timeZoneOffset?c)!0} : 0;
120
121
        if(!isNaN(extraOptions.minX))
122
        	options.xaxis.min = parseFloat(extraOptions.minX) + xOffset;
123
        
124
        if(!isNaN(extraOptions.maxX))
125
        	options.xaxis.max = parseFloat(extraOptions.maxX) + xOffset;
126
        
127
        if(!isNaN(extraOptions.minY))
128
        	options.yaxis.min = parseFloat(extraOptions.minY) + yOffset;
129
        
130
        if(!isNaN(extraOptions.maxY))
131
        	options.yaxis.max = parseFloat(extraOptions.maxY) + yOffset;
132
    }
133
}
134
135
// Filter, mark series and sort data
136
/**
137
 * @param data
138
 * @param noMatchColor if defined and true, series.color are not matched with index
139
 */
140
function prepareSeries(data, noMatchColor){
141
    var result = data.result;
142
143
    // Keep only series when needed
144
    if(seriesFilter && (!filtersOnlySampleSeries || result.supportsControllersDiscrimination)){
145
        // Insensitive case matching
146
        var regexp = new RegExp(seriesFilter, 'i');
147
        result.series = $.grep(result.series, function(series, index){
148
            return regexp.test(series.label);
149
        });
150
    }
151
152
    // Keep only controllers series when supported and needed
153
    if(result.supportsControllersDiscrimination && showControllersOnly){
154
        result.series = $.grep(result.series, function(series, index){
155
            return series.isController;
156
        });
157
    }
158
159
    // Sort data and mark series
160
    $.each(result.series, function(index, series) {
161
        series.data.sort(compareByXCoordinate);
162
        if(!(noMatchColor && noMatchColor===true)) {
163
	        series.color = index;
164
	    }
165
    });
166
}
167
168
// Set the zoom on the specified plot object
169
function zoomPlot(plot, xmin, xmax, ymin, ymax){
170
    var axes = plot.getAxes();
171
    // Override axes min and max options
172
    $.extend(true, axes, {
173
        xaxis: {
174
            options : { min: xmin, max: xmax }
175
        },
176
        yaxis: {
177
            options : { min: ymin, max: ymax }
178
        }
179
    });
180
181
    // Redraw the plot
182
    plot.setupGrid();
183
    plot.draw();
184
}
185
186
// Prepares DOM items to add zoom function on the specified graph
187
function setGraphZoomable(graphSelector, overviewSelector){
188
    var graph = $(graphSelector);
189
    var overview = $(overviewSelector);
190
191
    // Ignore mouse down event
192
    graph.bind("mousedown", function() { return false; });
193
    overview.bind("mousedown", function() { return false; });
194
195
    // Zoom on selection
196
    graph.bind("plotselected", function (event, ranges) {
197
        // clamp the zooming to prevent infinite zoom
198
        if (ranges.xaxis.to - ranges.xaxis.from < 0.00001) {
199
            ranges.xaxis.to = ranges.xaxis.from + 0.00001;
200
        }
201
        if (ranges.yaxis.to - ranges.yaxis.from < 0.00001) {
202
            ranges.yaxis.to = ranges.yaxis.from + 0.00001;
203
        }
204
205
        // Do the zooming
206
        var plot = graph.data('plot');
207
        zoomPlot(plot, ranges.xaxis.from, ranges.xaxis.to, ranges.yaxis.from, ranges.yaxis.to);
208
        plot.clearSelection();
209
210
        // Synchronize overview selection
211
        overview.data('plot').setSelection(ranges, true);
212
    });
213
214
    // Zoom linked graph on overview selection
215
    overview.bind("plotselected", function (event, ranges) {
216
        graph.data('plot').setSelection(ranges);
217
    });
218
219
    // Reset linked graph zoom when reseting overview selection
220
    overview.bind("plotunselected", function () {
221
        var overviewAxes = overview.data('plot').getAxes();
222
        zoomPlot(graph.data('plot'), overviewAxes.xaxis.min, overviewAxes.xaxis.max, overviewAxes.yaxis.min, overviewAxes.yaxis.max);
223
    });
224
}
225
39
226
var responseTimePercentilesInfos = {
40
var responseTimePercentilesInfos = {
227
        data: ${responseTimePercentiles!"{}"},
41
        data: ${responseTimePercentiles!"{}"},
Lines 1426-1431 Link Here
1426
                refreshLatenciesOverTime(true);
1240
                refreshLatenciesOverTime(true);
1427
            }
1241
            }
1428
            document.location.href="#latenciesOverTime";
1242
            document.location.href="#latenciesOverTime";
1243
        } else if (elem.id == "bodyCustomGraph") {
1244
            if (isGraph($(elem).find('.flot-chart-content')) == false) {
1245
                refreshCustomGraph(true);
1246
            }
1247
            document.location.href="#responseCustomGraph";
1429
        } else if (elem.id == "bodyConnectTimeOverTime") {
1248
        } else if (elem.id == "bodyConnectTimeOverTime") {
1430
            if (isGraph($(elem).find('.flot-chart-content')) == false) {
1249
            if (isGraph($(elem).find('.flot-chart-content')) == false) {
1431
                refreshConnectTimeOverTime(true);
1250
                refreshConnectTimeOverTime(true);
Lines 1485-1508 Link Here
1485
    }
1304
    }
1486
}
1305
}
1487
1306
1488
// Collapse
1489
$(function() {
1490
        $('.collapse').on('shown.bs.collapse', function(){
1491
            collapse(this, false);
1492
        }).on('hidden.bs.collapse', function(){
1493
            collapse(this, true);
1494
        });
1495
});
1496
1497
$(function() {
1498
    $(".glyphicon").mousedown( function(event){
1499
        var tmp = $('.in:not(ul)');
1500
        tmp.parent().parent().parent().find(".fa-chevron-up").removeClass("fa-chevron-down").addClass("fa-chevron-down");
1501
        tmp.removeClass("in");
1502
        tmp.addClass("out");
1503
    });
1504
});
1505
1506
/*
1307
/*
1507
 * Activates or deactivates all series of the specified graph (represented by id parameter)
1308
 * Activates or deactivates all series of the specified graph (represented by id parameter)
1508
 * depending on checked argument.
1309
 * depending on checked argument.
Lines 1521-1526 Link Here
1521
    } else if(id == "choicesResponseTimesOverTime"){
1322
    } else if(id == "choicesResponseTimesOverTime"){
1522
        choiceContainer = $("#choicesResponseTimesOverTime");
1323
        choiceContainer = $("#choicesResponseTimesOverTime");
1523
        refreshResponseTimeOverTime(false);
1324
        refreshResponseTimeOverTime(false);
1325
    }else if(id == "choicesResponseCustomGraph"){
1326
        choiceContainer = $("#choicesResponseCustomGraph");
1327
        refreshCustomGraph(false);
1524
    } else if ( id == "choicesLatenciesOverTime"){
1328
    } else if ( id == "choicesLatenciesOverTime"){
1525
        choiceContainer = $("#choicesLatenciesOverTime");
1329
        choiceContainer = $("#choicesLatenciesOverTime");
1526
        refreshLatenciesOverTime(false);
1330
        refreshLatenciesOverTime(false);
Lines 1570-1649 Link Here
1570
    });
1374
    });
1571
}
1375
}
1572
1376
1573
// Unchecks all boxes for "Hide all samples" functionality
1574
function uncheckAll(id){
1575
    toggleAll(id, false);
1576
}
1577
1578
// Checks all boxes for "Show all samples" functionality
1579
function checkAll(id){
1580
    toggleAll(id, true);
1581
}
1582
1583
// Prepares data to be consumed by plot plugins
1584
function prepareData(series, choiceContainer, customizeSeries){
1585
    var datasets = [];
1586
1587
    // Add only selected series to the data set
1588
    choiceContainer.find("input:checked").each(function (index, item) {
1589
        var key = $(item).attr("name");
1590
        var i = 0;
1591
        var size = series.length;
1592
        while(i < size && series[i].label != key)
1593
            i++;
1594
        if(i < size){
1595
            var currentSeries = series[i];
1596
            datasets.push(currentSeries);
1597
            if(customizeSeries)
1598
                customizeSeries(currentSeries);
1599
        }
1600
    });
1601
    return datasets;
1602
}
1603
1604
/*
1605
 * Ignore case comparator
1606
 */
1607
function sortAlphaCaseless(a,b){
1608
    return a.toLowerCase() > b.toLowerCase() ? 1 : -1;
1609
};
1610
1611
/*
1612
 * Creates a legend in the specified element with graph information
1613
 */
1614
function createLegend(choiceContainer, infos) {
1615
    // Sort series by name
1616
    var keys = [];
1617
    $.each(infos.data.result.series, function(index, series){
1618
        keys.push(series.label);
1619
    });
1620
    keys.sort(sortAlphaCaseless);
1621
1622
    // Create list of series with support of activation/deactivation
1623
    $.each(keys, function(index, key) {
1624
        var id = choiceContainer.attr('id') + index;
1625
        $('<li />')
1626
            .append($('<input id="' + id + '" name="' + key + '" type="checkbox" checked="checked" hidden />'))
1627
            .append($('<label />', { 'text': key , 'for': id }))
1628
            .appendTo(choiceContainer);
1629
    });
1630
    choiceContainer.find("label").click( function(){
1631
        if (this.style.color !== "rgb(129, 129, 129)" ){
1632
            this.style.color="#818181";
1633
        }else {
1634
            this.style.color="black";
1635
        }
1636
        $(this).parent().children().children().toggleClass("legend-disabled");
1637
    });
1638
    choiceContainer.find("label").mousedown( function(event){
1639
        event.preventDefault();
1640
    });
1641
    choiceContainer.find("label").mouseenter(function(){
1642
        this.style.cursor="pointer";
1643
    });
1644
1645
    // Recreate graphe on series activation toggle
1646
    choiceContainer.find("input").click(function(){
1647
        infos.createGraph();
1648
    });
1649
}
(-)bin/report-template/content/pages/CustomsGraphs.html.fmkr (+248 lines)
Line 0 Link Here
1
<!DOCTYPE html>
2
<html lang="en">
3
4
<head>
5
6
    <meta charset="utf-8">
7
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
8
    <meta name="viewport" content="width=device-width, initial-scale=1">
9
    <meta name="description" content="">
10
    <meta name="author" content="">
11
12
    <title>${reportTitle!"Apache JMeter Dashboard"}</title>
13
14
    <!-- Bootstrap Core CSS -->
15
    <link href="../../sbadmin2-1.0.7/bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
16
17
    <!-- icone onglet -->
18
    <link rel="icon" type="image/png" href="icon-apache.png" />
19
20
    <!-- MetisMenu CSS -->
21
    <link href="../../sbadmin2-1.0.7/bower_components/metisMenu/dist/metisMenu.min.css" rel="stylesheet">
22
23
    <!-- Legends CSS -->
24
    <link href="../css/legends.css" rel="stylesheet">
25
26
27
    <!-- Custom CSS -->
28
    <link href="../../sbadmin2-1.0.7/dist/css/sb-admin-2.css" rel="stylesheet">
29
30
    <!-- Custom Fonts -->
31
    <link href="../../sbadmin2-1.0.7/bower_components/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css">
32
33
   <!-- JQuery UI style -->
34
    <link href="../css/jquery-ui.css" rel="stylesheet">
35
    <link href="../css/jquery-ui.structure.css" rel="stylesheet">
36
    <link href="../css/jquery-ui.theme.css" rel="stylesheet">
37
</head>
38
39
<body>
40
41
    <div id="wrapper">
42
43
        <!-- Navigation -->
44
        <nav class="navbar navbar-default navbar-static-top" role="navigation" style="margin-bottom: 0">
45
            <div class="navbar-header">
46
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
47
                    <span class="sr-only">Toggle navigation</span>
48
                    <span class="icon-bar"></span>
49
                    <span class="icon-bar"></span>
50
                    <span class="icon-bar"></span>
51
                </button>
52
                <a class="navbar-brand" href="../../index.html">${reportTitle!"Apache JMeter Dashboard"}</a>
53
            </div>
54
            <!-- /.navbar-header -->
55
56
57
            <div class="navbar-default sidebar" role="navigation">
58
                <div class="sidebar-nav navbar-collapse">
59
                    <ul class="nav" id="side-menu">
60
61
                        <li>
62
                            <a href="../../index.html"><i class="fa fa-dashboard fa-fw"></i> Dashboard</a>
63
                        </li>
64
                        <li>
65
                            <a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Charts<span class="fa arrow"></span></a>
66
                            <ul class="nav nav-second-level">
67
                                <li>
68
                                    <a href="OverTime.html">Over Time</a>
69
70
                                </li>
71
                                <li>
72
                                    <a href="Throughput.html">Throughput</a>
73
                                </li>
74
                                <li>
75
                                    <a href="ResponseTimes.html">Response Times</a>
76
                                </li>
77
                
78
                            </ul>
79
                            <!-- /.nav-second-level -->
80
                        </li>
81
                        
82
                        <li>
83
                            <a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Customs Graphs<span class="fa arrow"></span></a>
84
                            <ul class="nav nav-second-level">
85
                                <li>
86
                                    <a href="CustomsGraphs.html">Over Time<span class="fa arrow"></span></a>
87
                                    <ul class="nav nav-third-level in" id="submenu">
88
                                        <#list customsGraphsData?keys as key>
89
                                            <li>
90
                                                <a href="CustomsGraphs.html#${key}" onclick="$('#bodyResponse${key}').collapse('show');">
91
                                                <#assign title = ""+graphConfigurations[key].getTitle()>
92
                                                <#if title == "">Default Graph Title
93
                                                <#else>${title}
94
                                                </#if>
95
                                                </a>
96
                                            </li>
97
                                        </#list>
98
                                    </ul>
99
                                <li>
100
                            </ul>
101
                        </li>
102
103
                    </ul>
104
                </div>
105
                <!-- /.sidebar-collapse -->
106
            </div>
107
            <!-- /.navbar-static-side -->
108
        </nav>
109
110
        <div id="page-wrapper">
111
            <div class="row">
112
                <div class="col-lg-12">
113
                     <div class="panel panel-default" >
114
                        <div class="panel-heading" style="text-align:center;">
115
                           <p class="dashboard-title">Test and Report informations</p>
116
                        </div>
117
                        <div class="panel-body">
118
                            <table id="generalInfos" class="table table-bordered table-condensed " >
119
                                <tr>
120
                                    <td>File:</td>
121
                                    <td>${testFile!""}</td>
122
                                </tr>
123
                                <tr>
124
                                    <td>Start Time:</td>
125
                                    <td>${beginDate!""}</td>
126
                                </tr>
127
                                <tr>
128
                                    <td>End Time:</td>
129
                                    <td>${endDate!""}</td>
130
                                </tr>
131
                                <#if overallFilter?has_content>
132
                                    <tr>
133
                                        <td>Filter for computing:</td>
134
                                        <td>${overallFilter}</td>
135
                                    </tr>
136
                                </#if>
137
                                <#if seriesFilter?has_content>
138
                                    <tr>
139
                                        <td>Filter for display:</td>
140
                                        <td>${seriesFilter}</td>
141
                                    </tr>
142
                                </#if>
143
                            </table>
144
                        </div>
145
                    </div>
146
                </div>
147
            </div>    
148
            <!-- /.row -->
149
                <div class="row" id="graphContainer">
150
                
151
                <#assign loopCount = 0>
152
                <#list customsGraphsData?keys as key> 
153
                    <div class="col-lg-12 portlet" id="${key!"Clé pas trouvé"}">
154
                        <div class="panel panel-default">
155
                            <div class="panel-heading portlet-header">
156
                                 <i  class="fa fa-bar-chart-o fa-fw" ></i> 
157
                                <span type="button" class="dropdown-toggle click-title span-title" data-toggle="collapse" href="#bodyResponse${key}" aria-expanded="true" aria-controls="body${key}"><#assign title = ""+graphConfigurations[key].getTitle()><#if title == "">Default Graph Title<#else>${title}</#if></span>
158
                                 <div class="pull-right">
159
                                    <div class="btn-group">
160
                                        <a class="btn btn-link btn-xs">
161
                                            <i class="glyphicon glyphicon-resize-vertical"></i>
162
                                        </a>
163
                                        <button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="dropdown">
164
                                            <i class="fa fa-wrench"></i>
165
                                        </button>
166
                                        <ul class="dropdown-menu dropdown-user">
167
                                            <li><a href="#bodyResponse${key}" onClick="checkAll('choicesResponse${key}');">Display all samples</a>
168
                                            </li>
169
                                            <li><a href="#bodyResponse${key}" onClick="uncheckAll('choicesResponse${key}');">Hide all samples</a>
170
                                            </li>
171
                                            <li><a href="#bodyResponse${key}" onclick="exportToPNG('flotResponse${key}', this);">Save as PNG</a></li>
172
                                        </ul>
173
                                        <button type="button" class="btn btn-link btn-xs dropdown-toggle" data-toggle="collapse" href="#bodyResponse${key}" aria-expanded="true" aria-controls="bodyResponse${key}">
174
                                            <i class="fa fa-chevron-up"></i>
175
                                        </button>
176
                                    </div>
177
                                </div>
178
                            </div>
179
                            <!-- /.panel-heading -->
180
                            <#if loopCount == 0>
181
                            <div class="collapse in portlet-content" id="bodyResponse${key}">
182
                            <#else>
183
                            <div class="collapse out portlet-content" id="bodyResponse${key}">
184
                            </#if>
185
                                <div class="panel-body" id="collapseResponse${key}">
186
                                    <div class="flot-chart">
187
                                        <div class="flot-chart-content" id="flotResponse${key}" style="float: left; width:80%;"></div>
188
                                        <div style="float:left;margin-left:5px">
189
                                            <p>Zoom :</p>
190
                                            <div id="overviewResponse${key}" style="width:190px;height:100px;"></div>
191
                                        </div>
192
                                    </div>
193
                                </div>
194
                                <div class="panel-footer" id="footerResponse${key}">
195
                                    <p id="legendResponse${key}" hidden></p>
196
                                    <ul id="choicesResponse${key}" class="legend"></ul>
197
                                </div>
198
                            </div>    
199
                        </div>
200
                        <!-- /.panel-body -->
201
                    </div>
202
                    <!-- /.panel -->
203
                    <#assign loopCount++>
204
                </#list>
205
                </div>
206
            <!-- /.row -->
207
        </div>
208
        <!-- /#page-wrapper -->
209
210
    </div>
211
    <!-- /#wrapper -->
212
213
     <!-- jQuery -->
214
    <script src="../../sbadmin2-1.0.7/bower_components/jquery/dist/jquery.min.js"></script>
215
216
    <!-- Bootstrap Core JavaScript -->
217
    <script src="../../sbadmin2-1.0.7/bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
218
219
    <!-- Metis Menu Plugin JavaScript -->
220
    <script src="../../sbadmin2-1.0.7/bower_components/metisMenu/dist/metisMenu.min.js"></script>
221
222
    <!-- Flot Charts JavaScript -->
223
    <script src="../../sbadmin2-1.0.7/bower_components/flot/excanvas.min.js"></script>
224
    <script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.js"></script>
225
    <script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.pie.js"></script>
226
    <script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.resize.js"></script>
227
    <script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.canvas.js"></script>
228
    <script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.navigate.js"></script>
229
    <script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.time.js"></script>
230
    <script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.selection.js"></script>
231
    <script src="../../sbadmin2-1.0.7/bower_components/flot.tooltip/js/jquery.flot.tooltip.min.js"></script>
232
    <script src="../../sbadmin2-1.0.7/bower_components/flot-axislabels/jquery.flot.axislabels.js"></script>
233
    <script src="../js/hashtable.js"></script>
234
    <script src="../js/jquery.numberformatter-1.2.3.min.js"></script>
235
    <script src="../js/curvedLines.js"></script>
236
    <script src="../js/dashboard-commons.js"></script>
237
    <script src="../js/customGraph.js"></script>
238
    <script src="../js/jquery-ui.js"></script>
239
    <script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.threshold.js"></script>
240
    <!-- Custom Theme JavaScript -->
241
    <script src="../../sbadmin2-1.0.7/dist/js/sb-admin-2.js"></script>
242
    <script src="../js/jquery.cookie.js"></script>
243
244
    <script src="../../sbadmin2-1.0.7/bower_components/flot/jquery.flot.symbol.js"></script>
245
246
</body>
247
248
</html>
(-)bin/report-template/content/pages/OverTime.html.fmkr (+9 lines)
Lines 109-114 Link Here
109
                            <!-- /.nav-second-level -->
109
                            <!-- /.nav-second-level -->
110
                        </li>
110
                        </li>
111
111
112
                        <li>
113
                            <a href="#"><i class="fa fa-bar-chart-o fa-fw"></i>Customs Graphs<span class="fa arrow"></span></a>
114
                            <ul class="nav nav-second-level">
115
                                <li>
116
                                    <a href="CustomsGraphs.html">Over Time</a>
117
                                </li>
118
                            </ul>
119
                        </li>
120
112
                    </ul>
121
                    </ul>
113
                </div>
122
                </div>
114
                <!-- /.sidebar-collapse -->
123
                <!-- /.sidebar-collapse -->
(-)bin/report-template/content/pages/ResponseTimes.html.fmkr (-1 / +8 lines)
Lines 99-105 Link Here
99
                            </ul>
99
                            </ul>
100
                            <!-- /.nav-second-level -->
100
                            <!-- /.nav-second-level -->
101
                        </li>
101
                        </li>
102
102
                        <li>
103
                            <a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Customs Graphs<span class="fa arrow"></span></a>
104
                            <ul class="nav nav-second-level">
105
                                <li>
106
                                    <a href="CustomsGraphs.html">Over Time</a>
107
                                </li>
108
                            </ul>
109
                        </li>
103
                    </ul>
110
                    </ul>
104
                </div>
111
                </div>
105
                <!-- /.sidebar-collapse -->
112
                <!-- /.sidebar-collapse -->
(-)bin/report-template/content/pages/Throughput.html.fmkr (+9 lines)
Lines 98-103 Link Here
98
                            <!-- /.nav-second-level -->
98
                            <!-- /.nav-second-level -->
99
                        </li>
99
                        </li>
100
100
101
                        <li>
102
                            <a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Customs Graphs<span class="fa arrow"></span></a>
103
                            <ul class="nav nav-second-level">
104
                                <li>
105
                                    <a href="CustomsGraphs.html">Over Time</a>
106
                                </li>
107
                            </ul>
108
                        </li>
109
101
                    </ul>
110
                    </ul>
102
                </div>
111
                </div>
103
                <!-- /.sidebar-collapse -->
112
                <!-- /.sidebar-collapse -->
(-)bin/report-template/index.html.fmkr (+9 lines)
Lines 66-71 Link Here
66
                            </ul>
66
                            </ul>
67
                            <!-- /.nav-second-level -->
67
                            <!-- /.nav-second-level -->
68
                        </li>
68
                        </li>
69
                        
70
                        <li>
71
                            <a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Customs Graphs<span class="fa arrow"></span></a>
72
                            <ul class="nav nav-second-level">
73
                                <li>
74
                                    <a href="content/pages/CustomsGraphs.html">Over Time</a>
75
                                </li>
76
                            </ul>
77
                        </li>
69
                    </ul>
78
                    </ul>
70
                </div>
79
                </div>
71
            </div>
80
            </div>
(-)bin/user.properties (+9 lines)
Lines 102-107 Link Here
102
#jmeter.reportgenerator.exported_transactions_pattern=[a-zA-Z0-9_\\-{}\\$\\.]*[-_][0-9]*
102
#jmeter.reportgenerator.exported_transactions_pattern=[a-zA-Z0-9_\\-{}\\$\\.]*[-_][0-9]*
103
103
104
104
105
## Custom graph definition
106
#jmeter.reportgenerator.graph.custom_mm_hit.classname=org.apache.jmeter.report.processor.graph.impl.CustomGraphConsumer
107
#jmeter.reportgenerator.graph.custom_mm_hit.title=Graph Title
108
#jmeter.reportgenerator.graph.custom_mm_hit.property.set_Y_Axis=Response Time (ms)
109
#jmeter.reportgenerator.graph.custom_mm_hit.property.set_X_Axis=Over Time
110
#jmeter.reportgenerator.graph.custom_mm_hit.property.set_granularity=${jmeter.reportgenerator.overall_granularity}
111
#jmeter.reportgenerator.graph.custom_mm_hit.property.setSampleVariableName=VarName
112
#jmeter.reportgenerator.graph.custom_mm_hit.property.setContentMessage=Message for graph point label
113
105
########################################################################
114
########################################################################
106
################## DISTRIBUTED TESTING CONFIGURATION  ##################
115
################## DISTRIBUTED TESTING CONFIGURATION  ##################
107
########################################################################
116
########################################################################
(-)src/core/org/apache/jmeter/report/dashboard/HtmlTemplateExporter.java (-5 / +14 lines)
Lines 20-25 Link Here
20
import java.io.File;
20
import java.io.File;
21
import java.io.IOException;
21
import java.io.IOException;
22
import java.nio.file.Files;
22
import java.nio.file.Files;
23
import java.util.HashMap;
23
import java.util.Map;
24
import java.util.Map;
24
import java.util.TimeZone;
25
import java.util.TimeZone;
25
import java.util.regex.Pattern;
26
import java.util.regex.Pattern;
Lines 444-466 Link Here
444
        EmptyGraphChecker checker = new EmptyGraphChecker(
445
        EmptyGraphChecker checker = new EmptyGraphChecker(
445
                filtersOnlySampleSeries, showControllerSeriesOnly,
446
                filtersOnlySampleSeries, showControllerSeriesOnly,
446
                filterPattern);
447
                filterPattern);
448
        DataContext customGraphs = new DataContext();
449
        Map<String, GraphConfiguration> mapConfiguration = new HashMap<>();
447
        for (Map.Entry<String, GraphConfiguration> graphEntry : configuration
450
        for (Map.Entry<String, GraphConfiguration> graphEntry : configuration
448
                .getGraphConfigurations().entrySet()) {
451
                .getGraphConfigurations().entrySet()) {
449
            final String graphId = graphEntry.getKey();
452
            final String graphId = graphEntry.getKey();
450
            final GraphConfiguration graphConfiguration = graphEntry.getValue();
453
            final GraphConfiguration graphConfiguration = graphEntry.getValue();
451
            final SubConfiguration extraOptions = exportCfg
454
            final SubConfiguration extraOptions = exportCfg.getGraphExtraConfigurations().get(graphId);
452
                    .getGraphExtraConfigurations().get(graphId);
453
455
454
            // Initialize customizer and checker
456
            // Initialize customizer and checker
455
            customizer.setExtraOptions(extraOptions);
457
            customizer.setExtraOptions(extraOptions);
456
            checker.setExcludesControllers(
458
            checker.setExcludesControllers(
457
                    graphConfiguration.excludesControllers());
459
                    graphConfiguration.excludesControllers());
458
            checker.setGraphId(graphId);
460
            checker.setGraphId(graphId);
459
461
            mapConfiguration.put(graphId, graphConfiguration);
460
            // Export graph data
462
            if(graphId.substring(0,7).equals("custom_")) {
461
            addResultToContext(graphId, storedData, dataContext, jsonizer,
463
                addResultToContext(graphId, storedData, customGraphs, jsonizer,
464
                        customizer, checker);
465
            } else {
466
                // Export graph data
467
                addResultToContext(graphId, storedData, dataContext, jsonizer,
462
                    customizer, checker);
468
                    customizer, checker);
469
            }
463
        }
470
        }
471
        dataContext.put("graphConfigurations", mapConfiguration);
472
        dataContext.put("customsGraphsData", customGraphs);
464
473
465
        // Replace the begin date with its formatted string and store the old
474
        // Replace the begin date with its formatted string and store the old
466
        // timestamp
475
        // timestamp
(-)src/core/org/apache/jmeter/report/dashboard/ReportGenerator.java (-8 / +4 lines)
Lines 235-244 Link Here
235
                .getGraphConfigurations();
235
                .getGraphConfigurations();
236
236
237
        // Process configuration to build graph consumers
237
        // Process configuration to build graph consumers
238
        for (Map.Entry<String, GraphConfiguration> entryGraphCfg : graphConfigurations
238
        for (Map.Entry<String, GraphConfiguration> entryGraphCfg : graphConfigurations.entrySet()) {
239
                .entrySet()) {
239
            addGraphConsumer(nameFilter, excludeControllerFilter, entryGraphCfg);
240
            addGraphConsumer(nameFilter, excludeControllerFilter,
241
                    entryGraphCfg);
242
        }
240
        }
243
241
244
        // Generate data
242
        // Generate data
Lines 353-358 Link Here
353
                setProperty(className, obj, methods, propertyName,
351
                setProperty(className, obj, methods, propertyName,
354
                        propertyValue, setterName);
352
                        propertyValue, setterName);
355
            }
353
            }
354
            graph.initialize(); 
356
355
357
            // Choose which entry point to use to plug the graph
356
            // Choose which entry point to use to plug the graph
358
            AbstractSampleConsumer entryPoint = graphConfiguration
357
            AbstractSampleConsumer entryPoint = graphConfiguration
Lines 383-393 Link Here
383
        } catch (ClassNotFoundException | IllegalAccessException
382
        } catch (ClassNotFoundException | IllegalAccessException
384
                | InstantiationException | ClassCastException ex) {
383
                | InstantiationException | ClassCastException ex) {
385
            String error = String.format(INVALID_CLASS_FMT, className);
384
            String error = String.format(INVALID_CLASS_FMT, className);
386
            log.error(error, ex);
387
            throw new GenerationException(error, ex);
385
            throw new GenerationException(error, ex);
388
        } catch (ExportException ex) {
386
        } catch (ExportException ex) {
389
            String error = String.format(INVALID_EXPORT_FMT, exporterName);
387
            String error = String.format(INVALID_EXPORT_FMT, exporterName);
390
            log.error(error, ex);
391
            throw new GenerationException(error, ex);
388
            throw new GenerationException(error, ex);
392
        }
389
        }
393
    }
390
    }
Lines 554-561 Link Here
554
                                                parameterType
551
                                                parameterType
555
                                                        .getName()));
552
                                                        .getName()));
556
                            }
553
                            }
557
                            method.invoke(obj, converter
554
                            method.invoke(obj, converter.convert(propertyValue));
558
                                    .convert(propertyValue));
559
                        }
555
                        }
560
                        return;
556
                        return;
561
                    }
557
                    }
(-)src/core/org/apache/jmeter/report/processor/graph/AbstractGraphConsumer.java (-4 / +6 lines)
Lines 90-99 Link Here
90
    public static final String DEFAULT_AGGREGATED_KEYS_SERIES_FORMAT = "%s-Aggregated";
90
    public static final String DEFAULT_AGGREGATED_KEYS_SERIES_FORMAT = "%s-Aggregated";
91
91
92
    /** The map used to store group information. */
92
    /** The map used to store group information. */
93
    private final HashMap<String, GroupInfo> groupInfos;
93
    private HashMap<String, GroupInfo> groupInfos;
94
94
95
    /** The keys selector. */
95
    /** The keys selector. */
96
    private final GraphKeysSelector keysSelector;
96
    private GraphKeysSelector keysSelector;
97
97
98
    /** The overall seriesData name. */
98
    /** The overall seriesData name. */
99
    private String overallSeriesFormat = DEFAULT_OVERALL_SERIES_FORMAT;
99
    private String overallSeriesFormat = DEFAULT_OVERALL_SERIES_FORMAT;
Lines 228-235 Link Here
228
     * Instantiates a new abstract graph consumer.
228
     * Instantiates a new abstract graph consumer.
229
     */
229
     */
230
    protected AbstractGraphConsumer() {
230
    protected AbstractGraphConsumer() {
231
        keysSelector = createKeysSelector();
232
        groupInfos = new HashMap<>(createGroupInfos());
233
    }
231
    }
234
232
235
    protected abstract GraphKeysSelector createKeysSelector();
233
    protected abstract GraphKeysSelector createKeysSelector();
Lines 591-594 Link Here
591
        }
589
        }
592
    }
590
    }
593
591
592
    public void initialize() {
593
        keysSelector = createKeysSelector();
594
        groupInfos = new HashMap<>(createGroupInfos());
595
    }
594
}
596
}
(-)src/core/org/apache/jmeter/report/processor/graph/AbstractOverTimeGraphConsumer.java (-7 / +6 lines)
Lines 51-70 Link Here
51
     *            the granularity to set
51
     *            the granularity to set
52
     */
52
     */
53
    public void setGranularity(long granularity) {
53
    public void setGranularity(long granularity) {
54
        _setGranularity(granularity);
55
    }
56
57
    // Called from ctor
58
    private final void _setGranularity(long granularity) {
59
        this.granularity = granularity;
54
        this.granularity = granularity;
60
        ((TimeStampKeysSelector) getKeysSelector()).setGranularity(granularity);
61
    }
55
    }
62
56
63
    /**
57
    /**
64
     * Instantiates a new abstract over time graph consumer.
58
     * Instantiates a new abstract over time graph consumer.
65
     */
59
     */
66
    protected AbstractOverTimeGraphConsumer() {
60
    protected AbstractOverTimeGraphConsumer() {
67
        _setGranularity(1L);
68
    }
61
    }
69
62
70
    /**
63
    /**
Lines 107-110 Link Here
107
        parentResult.setResult(RESULT_CTX_GRANULARITY, new ValueResultData(
100
        parentResult.setResult(RESULT_CTX_GRANULARITY, new ValueResultData(
108
                Long.valueOf(granularity)));
101
                Long.valueOf(granularity)));
109
    }
102
    }
103
    
104
    @Override
105
    public void initialize() {
106
        super.initialize();
107
        ((TimeStampKeysSelector) getKeysSelector()).setGranularity(granularity);
108
    }
110
}
109
}
(-)src/core/org/apache/jmeter/report/processor/graph/AbstractVersusRequestsGraphConsumer.java (-3 / +8 lines)
Lines 53-59 Link Here
53
     * The embedded time count consumer is used to buffer (disk storage) and tag
53
     * The embedded time count consumer is used to buffer (disk storage) and tag
54
     * samples with the number of samples in the same interval.
54
     * samples with the number of samples in the same interval.
55
     */
55
     */
56
    private final TimeCountConsumer embeddedConsumer;
56
    private TimeCountConsumer embeddedConsumer;
57
57
58
    /**
58
    /**
59
     * Gets the granularity.
59
     * Gets the granularity.
Lines 79-86 Link Here
79
     * Instantiates a new abstract over time graph consumer.
79
     * Instantiates a new abstract over time graph consumer.
80
     */
80
     */
81
    protected AbstractVersusRequestsGraphConsumer() {
81
    protected AbstractVersusRequestsGraphConsumer() {
82
        embeddedConsumer = new TimeCountConsumer(this);
83
        setGranularity(1L);
84
    }
82
    }
85
83
86
    /*
84
    /*
Lines 95-100 Link Here
95
        embeddedConsumer.startConsuming();
93
        embeddedConsumer.startConsuming();
96
    }
94
    }
97
95
96
    @Override
97
    public void initialize() {
98
        super.initialize();
99
        embeddedConsumer = new TimeCountConsumer(this);
100
        setGranularity(1L);
101
    }
102
98
    private void startConsumingBase() {
103
    private void startConsumingBase() {
99
        super.startConsuming();
104
        super.startConsuming();
100
    }
105
    }
(-)src/core/org/apache/jmeter/report/processor/graph/impl/BytesThroughputGraphConsumer.java (-1 / +6 lines)
Lines 101-109 Link Here
101
    @Override
101
    @Override
102
    public void setGranularity(long granularity) {
102
    public void setGranularity(long granularity) {
103
        super.setGranularity(granularity);
103
        super.setGranularity(granularity);
104
    }
105
106
    @Override
107
    public void initialize() {
108
        super.initialize();
104
        // Override the granularity of the aggregators factory
109
        // Override the granularity of the aggregators factory
105
        ((TimeRateAggregatorFactory) getGroupInfos().get(
110
        ((TimeRateAggregatorFactory) getGroupInfos().get(
106
                AbstractGraphConsumer.DEFAULT_GROUP).getAggregatorFactory())
111
                AbstractGraphConsumer.DEFAULT_GROUP).getAggregatorFactory())
107
                .setGranularity(granularity);
112
                .setGranularity(getGranularity());
108
    }
113
    }
109
}
114
}
(-)src/core/org/apache/jmeter/report/processor/graph/impl/CodesPerSecondGraphConsumer.java (-4 / +9 lines)
Lines 66-71 Link Here
66
        return groupInfos;
66
        return groupInfos;
67
    }
67
    }
68
68
69
    @Override
70
    public void initialize() {
71
        super.initialize();
72
        // Override the granularity of the aggregators factory
73
        ((TimeRateAggregatorFactory) getGroupInfos().get(
74
                AbstractGraphConsumer.DEFAULT_GROUP).getAggregatorFactory())
75
                .setGranularity(getGranularity());
76
    }
77
69
    /*
78
    /*
70
     * (non-Javadoc)
79
     * (non-Javadoc)
71
     * 
80
     * 
Lines 76-84 Link Here
76
    @Override
85
    @Override
77
    public void setGranularity(long granularity) {
86
    public void setGranularity(long granularity) {
78
        super.setGranularity(granularity);
87
        super.setGranularity(granularity);
79
        // Override the granularity of the aggregators factory
80
        ((TimeRateAggregatorFactory) getGroupInfos().get(
81
                AbstractGraphConsumer.DEFAULT_GROUP).getAggregatorFactory())
82
                .setGranularity(granularity);
83
    }
88
    }
84
}
89
}
(-)src/core/org/apache/jmeter/report/processor/graph/impl/CustomGraphConsumer.java (+238 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *   http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
package org.apache.jmeter.report.processor.graph.impl;
19
20
import java.util.Arrays;
21
import java.util.HashMap;
22
import java.util.Map;
23
24
import org.apache.commons.lang3.StringUtils;
25
import org.apache.jmeter.report.core.ConvertException;
26
import org.apache.jmeter.report.core.Converters;
27
import org.apache.jmeter.report.core.Sample;
28
import org.apache.jmeter.report.processor.MapResultData;
29
import org.apache.jmeter.report.processor.SampleConsumer;
30
import org.apache.jmeter.report.processor.TimeRateAggregatorFactory;
31
import org.apache.jmeter.report.processor.ValueResultData;
32
import org.apache.jmeter.report.processor.graph.AbstractGraphConsumer;
33
import org.apache.jmeter.report.processor.graph.AbstractOverTimeGraphConsumer;
34
import org.apache.jmeter.report.processor.graph.AbstractSeriesSelector;
35
import org.apache.jmeter.report.processor.graph.GraphValueSelector;
36
import org.apache.jmeter.report.processor.graph.GroupInfo;
37
import org.apache.jmeter.report.processor.graph.TimeStampKeysSelector;
38
39
/**
40
 * The class CustomGraphConsumer is added by the custom Graphs plugin.
41
 * It provides all the graphs the user defined in user.properties.
42
 *
43
 * @since 4.1
44
 */
45
46
public class CustomGraphConsumer extends AbstractOverTimeGraphConsumer implements SampleConsumer{
47
    
48
    public static final String RESULT_Y_AXIS = "Y_Axis"; //$NON-NLS-1$
49
    public static final String RESULT_X_AXIS = "X_Axis"; //$NON-NLS-1$
50
    public static final String RESULT_SAMPLE_VARIABLE_NAME = "sample_Metric_Name"; //$NON-NLS-1$
51
    public static final String RESULT_CONTENT_MESSAGE = "content_Message"; //$NON-NLS-1$
52
    public static final String REPORT_GENERATOR_PROPERTIES = "jmeter.reportgenerator.graph.customGraph.property"; //$NON-NLS-1$
53
54
    private String yAxis;
55
    private String xAxis;
56
    private String contentMessage;
57
    private String sampleVariableName;
58
    private boolean isNativeSampleVariableName = false;
59
    
60
    /**
61
     * Only used for junit tests.
62
     * Indicates if the sampleVariableName 
63
     * is native
64
     * 
65
     * @return the nativeSampleVariableName
66
     */
67
    public boolean getIsNativeSampleVariableName() {
68
        return isNativeSampleVariableName;
69
    }
70
71
    /**
72
     * Gets the Y Axis.
73
     *
74
     * @return the yAxis
75
     */
76
    public String getYAxis() {
77
        return yAxis;
78
    }
79
    
80
    /**
81
     * Gets the X Axis.
82
     *
83
     * @return the xAxis
84
     */
85
    public String getXAxis() {
86
        return xAxis;
87
    }
88
89
    /**
90
     * Sets the yAxis.
91
     *
92
     * @param axis
93
     * the yAxis to set
94
     */
95
    public void setYAxis(String axis) {
96
        yAxis=axis;
97
    }
98
    
99
    /**
100
     * Sets the xAxis.
101
     *
102
     * @param axis
103
     * the xAxis to set
104
     */
105
    public void setXAxis(String axis) {
106
        xAxis=axis;
107
    }
108
    
109
    /**
110
     * Sets the contentMessage.
111
     *
112
     * @param message
113
     * the message to set
114
     */
115
    public void setContentMessage(String message) {
116
        contentMessage=message;
117
    }
118
    
119
    /**
120
     * Gets the content message.
121
     *
122
     * @return the contentMessage
123
     */
124
    public String getContentMessage() {
125
        return contentMessage;
126
    }
127
    
128
    /**
129
     * Gets the sampleVariableName.
130
     *
131
     * @return the sampleVariableName
132
     */
133
    public String getSampleVariableName() {
134
        return sampleVariableName;
135
    }
136
    
137
    /**
138
     * Sets the sampleVariableName.
139
     * Sets the boolean isNativesSampleVariableName
140
     *
141
     * @param sampleVarName
142
     * the sampleVariableName to set
143
     */
144
    public void setSampleVariableName(String sampleVarName) {
145
        sampleVariableName = sampleVarName;
146
        // this if contains every native sample variables names
147
        if(sampleVarName.equals("timeStamp") || sampleVarName.equals("elapsed") 
148
                || sampleVarName.equals("label") || sampleVarName.equals("responseCode") 
149
                || sampleVarName.equals("threadName") || sampleVarName.equals("success") 
150
                || sampleVarName.equals("failureMessage") || sampleVarName.equals("bytes") 
151
                || sampleVarName.equals("sentBytes") || sampleVarName.equals("grpThreads") 
152
                || sampleVarName.equals("allThreads") || sampleVarName.equals("URL") 
153
                || sampleVarName.equals("Latency") || sampleVarName.equals("IdleTime") 
154
                || sampleVarName.equals("Connect")) {
155
            isNativeSampleVariableName = true;
156
        }else {
157
            isNativeSampleVariableName = false;
158
        }
159
    }
160
    
161
        
162
    @Override
163
    protected void initializeExtraResults(MapResultData parentResult) {
164
        parentResult.setResult(RESULT_CTX_GRANULARITY, new ValueResultData(Long.valueOf(getGranularity())));
165
        parentResult.setResult(RESULT_Y_AXIS, new ValueResultData(getYAxis()));
166
        parentResult.setResult(RESULT_X_AXIS, new ValueResultData(getXAxis()));
167
        parentResult.setResult(RESULT_SAMPLE_VARIABLE_NAME, new ValueResultData(getSampleVariableName()));
168
        parentResult.setResult(RESULT_CONTENT_MESSAGE, new ValueResultData(getContentMessage()));
169
    }
170
    
171
    /*
172
     * (non-Javadoc)
173
     * 
174
     * @see
175
     * org.apache.jmeter.report.csv.processor.impl.AbstractOverTimeGraphConsumer
176
     * #createTimeStampKeysSelector()
177
     */
178
    @Override
179
    protected TimeStampKeysSelector createTimeStampKeysSelector() {
180
        TimeStampKeysSelector keysSelector = new TimeStampKeysSelector();
181
        keysSelector.setSelectBeginTime(false);
182
        return keysSelector;
183
    }
184
185
    /*
186
     * (non-Javadoc)
187
     * 
188
     * @see org.apache.jmeter.report.csv.processor.impl.AbstractGraphConsumer#
189
     * createGroupInfos()
190
     */
191
    @Override
192
    protected Map<String, GroupInfo> createGroupInfos() {
193
        
194
        HashMap<String, GroupInfo> groupInfos = new HashMap<>(); 
195
        groupInfos.put(AbstractGraphConsumer.DEFAULT_GROUP,
196
                new GroupInfo(
197
                new TimeRateAggregatorFactory(), 
198
                new AbstractSeriesSelector() {
199
                    private final Iterable<String> values = Arrays.asList(sampleVariableName);
200
201
                  @Override
202
                  public Iterable<String> select(Sample sample) {
203
                      return values;
204
                  }
205
                },
206
                // We ignore Transaction Controller results
207
                new GraphValueSelector() {
208
                  @Override
209
                  public Double select(String series, Sample sample) {
210
                      String value="";
211
                      if(isNativeSampleVariableName) {
212
                          value = sample.getData(sampleVariableName);
213
                      }else {
214
                          value = sample.getData("\""+sampleVariableName+"\"");
215
                      }
216
                      if(StringUtils.isEmpty(value) || value.equals("null")) {
217
                          return null;
218
                      }
219
                      else {
220
                          try {
221
                            return Converters.convert(Double.class, value);
222
                        } catch (ConvertException e) {
223
                            throw new IllegalArgumentException("Double converter failed : {}",e);
224
                        }
225
                      }
226
              }}, false, false));
227
        return groupInfos;
228
    }
229
    
230
    @Override
231
    public void initialize() {
232
        super.initialize();
233
        // Override the granularity of the aggregators factory
234
        ((TimeRateAggregatorFactory) getGroupInfos().get(
235
                AbstractGraphConsumer.DEFAULT_GROUP).getAggregatorFactory())
236
                .setGranularity(getGranularity());
237
    }
238
}
(-)src/core/org/apache/jmeter/report/processor/graph/impl/HitsPerSecondGraphConsumer.java (-5 / +11 lines)
Lines 76-85 Link Here
76
    @Override
76
    @Override
77
    public void setGranularity(long granularity) {
77
    public void setGranularity(long granularity) {
78
        super.setGranularity(granularity);
78
        super.setGranularity(granularity);
79
        
80
    }
81
82
    @Override
83
    public void initialize() {
84
        super.initialize();
79
        // Override the granularity of the aggregators factory
85
        // Override the granularity of the aggregators factory
80
        ((TimeRateAggregatorFactory) getGroupInfos().get(
86
        ((TimeRateAggregatorFactory) getGroupInfos().get(
81
                AbstractGraphConsumer.DEFAULT_GROUP).getAggregatorFactory())
87
                AbstractGraphConsumer.DEFAULT_GROUP).getAggregatorFactory())
82
                .setGranularity(granularity);
88
                .setGranularity(getGranularity());
89
        // Override the series name with the name of the graph
90
        ((StaticSeriesSelector) getGroupInfos().get(
91
                AbstractGraphConsumer.DEFAULT_GROUP).getSeriesSelector())
92
                .setSeriesName(getName());
83
    }
93
    }
84
94
85
    /*
95
    /*
Lines 92-100 Link Here
92
    @Override
102
    @Override
93
    public void setName(String name) {
103
    public void setName(String name) {
94
        super.setName(name);
104
        super.setName(name);
95
        // Override the series name with the name of the graph
96
        ((StaticSeriesSelector) getGroupInfos().get(
97
                AbstractGraphConsumer.DEFAULT_GROUP).getSeriesSelector())
98
                .setSeriesName(name);
99
    }
105
    }
100
}
106
}
(-)src/core/org/apache/jmeter/report/processor/graph/impl/ResponseCustomGraphGraphConsumer.java (+69 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *   http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
package org.apache.jmeter.report.processor.graph.impl;
19
20
import java.util.HashMap;
21
import java.util.Map;
22
23
import org.apache.jmeter.report.processor.MeanAggregatorFactory;
24
import org.apache.jmeter.report.processor.graph.AbstractGraphConsumer;
25
import org.apache.jmeter.report.processor.graph.AbstractOverTimeGraphConsumer;
26
import org.apache.jmeter.report.processor.graph.ElapsedTimeValueSelector;
27
import org.apache.jmeter.report.processor.graph.GroupInfo;
28
import org.apache.jmeter.report.processor.graph.NameSeriesSelector;
29
import org.apache.jmeter.report.processor.graph.TimeStampKeysSelector;
30
31
/**
32
 * The class ResponseCustomGraphGraphConsumer provides a graph to visualize mean 
33
 * custom value per time period (defined by granularity)
34
 *
35
 * @since 4.1
36
 */
37
public class ResponseCustomGraphGraphConsumer extends
38
        AbstractOverTimeGraphConsumer {
39
40
    /*
41
     * (non-Javadoc)
42
     * 
43
     * @see
44
     * org.apache.jmeter.report.csv.processor.impl.AbstractOverTimeGraphConsumer
45
     * #createTimeStampKeysSelector()
46
     */
47
    @Override
48
    protected TimeStampKeysSelector createTimeStampKeysSelector() {
49
        TimeStampKeysSelector keysSelector = new TimeStampKeysSelector();
50
        keysSelector.setSelectBeginTime(false);
51
        return keysSelector;
52
    }
53
54
    /*
55
     * (non-Javadoc)
56
     * 
57
     * @see org.apache.jmeter.report.csv.processor.impl.AbstractGraphConsumer#
58
     * createGroupInfos()
59
     */
60
    @Override
61
    protected Map<String, GroupInfo> createGroupInfos() {
62
        HashMap<String, GroupInfo> groupInfos = new HashMap<>(1);
63
        groupInfos.put(AbstractGraphConsumer.DEFAULT_GROUP, new GroupInfo(
64
                new MeanAggregatorFactory(), new NameSeriesSelector(),
65
                // We include Transaction Controller results
66
                new ElapsedTimeValueSelector(false), false, false));
67
        return groupInfos;
68
    }
69
}
(-)src/core/org/apache/jmeter/report/processor/graph/impl/ResponseTimePerSampleGraphConsumer.java (+5 lines)
Lines 47-52 Link Here
47
     * Instantiates a new response time per sample graph consumer.
47
     * Instantiates a new response time per sample graph consumer.
48
     */
48
     */
49
    public ResponseTimePerSampleGraphConsumer() {
49
    public ResponseTimePerSampleGraphConsumer() {
50
    }
51
52
    @Override
53
    public void initialize() {
54
        super.initialize();
50
        setRevertKeysAndValues(true);
55
        setRevertKeysAndValues(true);
51
    }
56
    }
52
57
(-)src/core/org/apache/jmeter/report/processor/graph/impl/ResponseTimePercentilesGraphConsumer.java (+5 lines)
Lines 41-46 Link Here
41
     * Instantiates a new response time percentiles graph consumer.
41
     * Instantiates a new response time percentiles graph consumer.
42
     */
42
     */
43
    public ResponseTimePercentilesGraphConsumer() {
43
    public ResponseTimePercentilesGraphConsumer() {
44
    }
45
46
    @Override
47
    public void initialize() {
48
        super.initialize();
44
        setRenderPercentiles(true);
49
        setRenderPercentiles(true);
45
    }
50
    }
46
51
(-)src/core/org/apache/jmeter/report/processor/graph/impl/TotalTPSGraphConsumer.java (-3 / +8 lines)
Lines 91-99 Link Here
91
    @Override
91
    @Override
92
    public void setGranularity(long granularity) {
92
    public void setGranularity(long granularity) {
93
        super.setGranularity(granularity);
93
        super.setGranularity(granularity);
94
        // Override the granularity of the aggregators factory
95
        ((TimeRateAggregatorFactory) getGroupInfos().get(AbstractGraphConsumer.DEFAULT_GROUP).getAggregatorFactory())
96
                .setGranularity(granularity);
97
    }
94
    }
98
    
95
    
99
    @Override
96
    @Override
Lines 105-110 Link Here
105
        initializeSeries(parentResult, seriesLabels);
102
        initializeSeries(parentResult, seriesLabels);
106
    }
103
    }
107
    
104
    
105
    @Override
106
    public void initialize() {
107
        super.initialize();
108
        // Override the granularity of the aggregators factory
109
        ((TimeRateAggregatorFactory) getGroupInfos().get(AbstractGraphConsumer.DEFAULT_GROUP).getAggregatorFactory())
110
        .setGranularity(getGranularity());
111
    }
112
    
108
113
109
    private void initializeSeries(MapResultData parentResult, String[] series) {
114
    private void initializeSeries(MapResultData parentResult, String[] series) {
110
        ListResultData listResultData = (ListResultData) parentResult.getResult("series");
115
        ListResultData listResultData = (ListResultData) parentResult.getResult("series");
(-)src/core/org/apache/jmeter/report/processor/graph/impl/TransactionsPerSecondGraphConsumer.java (-4 / +9 lines)
Lines 84-89 Link Here
84
        return groupInfos;
84
        return groupInfos;
85
    }
85
    }
86
86
87
    @Override
88
    public void initialize() {
89
        super.initialize();
90
        // Override the granularity of the aggregators factory
91
        ((TimeRateAggregatorFactory) getGroupInfos().get(
92
                AbstractGraphConsumer.DEFAULT_GROUP).getAggregatorFactory())
93
                .setGranularity(getGranularity());
94
    }
95
87
    /*
96
    /*
88
     * (non-Javadoc)
97
     * (non-Javadoc)
89
     * 
98
     * 
Lines 94-103 Link Here
94
    @Override
103
    @Override
95
    public void setGranularity(long granularity) {
104
    public void setGranularity(long granularity) {
96
        super.setGranularity(granularity);
105
        super.setGranularity(granularity);
97
        // Override the granularity of the aggregators factory
98
        ((TimeRateAggregatorFactory) getGroupInfos().get(
99
                AbstractGraphConsumer.DEFAULT_GROUP).getAggregatorFactory())
100
                .setGranularity(granularity);
101
    }
106
    }
102
107
103
}
108
}
(-)test/src/org/apache/jmeter/report/processor/graph/impl/CustomGraphConsumerTest.java (+182 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *   http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
package org.apache.jmeter.report.processor.graph.impl;
19
20
import static org.hamcrest.core.IsEqual.equalTo;
21
import static org.junit.Assert.assertThat;
22
23
import java.util.HashMap;
24
import java.util.Map;
25
import java.util.Map.Entry;
26
27
import org.apache.jmeter.report.core.Sample;
28
import org.apache.jmeter.report.core.SampleMetadata;
29
import org.apache.jmeter.report.dashboard.JsonizerVisitor;
30
import org.apache.jmeter.report.processor.MapResultData;
31
import org.apache.jmeter.report.processor.ResultData;
32
import org.apache.jmeter.report.processor.graph.GroupData;
33
import org.apache.jmeter.report.processor.graph.GroupInfo;
34
import org.apache.jmeter.report.processor.graph.SeriesData;
35
import org.apache.jmeter.report.processor.graph.TimeStampKeysSelector;
36
import org.apache.jmeter.save.CSVSaveService;
37
import org.junit.Before;
38
import org.junit.Test;
39
40
public class CustomGraphConsumerTest {
41
    
42
    private CustomGraphConsumer customGraphConsumer;
43
    private MapResultData resultData;
44
    private TimeStampKeysSelector keysSelector;
45
    private Map<String, GroupInfo> map;
46
    // data array can't be initialized in the init()
47
    private String[] data = {"1527089951383", "0", "Read-compute", "200", "OK", "setupRegion 1-1", "true", "", "492", "0", "1", "1",
48
            "null", "0", "0", "0", "/stream1a/master.m3u8?e=0&h=56345c61b7b415e0260c19963a153092", "null", "5500000", "null",
49
            "null", "null", "null", "null", "null", "null", "null", "null", "null", "null", "null", "null", "null", "null"}; 
50
    private SampleMetadata sampleMetaData = createTestMetaData();
51
    
52
    @Before
53
    public void init() {
54
        customGraphConsumer = new CustomGraphConsumer();
55
        customGraphConsumer.setGranularity(60000);
56
        customGraphConsumer.setTitle("graph title");
57
        customGraphConsumer.setXAxis("X axis name");
58
        customGraphConsumer.setYAxis("Y axis name");
59
        customGraphConsumer.setContentMessage("content message");
60
        customGraphConsumer.setSampleVariableName("responseMessage");
61
        
62
        map = customGraphConsumer.createGroupInfos();
63
        
64
        resultData = new MapResultData();
65
    }
66
67
    @Test
68
    public void testGetters() {
69
        assertThat(customGraphConsumer.getTitle(), equalTo("graph title"));
70
        assertThat(customGraphConsumer.getXAxis(), equalTo("X axis name"));
71
        assertThat(customGraphConsumer.getYAxis(), equalTo("Y axis name"));
72
        assertThat(customGraphConsumer.getContentMessage(), equalTo("content message"));
73
        assertThat(customGraphConsumer.getSampleVariableName(), equalTo(CSVSaveService.RESPONSE_MESSAGE));
74
        assertThat(customGraphConsumer.getIsNativeSampleVariableName(), equalTo(false));
75
        
76
        // bytes is one of the native sample variables names
77
        customGraphConsumer.setSampleVariableName("bytes");
78
        assertThat(customGraphConsumer.getIsNativeSampleVariableName(), equalTo(true));
79
    }
80
    
81
    
82
    @Test
83
    public void testInitializeExtraResults() {
84
        customGraphConsumer.initializeExtraResults(resultData);
85
        
86
        JsonizerVisitor jsonizer = new JsonizerVisitor();
87
        for(Entry<String, ResultData> entrySet : resultData.entrySet()) {
88
            Object testedValue = entrySet.getValue().accept(jsonizer);
89
            String key = entrySet.getKey();
90
            
91
            if(key.equals("granularity")) {
92
                assertThat(testedValue, equalTo("60000"));
93
            }else if(key.equals("X_Axis")) {
94
                assertThat(testedValue, equalTo("\"X axis name\""));
95
            }else if(key.equals("Y_Axis")) {
96
                assertThat(testedValue, equalTo("\"Y axis name\""));
97
            }else if(key.equals("sample_Metric_Name")) {
98
                assertThat(testedValue, equalTo("\"responseMessage\""));
99
            }else if(key.equals("content_Message")) {
100
                assertThat(testedValue, equalTo("\"content message\""));
101
            }
102
        }
103
    }
104
    
105
    @Test
106
    public void testCreateTimeStampKeysSelector() {
107
        keysSelector = new TimeStampKeysSelector();
108
        keysSelector.setSelectBeginTime(false);
109
        assertThat(customGraphConsumer.createTimeStampKeysSelector().getGranularity(), equalTo(keysSelector.getGranularity()));
110
    }
111
    
112
    @Test
113
    public void testCreateGroupInfos() {
114
        // Testing defaults values
115
        assertThat(map.containsKey("Generic group"), equalTo(true));
116
        assertThat(map.containsKey("foo"), equalTo(false));
117
        assertThat(map.get("Generic group").getAggregatorFactory().getClass().toString(), 
118
                equalTo("class org.apache.jmeter.report.processor.TimeRateAggregatorFactory"));
119
        GroupData groupData = map.get("Generic group").getGroupData();
120
        assertThat(groupData.getOverallSeries(), equalTo(null));
121
        assertThat(groupData.getSeriesInfo(), equalTo(new HashMap<String, SeriesData>()));
122
        
123
        // Testing native sample variable
124
        customGraphConsumer.setSampleVariableName("bytes");
125
        Sample sample = new Sample(0, sampleMetaData, data);
126
        Double testedValue = map.get("Generic group").getValueSelector().select("bytes", sample);
127
        assertThat(testedValue, equalTo((Double) 492.0));
128
        
129
        // Testing non-native sample variable
130
        customGraphConsumer.setSampleVariableName("mm-miss");
131
        testedValue = map.get("Generic group").getValueSelector().select("mm-miss", sample);
132
        assertThat(testedValue, equalTo(null));
133
        
134
        // Testing empty data value, the change between data and data2
135
        // is on the last value that switchs from "null" to ""
136
        String[] data2 = {"1527089951383", "0", "Read-compute", "200", "OK", "setupRegion 1-1", "true", "", "492", "0", "1", "1",
137
                "null", "0", "0", "0", "/stream1a/master.m3u8?e=0&h=56345c61b7b415e0260c19963a153092", "null", "5500000", "null",
138
                "null", "null", "null", "null", "null", "null", "null", "null", "null", "null", "null", "null", "null", ""};
139
        sample = new Sample(0, sampleMetaData, data2);
140
        testedValue = map.get("Generic group").getValueSelector().select("mm-miss", sample);
141
        assertThat(testedValue, equalTo(null));
142
    }
143
144
    // Test the exception when the column data is not a Double
145
    @Test(expected=IllegalArgumentException.class)
146
    public void testCreateGroupInfosExceptions() {
147
        Sample sample = new Sample(0, sampleMetaData, data);
148
        customGraphConsumer.setSampleVariableName("label");
149
        
150
        // The following line is giving the exception
151
        map.get("Generic group").getValueSelector().select("label", sample);
152
    }
153
    
154
    @Test
155
    public void testSelectMetric() {
156
        Sample sample = new Sample(0, sampleMetaData, data);
157
        String testString = map.get("Generic group").getSeriesSelector().select(sample).toString();
158
        assertThat(testString, equalTo("[responseMessage]"));
159
    }
160
    
161
    // Create a static SampleMetadatObject
162
    private SampleMetadata createTestMetaData() {
163
        String columnsString = "timeStamp,elapsed,label,responseCode,responseMessage,threadName,success,failureMessage,bytes,sentBytes,"
164
                + "grpThreads,allThreads,URL,Latency,IdleTime,Connect,\"stream\",\"aws_region\",\"bitrate\",\"ulp_buffer_fill\",\"ulp_lag_time\","
165
                + "\"ulp_play_time\",\"ulp_lag_ratio\",\"lag_ratio_wo_bf\",\"ulp_dwn_time\",\"ulp_hits\",\"ulp_avg_chunk_time\","
166
                + "\"ulp_avg_manifest_time\",\"mm-hit\",\"mm-miss\",\"cm-hit\",\"cm-miss\",\"ts-hit\",\"ts-miss\"";
167
        String[] columns = new String[34];
168
        int lastComa = 0;
169
        int columnIndex = 0;
170
        for(int i = 0; i < columnsString.length(); i++) {
171
            if (columnsString.charAt(i)==',') {
172
                columns[columnIndex] = columnsString.substring(lastComa, i);
173
                lastComa=i+1;
174
                columnIndex++;
175
            }else if(i+1 == columnsString.length()) {
176
                columns[columnIndex] = columnsString.substring(lastComa, i+1);
177
            }
178
        }
179
        return new SampleMetadata(',',columns);
180
    }
181
    
182
}
(-)xdocs/usermanual/generating-dashboard.xml (-1 / +30 lines)
Lines 705-711 Link Here
705
                    </tbody>
705
                    </tbody>
706
                </table>
706
                </table>
707
            </subsection>
707
            </subsection>
708
            <subsection name="&sect-num;.5  Want to improve Report Dashboard ?" anchor="development">
708
            <subsection name="&sect-num;.5 Generating customs graphs over time" anchor="customs_graphs">
709
                <p>
710
                            If you have the plugin that allow it, you can customize your graphs
711
                            by settings their properties in the user.properties file.
712
                            <br />
713
                            They must use the prefix:
714
                            <source>jmeter.reportgenerator.graph.custom_&lt;your_graph_name_id&gt;.property.&lt;your_option_name&gt;</source>
715
                            To specify that this graph is a customized one :
716
                            <source>jmeter.reportgenerator.graph.custom_&lt;your_graph_name_id&gt;.classname=org.apache.jmeter.report.processor.graph.impl.CustomGraphConsumer</source>
717
                        </p>
718
                        <properties>
719
                            <property name="set_X_Axis" required="No">Sets the X axis name of the graph.</property>
720
                            <property name="set_Y_Axis" required="No">Sets the Y axis name of the graph.</property>
721
                            <property name="set_Content_Message" required="No">Sets the displayed message when the cursor is on a point of the graph.</property>
722
                            <property name="set_Sample_Variable_Name" required="Yes">Name of the columnyou want to graph on the csv.</property>
723
                        </properties>
724
                        
725
                        <p>Here is an exemple of a custom graph configuration : </p>
726
                        <source>
727
jmeter.reportgenerator.graph.custom_testGraph.classname=org.apache.jmeter.report.processor.graph.impl.CustomGraphConsumer
728
jmeter.reportgenerator.graph.custom_testGraph.title=Chunk Hit
729
jmeter.reportgenerator.graph.custom_testGraph.property.set_Y_Axis=Number of Hits
730
jmeter.reportgenerator.graph.custom_testGraph.set_X_Axis=Over Time
731
jmeter.reportgenerator.graph.custom_testGraph.property.set_granularity=60000
732
jmeter.reportgenerator.graph.custom_testGraph.property.set_Sample_Variable_Name=ts-hit
733
jmeter.reportgenerator.graph.custom_testGraph.property.set_Content_Message=Number of Hits :
734
                        </source>
735
            </subsection>
736
            
737
            <subsection name="&sect-num;.6  Want to improve Report Dashboard ?" anchor="development">
709
            If you want to contribute new graphs or improve current ones, you
738
            If you want to contribute new graphs or improve current ones, you
710
            can read this <a href="../devguide-dashboard.html" >developer documentation</a>.<br/>
739
            can read this <a href="../devguide-dashboard.html" >developer documentation</a>.<br/>
711
            Read this <a href="../building.html" >documentation</a> on contributing.
740
            Read this <a href="../building.html" >documentation</a> on contributing.

Return to bug 62166