From bd0ad9d2d3104e675f4184294412137fa1d8c659 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Mon, 3 Mar 2014 17:17:11 +0100 Subject: [PATCH] SONAR-4366 Quality Gates: latest feedback --- .../resources/org/sonar/l10n/core.properties | 9 ++++--- .../app/views/quality_gates/index.html.erb | 1 + .../_quality_gate_edit_template.hbs.erb | 6 ++--- ...y_gate_sidebar_list_empty_template.hbs.erb | 3 +++ .../webapp/javascripts/common/select-list.js | 15 +++++------ .../javascripts/quality-gate/app.coffee | 25 ++++++++++++++++--- .../webapp/javascripts/quality-gate/app.js | 17 +++++++++---- .../javascripts/quality-gate/layout.coffee | 16 ++++++++++++ .../webapp/javascripts/quality-gate/layout.js | 15 +++++++++++ ...uality-gate-sidebar-list-empty-view.coffee | 12 +++++++++ .../quality-gate-sidebar-list-view.coffee | 7 ++++-- .../views/quality-gate-sidebar-list-view.js | 4 ++- .../main/webapp/stylesheets/quality-gates.css | 8 +++++- .../webapp/stylesheets/quality-gates.less | 10 +++++++- .../main/webapp/stylesheets/select-list.css | 10 ++++++++ 15 files changed, 130 insertions(+), 28 deletions(-) create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/quality_gates/templates/_quality_gate_sidebar_list_empty_template.hbs.erb create mode 100644 sonar-server/src/main/webapp/javascripts/quality-gate/views/quality-gate-sidebar-list-empty-view.coffee diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties index 99325c57268..de3fc835737 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties @@ -1638,20 +1638,21 @@ quality_profiles.copy_new_name=New name # #------------------------------------------------------------------------------ +quality_gates.noQualityGates=No Quality Gates quality_gates.add=Add Quality Gate -quality_gates.rename=Rename Quality Gate -quality_gates.copy=Copy Quality Gate +quality_gates.rename=Rename Quality Gate: +quality_gates.copy=Copy Quality Gate: quality_gates.conditions=Conditions quality_gates.projects=Projects quality_gates.add_condition=Add Condition quality_gates.no_conditions=No Conditions quality_gates.introduction=Only project measures are checked against thresholds. Modules, packages and classes are ignored. quality_gates.health_icons=Project health icons represent: -quality_gates.projects_for_default=You must not select specific projects for the default quality gate +quality_gates.projects_for_default=You must not select specific projects for the default quality gate. quality_gates.projects.with=With quality_gates.projects.without=Without quality_gates.projects.all=All -quality_gates.projects.noResults=There are no associated projects. +quality_gates.projects.noResults=No Projects quality_gates.projects.select_hint=Click to associate this project with the quality gate quality_gates.projects.deselect_hint=Click to remove association between this project and the quality gate diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/quality_gates/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/quality_gates/index.html.erb index 3d4f395e217..f8be9355257 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/quality_gates/index.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/quality_gates/index.html.erb @@ -8,6 +8,7 @@ <%= render :partial => '/quality_gates/templates/quality_gates_layout.hbs' -%> <%= render :partial => '/quality_gates/templates/quality_gate_sidebar_list_item_template.hbs' -%> +<%= render :partial => '/quality_gates/templates/quality_gate_sidebar_list_empty_template.hbs' -%> <%= render :partial => '/quality_gates/templates/quality_gate_actions_template.hbs' -%> <%= render :partial => '/quality_gates/templates/quality_gate_edit_template.hbs' -%> <%= render :partial => '/quality_gates/templates/quality_gate_detail_template.hbs' -%> diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/quality_gates/templates/_quality_gate_edit_template.hbs.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/quality_gates/templates/_quality_gate_edit_template.hbs.erb index 1bf1a58f3b1..34b12f6ebef 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/quality_gates/templates/_quality_gate_edit_template.hbs.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/quality_gates/templates/_quality_gate_edit_template.hbs.erb @@ -1,8 +1,8 @@ diff --git a/sonar-server/src/main/webapp/javascripts/common/select-list.js b/sonar-server/src/main/webapp/javascripts/common/select-list.js index 1a352f8c3f1..0c23099255b 100644 --- a/sonar-server/src/main/webapp/javascripts/common/select-list.js +++ b/sonar-server/src/main/webapp/javascripts/common/select-list.js @@ -90,6 +90,10 @@ requirejs(['backbone'], function (Backbone) { this.settings.tooltips.deselect : this.settings.tooltips.select) .prop('checked', this.model.get('selected')); + + if (this.settings.readOnly) { + this.$('.select-list-list-checkbox').prop('disabled', true); + } }, remove: function (postpone) { @@ -105,11 +109,6 @@ requirejs(['backbone'], function (Backbone) { }, toggle: function (e) { - if (this.settings.readOnly) { - $(e.target).prop('checked', !$(e.target).prop('checked')); - return; - } - var selected = this.model.get('selected'), that = this, url = selected ? this.settings.deselectUrl : this.settings.selectUrl, @@ -225,7 +224,9 @@ requirejs(['backbone'], function (Backbone) { if (this.collection.length > 0) { this.collection.each(this.renderListItem, this); } else { - this.renderEmpty(); + if (this.settings.readOnly) { + this.renderEmpty(); + } } this.$listContainer.scrollTop(0); }, @@ -241,7 +242,7 @@ requirejs(['backbone'], function (Backbone) { }, renderEmpty: function () { - this.$listContainer.html(this.settings.labels.noResults); + this.$list.append('
  • ' + this.settings.labels.noResults + '
  • '); }, confirmFilter: function (model) { diff --git a/sonar-server/src/main/webapp/javascripts/quality-gate/app.coffee b/sonar-server/src/main/webapp/javascripts/quality-gate/app.coffee index d096f4ce606..346636af8a7 100644 --- a/sonar-server/src/main/webapp/javascripts/quality-gate/app.coffee +++ b/sonar-server/src/main/webapp/javascripts/quality-gate/app.coffee @@ -47,45 +47,58 @@ requirejs [ # Create a generic error handler for ajax requests jQuery.ajaxSetup error: (jqXHR) -> + text = jqXHR.responseText + errorBox = jQuery('.modal-error') if jqXHR.responseJSON?.errors? - alert _.pluck(jqXHR.responseJSON.errors, 'msg').join '. ' + text = _.pluck(jqXHR.responseJSON.errors, 'msg').join '. ' + if errorBox.length > 0 + errorBox.show().text text else - alert jqXHR.responseText + alert text + # Add html class to mark the page as navigator page jQuery('html').addClass('navigator-page quality-gates-page'); + # Create a Quality Gate Application App = new Marionette.Application + App.metrics = new Metrics App.qualityGates = new QualityGates + App.openFirstQualityGate = -> if @qualityGates.length > 0 @router.navigate "show/#{@qualityGates.models[0].get('id')}", trigger: true else - App.layout.contentRegion.reset() + App.layout.detailsRegion.reset() + App.deleteQualityGate = (id) -> App.qualityGates.remove id App.openFirstQualityGate() + App.unsetDefaults = (id) -> App.qualityGates.each (gate) -> gate.set('default', false) unless gate.id == id + # Construct layout App.addInitializer -> - @layout = new QualityGateLayout + @layout = new QualityGateLayout app: @ jQuery('body').append @layout.render().el + # Construct actions bar App.addInitializer -> @qualityGateActionsView = new QualityGateActionsView app: @ @layout.actionsRegion.show @qualityGateActionsView + # Construct sidebar App.addInitializer -> @qualityGateSidebarListView = new QualityGateSidebarListItemView @@ -93,21 +106,25 @@ requirejs [ app: @ @layout.listRegion.show @qualityGateSidebarListView + # Construct edit view App.addInitializer -> @qualityGateEditView = new QualityGateEditView app: @ @qualityGateEditView.render() + # Start router App.addInitializer -> @router = new QualityGateRouter app: @ Backbone.history.start() + # Open first quality gate when come to the page App.addInitializer -> initial = Backbone.history.fragment == '' App.openFirstQualityGate() if initial + # Load metrics and the list of quality gates before start the application qualityGatesXHR = App.qualityGates.fetch() jQuery.when(App.metrics.fetch(), qualityGatesXHR) diff --git a/sonar-server/src/main/webapp/javascripts/quality-gate/app.js b/sonar-server/src/main/webapp/javascripts/quality-gate/app.js index 8581847c123..de8c167eb71 100644 --- a/sonar-server/src/main/webapp/javascripts/quality-gate/app.js +++ b/sonar-server/src/main/webapp/javascripts/quality-gate/app.js @@ -34,11 +34,16 @@ var App, qualityGatesXHR; jQuery.ajaxSetup({ error: function(jqXHR) { - var _ref; + var errorBox, text, _ref; + text = jqXHR.responseText; + errorBox = jQuery('.modal-error'); if (((_ref = jqXHR.responseJSON) != null ? _ref.errors : void 0) != null) { - return alert(_.pluck(jqXHR.responseJSON.errors, 'msg').join('. ')); + text = _.pluck(jqXHR.responseJSON.errors, 'msg').join('. '); + } + if (errorBox.length > 0) { + return errorBox.show().text(text); } else { - return alert(jqXHR.responseText); + return alert(text); } } }); @@ -52,7 +57,7 @@ trigger: true }); } else { - return App.layout.contentRegion.reset(); + return App.layout.detailsRegion.reset(); } }; App.deleteQualityGate = function(id) { @@ -67,7 +72,9 @@ }); }; App.addInitializer(function() { - this.layout = new QualityGateLayout; + this.layout = new QualityGateLayout({ + app: this + }); return jQuery('body').append(this.layout.render().el); }); App.addInitializer(function() { diff --git a/sonar-server/src/main/webapp/javascripts/quality-gate/layout.coffee b/sonar-server/src/main/webapp/javascripts/quality-gate/layout.coffee index 6b0ef9175fe..929d8cae128 100644 --- a/sonar-server/src/main/webapp/javascripts/quality-gate/layout.coffee +++ b/sonar-server/src/main/webapp/javascripts/quality-gate/layout.coffee @@ -8,8 +8,24 @@ define [ class AppLayout extends Marionette.Layout className: 'navigator quality-gates-navigator' template: getTemplate '#quality-gates-layout' + + regions: headerRegion: '.navigator-header' actionsRegion: '.navigator-actions' listRegion: '.navigator-results' detailsRegion: '.navigator-details' + + + initialize: (options) -> + @listenTo options.app.qualityGates, 'all', @updateLayout + + + updateLayout: -> + empty = @options.app.qualityGates.length == 0 + @$(@headerRegion.el).toggle !empty + @$(@detailsRegion.el).toggle !empty + + + onRender: -> + @updateLayout() diff --git a/sonar-server/src/main/webapp/javascripts/quality-gate/layout.js b/sonar-server/src/main/webapp/javascripts/quality-gate/layout.js index e99e6b0dd26..20b544fabdd 100644 --- a/sonar-server/src/main/webapp/javascripts/quality-gate/layout.js +++ b/sonar-server/src/main/webapp/javascripts/quality-gate/layout.js @@ -24,6 +24,21 @@ detailsRegion: '.navigator-details' }; + AppLayout.prototype.initialize = function(options) { + return this.listenTo(options.app.qualityGates, 'all', this.updateLayout); + }; + + AppLayout.prototype.updateLayout = function() { + var empty; + empty = this.options.app.qualityGates.length === 0; + this.$(this.headerRegion.el).toggle(!empty); + return this.$(this.detailsRegion.el).toggle(!empty); + }; + + AppLayout.prototype.onRender = function() { + return this.updateLayout(); + }; + return AppLayout; })(Marionette.Layout); diff --git a/sonar-server/src/main/webapp/javascripts/quality-gate/views/quality-gate-sidebar-list-empty-view.coffee b/sonar-server/src/main/webapp/javascripts/quality-gate/views/quality-gate-sidebar-list-empty-view.coffee new file mode 100644 index 00000000000..e867a80b1d2 --- /dev/null +++ b/sonar-server/src/main/webapp/javascripts/quality-gate/views/quality-gate-sidebar-list-empty-view.coffee @@ -0,0 +1,12 @@ +define [ + 'backbone.marionette', + 'handlebars' +], ( + Marionette, + Handlebars +) -> + + class QualityGateSidebarListEmptyView extends Marionette.ItemView + tagName: 'li' + className: 'empty' + template: Handlebars.compile jQuery('#quality-gate-sidebar-list-empty-template').html() diff --git a/sonar-server/src/main/webapp/javascripts/quality-gate/views/quality-gate-sidebar-list-view.coffee b/sonar-server/src/main/webapp/javascripts/quality-gate/views/quality-gate-sidebar-list-view.coffee index e8e8cb96169..44269dbd3f1 100644 --- a/sonar-server/src/main/webapp/javascripts/quality-gate/views/quality-gate-sidebar-list-view.coffee +++ b/sonar-server/src/main/webapp/javascripts/quality-gate/views/quality-gate-sidebar-list-view.coffee @@ -2,18 +2,21 @@ define [ 'backbone.marionette', 'handlebars', 'quality-gate/models/quality-gate', - 'quality-gate/views/quality-gate-sidebar-list-item-view' + 'quality-gate/views/quality-gate-sidebar-list-item-view', + 'quality-gate/views/quality-gate-sidebar-list-empty-view' ], ( Marionette, Handlebars, QualityGate, - QualityGateSidebarListItemView + QualityGateSidebarListItemView, + QualityGateSidebarListEmptyView ) -> class QualityGateSidebarListView extends Marionette.CollectionView tagName: 'ol' className: 'navigator-results-list' itemView: QualityGateSidebarListItemView + emptyView: QualityGateSidebarListEmptyView itemViewOptions: (model) -> diff --git a/sonar-server/src/main/webapp/javascripts/quality-gate/views/quality-gate-sidebar-list-view.js b/sonar-server/src/main/webapp/javascripts/quality-gate/views/quality-gate-sidebar-list-view.js index d2ac9a4393d..03de62322aa 100644 --- a/sonar-server/src/main/webapp/javascripts/quality-gate/views/quality-gate-sidebar-list-view.js +++ b/sonar-server/src/main/webapp/javascripts/quality-gate/views/quality-gate-sidebar-list-view.js @@ -3,7 +3,7 @@ var __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; - define(['backbone.marionette', 'handlebars', 'quality-gate/models/quality-gate', 'quality-gate/views/quality-gate-sidebar-list-item-view'], function(Marionette, Handlebars, QualityGate, QualityGateSidebarListItemView) { + define(['backbone.marionette', 'handlebars', 'quality-gate/models/quality-gate', 'quality-gate/views/quality-gate-sidebar-list-item-view', 'quality-gate/views/quality-gate-sidebar-list-empty-view'], function(Marionette, Handlebars, QualityGate, QualityGateSidebarListItemView, QualityGateSidebarListEmptyView) { var QualityGateSidebarListView, _ref; return QualityGateSidebarListView = (function(_super) { __extends(QualityGateSidebarListView, _super); @@ -19,6 +19,8 @@ QualityGateSidebarListView.prototype.itemView = QualityGateSidebarListItemView; + QualityGateSidebarListView.prototype.emptyView = QualityGateSidebarListEmptyView; + QualityGateSidebarListView.prototype.itemViewOptions = function(model) { return { app: this.options.app, diff --git a/sonar-server/src/main/webapp/stylesheets/quality-gates.css b/sonar-server/src/main/webapp/stylesheets/quality-gates.css index 91a8e69ddd5..fbf82419b0e 100644 --- a/sonar-server/src/main/webapp/stylesheets/quality-gates.css +++ b/sonar-server/src/main/webapp/stylesheets/quality-gates.css @@ -373,18 +373,24 @@ left: 230px; padding: 10px; } +.navigator-page #footer { + left: 230px; +} .quality-gates-nav { background-color: #efefef; } .quality-gates-nav .navigator-results-list > li { border-color: transparent; } -.quality-gates-nav .navigator-results-list > li:hover:not(.active) { +.quality-gates-nav .navigator-results-list > li:hover:not(.active):not(.empty) { background-color: #e1e1e1; } .quality-gates-nav .navigator-results-list > li.active { border-color: #4B9FD5; } +.quality-gates-nav .navigator-results-list > li.empty { + cursor: default; +} .quality-gates-nav .navigator-results-list > li .line { padding-top: 2px; padding-bottom: 2px; diff --git a/sonar-server/src/main/webapp/stylesheets/quality-gates.less b/sonar-server/src/main/webapp/stylesheets/quality-gates.less index 265169aba58..134920e1974 100644 --- a/sonar-server/src/main/webapp/stylesheets/quality-gates.less +++ b/sonar-server/src/main/webapp/stylesheets/quality-gates.less @@ -46,6 +46,10 @@ } +.navigator-page #footer { + left: @qualityGateSidebarWidth; +} + .quality-gates-nav { background-color: @navigatorBarBackground; @@ -55,13 +59,17 @@ & > li { border-color: transparent; - &:hover:not(.active) { + &:hover:not(.active):not(.empty) { background-color: @navigatorBorderLightColor; } &.active { border-color: #4B9FD5; } + + &.empty { + cursor: default; + } .line { padding-top: 2px; diff --git a/sonar-server/src/main/webapp/stylesheets/select-list.css b/sonar-server/src/main/webapp/stylesheets/select-list.css index 7a39861bc77..90f9d64f594 100644 --- a/sonar-server/src/main/webapp/stylesheets/select-list.css +++ b/sonar-server/src/main/webapp/stylesheets/select-list.css @@ -24,6 +24,10 @@ border: none; } +.select-list-list-container-readonly .select-list-list { + overflow: visible; +} + .select-list-list-container-readonly .select-list-list > li { border: none; } @@ -68,6 +72,12 @@ visibility: hidden; } + .select-list-list > li.empty-message { + padding: 6px 5px; + border: 1px solid #ddd; + background-color: #efefef; + } + .select-list-list-checkbox { display: inline-block; -- 2.39.5