From 359547e4d6ca79786d9df319f85c17d20806cad4 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Wed, 28 Jan 2015 10:08:19 +0100 Subject: [PATCH] SONAR-5886 improve the creation date facet on the issues page --- .../core/widgets/issues/issues.html.erb | 2 +- server/sonar-web/Gruntfile.coffee | 4 + .../src/main/coffee/issues/facets-view.coffee | 2 +- .../issues/facets/creation-date-facet.coffee | 19 ++++ .../main/coffee/issues/models/state.coffee | 16 +-- .../facets/issues-creation-date-facet.hbs | 18 ++- .../main/js/common/handlebars-extensions.js | 4 + .../src/main/js/graphics/barchart.js | 105 ++++++++++++++++++ .../src/main/js/graphics/timeline.js | 98 ++++++++++++++++ .../less/components/search-navigator.less | 85 ++++++++++++++ .../resources/org/sonar/l10n/core.properties | 2 +- 11 files changed, 340 insertions(+), 15 deletions(-) create mode 100644 server/sonar-web/src/main/js/graphics/barchart.js create mode 100644 server/sonar-web/src/main/js/graphics/timeline.js 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}}) {{else}} -
- - to - + {{/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 -- 2.39.5