diff options
Diffstat (limited to 'server/sonar-web')
13 files changed, 386 insertions, 18 deletions
diff --git a/server/sonar-web/Gruntfile.coffee b/server/sonar-web/Gruntfile.coffee index 29b2f98eb14..a6858a6a19d 100644 --- a/server/sonar-web/Gruntfile.coffee +++ b/server/sonar-web/Gruntfile.coffee @@ -243,6 +243,10 @@ module.exports = (grunt) -> name: 'nav/app' out: '<%= grunt.option("assetsDir") || pkg.assets %>build/js/nav/app.js' + issueFilterWidget: options: + name: 'widgets/issue-filter' + out: '<%= grunt.option("assetsDir") || pkg.assets %>build/js/widgets/issue-filter.js' + handlebars: options: @@ -296,6 +300,9 @@ module.exports = (grunt) -> '<%= grunt.option("assetsDir") || pkg.assets %>js/templates/nav.js': [ '<%= pkg.sources %>hbs/nav/**/*.hbs' ] + '<%= grunt.option("assetsDir") || pkg.assets %>js/templates/widgets.js': [ + '<%= pkg.sources %>hbs/widgets/**/*.hbs' + ] clean: diff --git a/server/sonar-web/src/main/hbs/widgets/_widget-issue-filter-total.hbs b/server/sonar-web/src/main/hbs/widgets/_widget-issue-filter-total.hbs new file mode 100644 index 00000000000..407209bb5ab --- /dev/null +++ b/server/sonar-web/src/main/hbs/widgets/_widget-issue-filter-total.hbs @@ -0,0 +1,11 @@ +<tr> + <td> + <a href="{{link '/issues/search#' query}}"><strong>{{t 'total'}}</strong></a> + </td> + <td class="text-right"><strong>{{total}}</strong></td> + <td class="barchart"> + <div class="barchart" style="width: 100%;"> + <div style="width: 100%;"></div> + </div> + </td> +</tr> diff --git a/server/sonar-web/src/main/hbs/widgets/widget-issue-filter-action-plans.hbs b/server/sonar-web/src/main/hbs/widgets/widget-issue-filter-action-plans.hbs new file mode 100644 index 00000000000..748c57df613 --- /dev/null +++ b/server/sonar-web/src/main/hbs/widgets/widget-issue-filter-action-plans.hbs @@ -0,0 +1,22 @@ +<table class="data zebra"> + {{> '_widget-issue-filter-total'}} + {{#each items}} + <tr> + <td> + {{#eq val ''}} + <a href="{{issueFilterItemLink ../../parsedQuery 'planned' 'false'}}">{{t 'issue.unplanned'}}</a> + {{else}} + <a href="{{issueFilterItemLink ../../parsedQuery 'actionPlans' val}}">{{default label val}}</a> + {{/eq}} + </td> + <td class="text-right nowrap"> + {{numberShort count}} + </td> + <td class="barchart"> + <div class="barchart" style="width: 100%;"> + <div style="width: {{percent count ../total}};"></div> + </div> + </td> + </tr> + {{/each}} +</table> diff --git a/server/sonar-web/src/main/hbs/widgets/widget-issue-filter-assignees.hbs b/server/sonar-web/src/main/hbs/widgets/widget-issue-filter-assignees.hbs new file mode 100644 index 00000000000..030e94370c1 --- /dev/null +++ b/server/sonar-web/src/main/hbs/widgets/widget-issue-filter-assignees.hbs @@ -0,0 +1,22 @@ +<table class="data zebra"> + {{> '_widget-issue-filter-total'}} + {{#each items}} + <tr> + <td> + {{#eq val ''}} + <a href="{{issueFilterItemLink ../../parsedQuery 'assigned' 'false'}}">{{t 'unassigned'}}</a> + {{else}} + <a href="{{issueFilterItemLink ../../parsedQuery 'assignees' val}}">{{default label val}}</a> + {{/eq}} + </td> + <td class="text-right nowrap"> + {{numberShort count}} + </td> + <td class="barchart"> + <div class="barchart" style="width: 100%;"> + <div style="width: {{percent count ../total}};"></div> + </div> + </td> + </tr> + {{/each}} +</table> diff --git a/server/sonar-web/src/main/hbs/widgets/widget-issue-filter-resolutions.hbs b/server/sonar-web/src/main/hbs/widgets/widget-issue-filter-resolutions.hbs new file mode 100644 index 00000000000..2d83a97ba4a --- /dev/null +++ b/server/sonar-web/src/main/hbs/widgets/widget-issue-filter-resolutions.hbs @@ -0,0 +1,22 @@ +<table class="data zebra"> + {{> '_widget-issue-filter-total'}} + {{#each items}} + <tr> + <td> + {{#eq val ''}} + <a href="{{issueFilterItemLink ../../parsedQuery 'resolved' 'false'}}">{{t 'unresolved'}}</a> + {{else}} + <a href="{{issueFilterItemLink ../../parsedQuery 'resolutions' val}}">{{t 'issue.resolution' val}}</a> + {{/eq}} + </td> + <td class="text-right nowrap"> + {{numberShort count}} + </td> + <td class="barchart"> + <div class="barchart" style="width: 100%;"> + <div style="width: {{percent count ../total}};"></div> + </div> + </td> + </tr> + {{/each}} +</table> diff --git a/server/sonar-web/src/main/hbs/widgets/widget-issue-filter-severities.hbs b/server/sonar-web/src/main/hbs/widgets/widget-issue-filter-severities.hbs new file mode 100644 index 00000000000..64cb0477d9f --- /dev/null +++ b/server/sonar-web/src/main/hbs/widgets/widget-issue-filter-severities.hbs @@ -0,0 +1,19 @@ +<table class="data zebra"> + <tr> + <td> + <a href="{{link '/issues/search#' query}}"><strong>{{t 'total'}}</strong></a> + </td> + <td class="text-right"><strong>{{total}}</strong></td> + </tr> + {{#each items}} + <tr> + <td> + {{severityIcon val}} + <a href="{{issueFilterItemLink ../parsedQuery ../property val}}">{{t 'severity' val}}</a> + </td> + <td class="text-right nowrap"> + {{numberShort count}} + </td> + </tr> + {{/each}} +</table> diff --git a/server/sonar-web/src/main/hbs/widgets/widget-issue-filter-statuses.hbs b/server/sonar-web/src/main/hbs/widgets/widget-issue-filter-statuses.hbs new file mode 100644 index 00000000000..97dc5ec5e70 --- /dev/null +++ b/server/sonar-web/src/main/hbs/widgets/widget-issue-filter-statuses.hbs @@ -0,0 +1,27 @@ +<table class="data zebra"> + <tr> + <td><strong>{{t 'total'}}</strong></td> + <td class="text-right"><strong>{{total}}</strong></td> + <td class="barchart"> + <div class="barchart" style="width: 100%;"> + <div style="width: 100%;"></div> + </div> + </td> + </tr> + {{#each items}} + <tr> + <td> + {{statusIcon val}} + <a href="{{issueFilterItemLink ../parsedQuery ../property val}}">{{t 'issue.status' val}}</a> + </td> + <td class="text-right nowrap"> + {{numberShort count}} + </td> + <td class="barchart"> + <div class="barchart" style="width: 100%;"> + <div style="width: {{percent count ../total}};"></div> + </div> + </td> + </tr> + {{/each}} +</table> diff --git a/server/sonar-web/src/main/hbs/widgets/widget-issue-filter.hbs b/server/sonar-web/src/main/hbs/widgets/widget-issue-filter.hbs new file mode 100644 index 00000000000..e635a2fdcde --- /dev/null +++ b/server/sonar-web/src/main/hbs/widgets/widget-issue-filter.hbs @@ -0,0 +1,18 @@ +<table class="data zebra"> + {{> '_widget-issue-filter-total'}} + {{#each items}} + <tr> + <td> + <a href="{{searchLink}}">{{default label val}}</a> + </td> + <td class="text-right nowrap"> + {{numberShort count}} + </td> + <td class="barchart"> + <div class="barchart" style="width: 100%;"> + <div style="width: {{percent count ../total}};"></div> + </div> + </td> + </tr> + {{/each}} +</table> diff --git a/server/sonar-web/src/main/js/widgets/issue-filter.js b/server/sonar-web/src/main/js/widgets/issue-filter.js new file mode 100644 index 00000000000..de810bf8e66 --- /dev/null +++ b/server/sonar-web/src/main/js/widgets/issue-filter.js @@ -0,0 +1,218 @@ +define(['templates/widgets'], function () { + + var $ = jQuery, + defaultComparator = function (item) { + return -item.count; + }, + defaultFilter = function (item) { + var items = this.query[this.property]; + return items == null || + (items != null && items.split(',').indexOf(item.val) !== -1); + }, + defaultLabel = function (item) { + return item.val; + }, + defaultLink = function (item, property, query) { + var criterion = {}; + criterion[property] = item.val; + var r = _.extend({}, query, criterion); + return baseUrl + '/issues/search#' + getQuery(r); + }, + byDistributionConf = { + 'severities': { + template: 'widget-issue-filter-severities', + comparator: function (item) { + var order = ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO']; + return order.indexOf(item.val); + } + }, + 'statuses': { + template: 'widget-issue-filter-statuses', + comparator: function (item) { + var order = ['OPEN', 'REOPENED', 'CONFIRMED', 'RESOLVED', 'CLOSED']; + return order.indexOf(item.val); + } + }, + 'resolutions': { + template: 'widget-issue-filter-resolutions', + comparator: function (item) { + var order = ['', 'FALSE-POSITIVE', 'WONTFIX', 'FIXED', 'REMOVED']; + return order.indexOf(item.val); + } + }, + 'rules': { + label: function (item, r) { + if (_.isArray(r.rules)) { + var rule = _.findWhere(r.rules, { key: item.val }); + if (rule != null) { + return rule.name; + } + } + } + }, + 'projectUuids': { + label: function (item, r) { + if (_.isArray(r.projects)) { + var project = _.findWhere(r.projects, { uuid: item.val }); + if (project != null) { + return project.name; + } + } + } + }, + 'assignees': { + template: 'widget-issue-filter-assignees', + label: function (item, r) { + if (_.isArray(r.users)) { + var user = _.findWhere(r.users, { login: item.val }); + if (user != null) { + return user.name; + } + } + } + }, + 'languages': { + label: function (item, r) { + if (_.isArray(r.languages)) { + var lang = _.findWhere(r.languages, { key: item.val }); + if (lang != null) { + return lang.name; + } + } + } + }, + 'actionPlans': { + template: 'widget-issue-filter-action-plans', + label: function (item, r) { + if (_.isArray(r.actionPlans)) { + var actionPlan = _.findWhere(r.actionPlans, { key: item.val }); + if (actionPlan != null) { + return actionPlan.name; + } + } + } + }, + 'createdAt': { + comparator: function (item) { + return moment(item.val).toDate(); + }, + label: function (item, r, items, index, query) { + var beginning = moment(item.val), + endDate = query.createdBefore != null ? moment(query.createdBefore) : moment(), + ending = index < items.length - 1 ? moment(items[index + 1].val).subtract(1, 'days') : endDate, + isSameDay = ending.diff(beginning, 'days') <= 1; + return beginning.format('LL') + (isSameDay ? '' : (' – ' + ending.format('LL'))); + }, + link: function (item, property, query, index, items) { + var createdAfter = moment(item.val), + endDate = query.createdBefore != null ? moment(query.createdBefore) : moment(), + createdBefore = index < items.length - 1 ? moment(items[index + 1].val).subtract(1, 'days') : endDate, + isSameDay = createdBefore.diff(createdAfter, 'days') <= 1; + if (isSameDay) { + createdBefore.add(1, 'days'); + } + var r = _.extend({}, query, { + createdAfter: createdAfter.format('YYYY-MM-DD'), + createdBefore: createdBefore.format('YYYY-MM-DD') + }); + return baseUrl + '/issues/search#' + getQuery(r); + } + } + }; + + function getQuery (query, separator) { + separator = separator || '|'; + var route = []; + _.forEach(query, function (value, property) { + route.push('' + property + '=' + encodeURIComponent(value)); + }); + return route.join(separator); + } + + Handlebars.registerHelper('issueFilterItemLink', function (query, property, value) { + var criterion = {}; + criterion[property] = value; + var r = _.extend({}, query, criterion); + return baseUrl + '/issues/search#' + getQuery(r); + }); + + return Marionette.ItemView.extend({ + + getTemplate: function () { + var template = this.conf != null && this.conf.template != null ? this.conf.template : 'widget-issue-filter'; + return Templates[template]; + }, + + initialize: function () { + this.model = new Backbone.Model({ + query: this.options.query, + parsedQuery: this.getParsedQuery(), + property: this.options.distributionAxis + }); + this.listenTo(this.model, 'change', this.render); + this.conf = byDistributionConf[this.options.distributionAxis]; + this.query = this.getParsedQuery(); + this.requestIssues(); + }, + + getParsedQuery: function () { + var queryString = this.options.query || '', + query = {}; + queryString.split('|').forEach(function (criterionString) { + var criterion = criterionString.split('='); + if (criterion.length === 2) { + query[criterion[0]] = criterion[1]; + } + }); + return query; + }, + + sortItems: function (items) { + var comparator = this.conf != null && this.conf.comparator != null ? this.conf.comparator : defaultComparator; + return _.sortBy(items, comparator); + }, + + filterItems: function (items) { + var filter = this.conf != null && this.conf.filter != null ? this.conf.filter : defaultFilter; + return _.filter(items, filter, { query: this.query, property: this.options.distributionAxis }); + }, + + withLink: function (items) { + var link = this.conf != null && this.conf.link != null ? this.conf.link : defaultLink, + property = this.options.distributionAxis, + query = this.model.get('parsedQuery'); + return items.map(function (item, index) { + return _.extend(item, { searchLink: link(item, property, query, index, items) }); + }); + }, + + withLabels: function (items) { + var label = this.conf != null && this.conf.label != null ? this.conf.label : defaultLabel, + r = this.model.get('rawResponse'), + query = this.model.get('parsedQuery'); + return items.map(function (item, index) { + return _.extend(item, { label: label(item, r, items, index, query) }); + }); + }, + + requestIssues: function () { + var that = this, + url = baseUrl + '/api/issues/search', + options = _.extend({}, this.query, { + ps: 1, + facets: this.options.distributionAxis + }); + return $.get(url, options).done(function (r) { + if (_.isArray(r.facets) && r.facets.length === 1) { + // save response object, but do not trigger repaint + that.model.set({ rawResponse: r }, { silent: true }); + that.model.set({ + items: that.sortItems(that.withLabels(that.withLink(that.filterItems(r.facets[0].values)))), + total: r.total + }); + } + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/less/init/misc.less b/server/sonar-web/src/main/less/init/misc.less index f8b31448f3d..3dc7e5df1ec 100644 --- a/server/sonar-web/src/main/less/init/misc.less +++ b/server/sonar-web/src/main/less/init/misc.less @@ -103,3 +103,19 @@ td.spacer-top { .bordered-top { border-top: 1px solid @barBorderColor; } + +.zero-font-size { + font-size: 0 !important; +} + +.width-100 { + width: 100%; +} + +.width-80 { + width: 80%; +} + +.width-60 { + width: 60%; +} diff --git a/server/sonar-web/src/main/less/init/tables.less b/server/sonar-web/src/main/less/init/tables.less index afca8360a88..aca9725e965 100644 --- a/server/sonar-web/src/main/less/init/tables.less +++ b/server/sonar-web/src/main/less/init/tables.less @@ -53,8 +53,9 @@ table.data > tfoot > tr > td { } table.data > tbody > tr > td { - padding: 5px; + padding: 4px 5px; vertical-align: text-top; + line-height: 20px; } table.data td.small, table.data th.small { diff --git a/server/sonar-web/src/main/less/pages/dashboard.less b/server/sonar-web/src/main/less/pages/dashboard.less index 8dc8cfdffe6..84fd6334170 100644 --- a/server/sonar-web/src/main/less/pages/dashboard.less +++ b/server/sonar-web/src/main/less/pages/dashboard.less @@ -325,22 +325,6 @@ .widget-span-11 { width: 91.666666666667%; } .widget-span-12 { width: 100%; } -@media (max-width: 1279px) { - .widget-span-1 { width: 50%; } - .widget-span-2 { width: 50%; } - .widget-span-3 { width: 50%; } - .widget-span-3-5 { width: 50%; } - .widget-span-4 { width: 50%; } - .widget-span-5 { width: 50%; } - .widget-span-6 { width: 50%; } - .widget-span-7 { width: 100%; } - .widget-span-8 { width: 100%; } - .widget-span-9 { width: 100%; } - .widget-span-10 { width: 100%; } - .widget-span-11 { width: 100%; } - .widget-span-12 { width: 100%; } -} - .widget-label { display: block; font-size: @baseFontSize; diff --git a/server/sonar-web/src/main/less/style.less b/server/sonar-web/src/main/less/style.less index 5278454f452..806d57bfc2c 100644 --- a/server/sonar-web/src/main/less/style.less +++ b/server/sonar-web/src/main/less/style.less @@ -707,7 +707,8 @@ div.barchart { } div.barchart > div { - background-color: @darkBlue; + min-width: 1px; + background-color: #c4d6e1; height: 0.9em; } |