aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2015-01-28 10:08:19 +0100
committerStas Vilchik <vilchiks@gmail.com>2015-02-04 17:16:18 +0100
commit359547e4d6ca79786d9df319f85c17d20806cad4 (patch)
tree6fa7cf7b8f0b12cb3d5e76270145a4f14ca5ef7a
parent1a2621e5d9c8f4f1c568576745668debafd4e8aa (diff)
downloadsonarqube-359547e4d6ca79786d9df319f85c17d20806cad4.tar.gz
sonarqube-359547e4d6ca79786d9df319f85c17d20806cad4.zip
SONAR-5886 improve the creation date facet on the issues page
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/issues/issues.html.erb2
-rw-r--r--server/sonar-web/Gruntfile.coffee4
-rw-r--r--server/sonar-web/src/main/coffee/issues/facets-view.coffee2
-rw-r--r--server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee19
-rw-r--r--server/sonar-web/src/main/coffee/issues/models/state.coffee16
-rw-r--r--server/sonar-web/src/main/hbs/issues/facets/issues-creation-date-facet.hbs18
-rw-r--r--server/sonar-web/src/main/js/common/handlebars-extensions.js4
-rw-r--r--server/sonar-web/src/main/js/graphics/barchart.js105
-rw-r--r--server/sonar-web/src/main/js/graphics/timeline.js98
-rw-r--r--server/sonar-web/src/main/less/components/search-navigator.less85
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties2
11 files changed, 340 insertions, 15 deletions
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/issues/issues.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/issues/issues.html.erb
index bf9a47204d7..a2e540c8511 100644
--- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/issues/issues.html.erb
+++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/issues/issues.html.erb
@@ -17,7 +17,7 @@
new_technical_debt = @snapshot.measure('new_technical_debt')
if @dashboard_configuration.selected_period?
- period_date = @snapshot.period_datetime(@dashboard_configuration.period_index).strftime('%Y-%m-%d')
+ period_date = @snapshot.period_datetime(@dashboard_configuration.period_index).strftime('%FT%T%z')
end
%>
diff --git a/server/sonar-web/Gruntfile.coffee b/server/sonar-web/Gruntfile.coffee
index d7afde755b1..739811baabc 100644
--- a/server/sonar-web/Gruntfile.coffee
+++ b/server/sonar-web/Gruntfile.coffee
@@ -104,6 +104,8 @@ module.exports = (grunt) ->
'<%= grunt.option("assetsDir") || pkg.assets %>js/widgets/tag-cloud.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/widgets/treemap.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/graphics/pie-chart.js'
+ '<%= grunt.option("assetsDir") || pkg.assets %>js/graphics/timeline.js'
+ '<%= grunt.option("assetsDir") || pkg.assets %>js/graphics/barchart.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/top-search.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/sortable.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/common/inputs.js'
@@ -148,6 +150,8 @@ module.exports = (grunt) ->
'<%= grunt.option("assetsDir") || pkg.assets %>js/widgets/tag-cloud.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/widgets/treemap.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/graphics/pie-chart.js'
+ '<%= grunt.option("assetsDir") || pkg.assets %>js/graphics/timeline.js'
+ '<%= grunt.option("assetsDir") || pkg.assets %>js/graphics/barchart.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/top-search.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/sortable.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/common/inputs.js'
diff --git a/server/sonar-web/src/main/coffee/issues/facets-view.coffee b/server/sonar-web/src/main/coffee/issues/facets-view.coffee
index 564e8d1f477..a116b380b90 100644
--- a/server/sonar-web/src/main/coffee/issues/facets-view.coffee
+++ b/server/sonar-web/src/main/coffee/issues/facets-view.coffee
@@ -46,7 +46,7 @@ define [
when 'statuses' then StatusFacet
when 'assignees' then AssigneeFacet
when 'resolutions' then ResolutionFacet
- when 'creationDate' then CreationDateFacet
+ when 'createdAt' then CreationDateFacet
when 'projectUuids' then ProjectFacet
when 'moduleUuids' then ModuleFacet
when 'rules' then RuleFacet
diff --git a/server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee
index 45672f29202..98249b2da88 100644
--- a/server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee
+++ b/server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee
@@ -15,6 +15,8 @@ define [
events: ->
_.extend super,
'change input': 'applyFacet'
+ 'click .js-select-period-start': 'selectPeriodStart'
+ 'click .js-select-period-end': 'selectPeriodEnd'
onRender: ->
@@ -31,6 +33,21 @@ define [
value = query[prop]
@$("input[name=#{prop}]").val value if value?
+ @$('.js-barchart').barchart @model.getValues()
+
+ @$('select').select2
+ width: '100%'
+ minimumResultsForSearch: 999
+
+
+ selectPeriodStart: ->
+ @$('.js-period-start').datepicker 'show'
+
+
+ selectPeriodEnd: ->
+ @$('.js-period-end').datepicker 'show'
+
+
applyFacet: ->
obj = {}
@@ -47,4 +64,6 @@ define [
serializeData: ->
_.extend super,
+ periodStart: @options.app.state.get('query').createdAfter
+ periodEnd: @options.app.state.get('query').createdBefore
createdAt: @options.app.state.get('query').createdAt
diff --git a/server/sonar-web/src/main/coffee/issues/models/state.coffee b/server/sonar-web/src/main/coffee/issues/models/state.coffee
index 337b585c2e7..b16dea09c72 100644
--- a/server/sonar-web/src/main/coffee/issues/models/state.coffee
+++ b/server/sonar-web/src/main/coffee/issues/models/state.coffee
@@ -7,20 +7,20 @@ define [
page: 1
maxResultsReached: false
query: {}
- facets: ['severities', 'resolutions', 'rules', 'tags', 'projectUuids']
+ facets: ['severities', 'resolutions', 'createdAt', 'rules', 'tags', 'projectUuids']
isContext: false
- allFacets: ['issues', 'severities', 'resolutions', 'rules', 'tags', 'statuses', 'projectUuids', 'moduleUuids',
- 'directories', 'fileUuids', 'assignees', 'reporters', 'authors', 'languages', 'actionPlans',
- 'creationDate'],
+ allFacets: ['issues', 'severities', 'resolutions', 'createdAt', 'rules', 'tags', 'statuses', 'projectUuids',
+ 'moduleUuids', 'directories', 'fileUuids', 'assignees', 'reporters', 'authors', 'languages',
+ 'actionPlans'],
facetsFromServer: ['severities', 'statuses', 'resolutions', 'actionPlans', 'projectUuids', 'directories', 'rules',
- 'moduleUuids', 'tags', 'assignees', 'reporters', 'authors', 'fileUuids', 'languages'],
+ 'moduleUuids', 'tags', 'assignees', 'reporters', 'authors', 'fileUuids', 'languages',
+ 'createdAt'],
transform: {
'resolved': 'resolutions'
'assigned': 'assignees'
'planned': 'actionPlans'
- 'createdAt': 'creationDate'
- 'createdBefore': 'creationDate'
- 'createdAfter': 'creationDate'
+ 'createdBefore': 'createdAt'
+ 'createdAfter': 'createdAt'
}
diff --git a/server/sonar-web/src/main/hbs/issues/facets/issues-creation-date-facet.hbs b/server/sonar-web/src/main/hbs/issues/facets/issues-creation-date-facet.hbs
index c5b3e5dd47e..5aea5db9109 100644
--- a/server/sonar-web/src/main/hbs/issues/facets/issues-creation-date-facet.hbs
+++ b/server/sonar-web/src/main/hbs/issues/facets/issues-creation-date-facet.hbs
@@ -6,9 +6,19 @@
{{dt createdAt}} ({{fromNow createdAt}})
</div>
{{else}}
- <div class="search-navigator-facet-container justify">
- <input type="text" class="search-navigator-facet-input" name="createdAfter" placeholder="From">
- to
- <input type="text" class="search-navigator-facet-input" name="createdBefore" placeholder="To">
+ <div class="search-navigator-facet-container">
+ <div class="js-barchart" data-height="75"></div>
+ <div class="search-navigator-date-facet-selection">
+ <a class="js-select-period-start search-navigator-date-facet-selection-dropdown-left">
+ {{#if periodStart}}{{d periodStart}}{{else}}Past{{/if}}&nbsp;<i class="icon-dropdown"></i>
+ </a>
+ <a class="js-select-period-end search-navigator-date-facet-selection-dropdown-right">
+ {{#if periodEnd}}{{d periodEnd}}{{else}}Now{{/if}}&nbsp;<i class="icon-dropdown"></i>
+ </a>
+ <input class="js-period-start search-navigator-date-facet-selection-input-left"
+ type="text" value="{{#if periodStart}}{{ds periodStart}}{{/if}}" name="createdAfter">
+ <input class="js-period-end search-navigator-date-facet-selection-input-right"
+ type="text" value="{{#if periodEnd}}{{ds periodEnd}}{{/if}}" name="createdBefore">
+ </div>
</div>
{{/if}}
diff --git a/server/sonar-web/src/main/js/common/handlebars-extensions.js b/server/sonar-web/src/main/js/common/handlebars-extensions.js
index 679f23af31e..e72ed9b0ca8 100644
--- a/server/sonar-web/src/main/js/common/handlebars-extensions.js
+++ b/server/sonar-web/src/main/js/common/handlebars-extensions.js
@@ -274,6 +274,10 @@
return moment(date).format('LLL');
});
+ Handlebars.registerHelper('ds', function(date) {
+ return moment(date).format('YYYY-MM-DD');
+ });
+
Handlebars.registerHelper('fromNow', function(date) {
return moment(date).fromNow();
});
diff --git a/server/sonar-web/src/main/js/graphics/barchart.js b/server/sonar-web/src/main/js/graphics/barchart.js
new file mode 100644
index 00000000000..1331a725280
--- /dev/null
+++ b/server/sonar-web/src/main/js/graphics/barchart.js
@@ -0,0 +1,105 @@
+(function ($) {
+
+ function trans (left, top) {
+ return 'translate(' + left + ', ' + top + ')';
+ }
+
+ var defaults = {
+ height: 140,
+ color: '#1f77b4',
+ interpolate: 'basis'
+ };
+
+ /*
+ * data = [
+ * { val: '2015-01-30', count: 30 },
+ * ...
+ * ]
+ */
+
+ $.fn.barchart = function (data) {
+ $(this).each(function () {
+ var options = _.defaults($(this).data(), defaults);
+ _.extend(options, { width: $(this).width() });
+
+ var container = d3.select(this),
+ svg = container.append('svg')
+ .attr('width', options.width + 2)
+ .attr('height', options.height + 2)
+ .classed('sonar-d3', true),
+
+ plot = svg.append('g')
+ .classed('plot', true),
+
+ xScale = d3.scale.ordinal()
+ .domain(data.map(function (d, i) {
+ return i;
+ })),
+
+ yScaleMax = d3.max(data, function (d) {
+ return d.count;
+ }),
+ yScale = d3.scale.linear()
+ .domain([0, yScaleMax]);
+
+ _.extend(options, {
+ marginLeft: 1,
+ marginRight: 1,
+ marginTop: 18,
+ marginBottom: 1
+ });
+
+ _.extend(options, {
+ availableWidth: options.width - options.marginLeft - options.marginRight,
+ availableHeight: options.height - options.marginTop - options.marginBottom
+ });
+
+ plot.attr('transform', trans(options.marginLeft, options.marginTop));
+ xScale.rangeRoundBands([0, options.availableWidth], 0.05, 0);
+ yScale.range([3, options.availableHeight]);
+
+ var barWidth = xScale.rangeBand(),
+ bars = plot.selectAll('g').data(data);
+
+ if (barWidth > 0) {
+ var barsEnter = bars.enter()
+ .append('g')
+ .attr('transform', function (d, i) {
+ return trans(xScale(i), Math.ceil(options.availableHeight - yScale(d.count)));
+ });
+
+ barsEnter.append('rect')
+ .style('fill', options.color)
+ .attr('width', barWidth)
+ .attr('height', function (d) {
+ return Math.floor(yScale(d.count));
+ })
+ .attr('title', function (d, i) {
+ var beginning = moment(d.val),
+ ending = i < data.length - 1 ? moment(data[i].val).subtract(1, 'days') : moment();
+ return d.count + ' | ' + beginning.format('LL') + ' - ' + ending.format('LL');
+ })
+ .attr('data-placement', 'right')
+ .attr('data-toggle', 'tooltip');
+
+ var maxValue = d3.max(data, function (d) {
+ return d.count;
+ }),
+ isValueShown = false;
+
+ barsEnter.append('text')
+ .classed('subtitle', true)
+ .attr('transform', trans(barWidth / 2, -4))
+ .style('text-anchor', 'middle')
+ .text(function (d) {
+ var text = !isValueShown && d.count === maxValue ? d.count : '';
+ isValueShown = d.count === maxValue;
+ return text;
+ });
+
+ $(this).find('[data-toggle=tooltip]').tooltip({ container: 'body' });
+ }
+ });
+ };
+
+})(window.jQuery);
diff --git a/server/sonar-web/src/main/js/graphics/timeline.js b/server/sonar-web/src/main/js/graphics/timeline.js
new file mode 100644
index 00000000000..91ec9776398
--- /dev/null
+++ b/server/sonar-web/src/main/js/graphics/timeline.js
@@ -0,0 +1,98 @@
+(function ($) {
+
+ function trans (left, top) {
+ return 'translate(' + left + ', ' + top + ')';
+ }
+
+ var defaults = {
+ height: 140,
+ color: '#1f77b4',
+ interpolate: 'basis'
+ };
+
+ /*
+ * data = [
+ * { val: '2015-01-30', count: 30 },
+ * ...
+ * ]
+ */
+
+ $.fn.timeline = function (data) {
+ $(this).each(function () {
+ var options = _.defaults($(this).data(), defaults);
+ _.extend(options, { width: $(this).width() });
+
+ var container = d3.select(this),
+ svg = container.append('svg')
+ .attr('width', options.width + 2)
+ .attr('height', options.height + 2)
+ .classed('sonar-d3', true),
+
+ plot = svg.append('g')
+ .classed('plot', true),
+
+ xScale = d3.time.scale()
+ .domain(d3.extent(data, function (d) {
+ return new Date(d.val);
+ })),
+
+ yScale = d3.scale.linear()
+ .domain([
+ 0, d3.max(data, function (d) {
+ return d.count;
+ })
+ ]),
+
+ line = d3.svg.line()
+ .x(function (d) {
+ return xScale(new Date(d.val));
+ })
+ .y(function (d) {
+ return yScale(d.count);
+ })
+ .interpolate(options.interpolate),
+
+ minDate = xScale.domain()[0],
+ minDateTick = svg.append('text')
+ .classed('subtitle', true)
+ .text(moment(minDate).format('LL')),
+
+ maxDate = xScale.domain()[1],
+ maxDateTick = svg.append('text')
+ .classed('subtitle', true)
+ .text(moment(maxDate).format('LL'))
+ .style('text-anchor', 'end');
+
+ _.extend(options, {
+ marginLeft: 1,
+ marginRight: 1,
+ marginTop: 1,
+ marginBottom: 1 + maxDateTick.node().getBBox().height
+ });
+
+ _.extend(options, {
+ availableWidth: options.width - options.marginLeft - options.marginRight,
+ availableHeight: options.height - options.marginTop - options.marginBottom
+ });
+
+ plot.attr('transform', trans(options.marginLeft, options.marginTop));
+ xScale.range([0, options.availableWidth]);
+ yScale.range([0, options.availableHeight]);
+
+ minDateTick
+ .attr('x', options.marginLeft)
+ .attr('y', options.height);
+ maxDateTick
+ .attr('x', options.width - options.marginRight)
+ .attr('y', options.height);
+
+ plot.append('path')
+ .datum(data)
+ .attr('d', line)
+ .attr('class', 'line')
+ .style('stroke', options.color);
+
+ });
+ };
+
+})(window.jQuery);
diff --git a/server/sonar-web/src/main/less/components/search-navigator.less b/server/sonar-web/src/main/less/components/search-navigator.less
index 3ed338d48c7..6e181377bfa 100644
--- a/server/sonar-web/src/main/less/components/search-navigator.less
+++ b/server/sonar-web/src/main/less/components/search-navigator.less
@@ -181,6 +181,10 @@
padding: 0 10px 16px;
}
+.search-navigator-facet-container-center {
+ text-align: center;
+}
+
.search-navigator-facet-query {
padding: 7px 10px;
@@ -198,6 +202,87 @@
width: @sideWidth * 0.4;
}
+.search-navigator-facet-histogram {
+ font-size: 0;
+
+ > li {
+ display: inline-block;
+ vertical-align: bottom;
+ width: 24px;
+ font-size: @smallFontSize;
+ }
+
+ > li > a {
+ display: block;
+ padding-bottom: 3px;
+ .link-no-underline;
+ .trans(none);
+
+ &:hover, &:focus,
+ &:hover .search-navigator-facet-histogram-bar-inner, &:focus .search-navigator-facet-histogram-bar-inner {
+ background-color: @barBorderColor;
+ }
+
+ &:hover .search-navigator-facet-histogram-bar, &:focus .search-navigator-facet-histogram-bar {
+ background-color: @blue;
+ }
+ }
+}
+
+.search-navigator-facet-histogram-large {
+ > li {
+ width: 48px;
+ }
+}
+
+.search-navigator-facet-histogram-bar {
+ display: block;
+ height: 60px;
+ background-color: @darkBlue;
+}
+
+.search-navigator-facet-histogram-bar-inner {
+ display: block;
+ max-height: 59px;
+ background-color: @barBackgroundColor;
+}
+
+.search-navigator-facet-histogram-label {
+ display: block;
+ text-align: center;
+}
+
+.search-navigator-date-facet-selection {
+ .clearfix;
+ position: relative;
+ font-size: @smallFontSize;
+}
+
+.search-navigator-date-facet-selection-dropdown-left {
+ float: left;
+ .link-no-underline;
+}
+
+.search-navigator-date-facet-selection-dropdown-right {
+ float: right;
+ .link-no-underline;
+}
+
+.search-navigator-date-facet-selection-input-left {
+ position: absolute;
+ left: 0;
+ width: 100px;
+ visibility: hidden;
+}
+
+.search-navigator-date-facet-selection-input-right {
+ position: absolute;
+ right: 0;
+ width: 100px;
+ visibility: hidden;
+}
+
+
.search-navigator-filters {
position: relative;
.clearfix;
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index a4d4d29b173..a4bc5ab4abd 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -818,7 +818,7 @@ issues.facet.tags=Tag
issues.facet.rules=Rule
issues.facet.resolutions=Resolution
issues.facet.languages=Language
-issues.facet.creationDate=Creation Date
+issues.facet.createdAt=Creation Date
issues.facet.reporters=Reporter
issues.facet.authors=Author
issues.facet.issues=Issue Key