]> source.dussan.org Git - sonarqube.git/commitdiff
refactor quality gates page
authorStas Vilchik <vilchiks@gmail.com>
Tue, 26 May 2015 13:58:33 +0000 (15:58 +0200)
committerStas Vilchik <vilchiks@gmail.com>
Thu, 28 May 2015 07:48:55 +0000 (09:48 +0200)
77 files changed:
server/sonar-web/Gruntfile.coffee
server/sonar-web/src/main/coffee/apps/quality-gate/app.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/collections/conditions.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/collections/quality-gates.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/layout.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/models/condition.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/models/quality-gate.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/router.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/templates/_quality-gate-intro.hbs [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-actions.hbs [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail-condition.hbs [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail-conditions-empty.hbs [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail-conditions.hbs [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail-header.hbs [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail-projects.hbs [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail.hbs [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-edit.hbs [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-sidebar-list-empty.hbs [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-sidebar-list-item.hbs [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gates-layout.hbs [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-actions-view.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-condition-view.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-conditions-empty-view.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-conditions-view.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-header-view.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-projects-view.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-view.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-edit-view.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-sidebar-list-empty-view.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-sidebar-list-item-view.coffee [deleted file]
server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-sidebar-list-view.coffee [deleted file]
server/sonar-web/src/main/js/apps/quality-gates/actions-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/app.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/condition.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/conditions.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/controller.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/copy-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/create-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/delete-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/details-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/form-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/gate-condition-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-delete-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-empty-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/gate-projects-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/gate-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/gate.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/gates-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/gates.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/header-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/layout.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/rename-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/router.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/templates/_quality-gate-intro.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-actions.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-condition.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-conditions-empty.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-conditions.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-header.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-projects.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-form.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-condition-delete.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-delete.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-gate.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-gates.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-layout.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/components/common/handlebars-extensions.js
server/sonar-web/src/main/less/init/forms.less
server/sonar-web/src/main/less/init/icons.less
server/sonar-web/src/main/less/init/misc.less
server/sonar-web/src/main/less/init/tables.less
server/sonar-web/src/main/webapp/WEB-INF/app/views/quality_gates/index.html.erb
server/sonar-web/src/test/js/quality-gates-spec.js
server/sonar-web/src/test/json/quality-gates-spec/projects.json [new file with mode: 0644]
server/sonar-web/src/test/views/quality_gates.jade

index ac2a0b1bbf7d3fa38aec64dc27c73c64eefe2a12..a8acf348a72731ec0cf82ee5603532af86a0d52c 100644 (file)
@@ -140,7 +140,7 @@ module.exports = (grunt) ->
           { grunt: true, args: ['requirejs:app', '--app=measures'] }
           { grunt: true, args: ['requirejs:app', '--app=nav'] }
           { grunt: true, args: ['requirejs:app', '--app=provisioning'] }
-          { grunt: true, args: ['requirejs:app', '--app=quality-gate'] }
+          { grunt: true, args: ['requirejs:app', '--app=quality-gates'] }
           { grunt: true, args: ['requirejs:app', '--app=quality-profiles'] }
           { grunt: true, args: ['requirejs:app', '--app=source-viewer'] }
           { grunt: true, args: ['requirejs:app', '--app=users'] }
@@ -195,8 +195,8 @@ module.exports = (grunt) ->
             '<%= SOURCE_PATH %>/js/components/common/templates/**/*.hbs'
             '<%= SOURCE_PATH %>/js/apps/coding-rules/templates/**/*.hbs'
           ]
-          '<%= BUILD_PATH %>/js/apps/quality-gate/templates.js': [
-            '<%= SOURCE_PATH %>/coffee/apps/quality-gate/templates/**/*.hbs'
+          '<%= BUILD_PATH %>/js/apps/quality-gates/templates.js': [
+            '<%= SOURCE_PATH %>/js/apps/quality-gates/templates/**/*.hbs'
           ]
           '<%= BUILD_PATH %>/js/apps/quality-profiles/templates.js': [
             '<%= SOURCE_PATH %>/js/apps/quality-profiles/templates/**/*.hbs'
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/app.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/app.coffee
deleted file mode 100644 (file)
index f238dde..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-define [
-  './collections/quality-gates',
-  './views/quality-gate-sidebar-list-view',
-  './views/quality-gate-actions-view',
-  './views/quality-gate-edit-view',
-  './router',
-  './layout'
-], (QualityGates,
-  QualityGateSidebarListItemView,
-  QualityGateActionsView,
-  QualityGateEditView,
-  QualityGateRouter,
-  QualityGateLayout) ->
-
-  # Create a Quality Gate Application
-  App = new Marionette.Application
-
-
-  App.qualityGates = new QualityGates
-
-
-  App.openFirstQualityGate = ->
-    App.layout.headerRegion.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 app: @
-    jQuery('#quality-gates').append @layout.render().el
-    jQuery('#footer').addClass 'search-navigator-footer'
-
-
-  # Construct actions bar
-  App.addInitializer ->
-    @codingRulesHeaderView = new QualityGateActionsView
-      app: @
-    @layout.actionsRegion.show @codingRulesHeaderView
-
-
-  # Construct sidebar
-  App.addInitializer ->
-    @qualityGateSidebarListView = new QualityGateSidebarListItemView
-      collection: @qualityGates
-      app: @
-    @layout.resultsRegion.show @qualityGateSidebarListView
-
-
-  # Construct edit view
-  App.addInitializer ->
-    @qualityGateEditView = new QualityGateEditView app: @
-    @qualityGateEditView.render()
-
-
-  # Start router
-  App.addInitializer ->
-    @router = new QualityGateRouter app: @
-
-    QUALITY_GATES = '/quality_gates'
-    path = window.location.pathname
-    pos = path.indexOf QUALITY_GATES
-    root = path.substr(0, pos + QUALITY_GATES.length)
-    Backbone.history.start pushState: true, root: root
-
-
-  # Open first quality gate when come to the page
-  App.addInitializer ->
-    initial = Backbone.history.fragment == ''
-    App.openFirstQualityGate() if initial
-
-
-  # Call app, Load metrics and the list of quality gates before start the application
-  appXHR = jQuery.ajax
-    url: "#{baseUrl}/api/qualitygates/app"
-  .done (r) =>
-    App.canEdit = r.edit
-    App.periods = r.periods
-    App.metrics = r.metrics
-
-  qualityGatesXHR = App.qualityGates.fetch()
-
-  # Message bundles
-  l10nXHR = window.requestMessages()
-
-  jQuery.when(qualityGatesXHR, appXHR, l10nXHR)
-  .done ->
-    # Start the application
-    App.start()
-
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/collections/conditions.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/collections/conditions.coffee
deleted file mode 100644 (file)
index a3ecccc..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  '../models/condition'
-], (
-  Condition
-) ->
-
-  class Conditions extends Backbone.Collection
-    model: Condition
-    comparator: 'metric'
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/collections/quality-gates.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/collections/quality-gates.coffee
deleted file mode 100644 (file)
index c74a50c..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  '../models/quality-gate'
-], (
-  QualityGate
-) ->
-
-  class QualityGates extends Backbone.Collection
-    model: QualityGate
-
-
-    url: ->
-      "#{baseUrl}/api/qualitygates/list"
-
-
-    # {
-    #   "qualitygates": [
-    #     { "id": 42, "name": "QG 1" },
-    #     { "id": 43, "name": "QG 2" },
-    #     { "id": 44, "name": "QG 3" }
-    #   ],
-    #   "default": 42
-    # }
-    parse: (r) ->
-      r.qualitygates.map (gate) ->
-        _.extend gate, default: gate.id == r.default
-
-
-    comparator: (item) -> item.get('name').toLowerCase()
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/layout.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/layout.coffee
deleted file mode 100644 (file)
index 36da66f..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  './templates'
-], ->
-
-  $ = jQuery
-
-  class AppLayout extends Marionette.Layout
-    template: Templates['quality-gates-layout']
-
-
-    regions:
-      headerRegion: '.search-navigator-workspace-header'
-      actionsRegion: '.search-navigator-filters'
-      resultsRegion: '.quality-gates-results'
-      detailsRegion: '.search-navigator-workspace-details'
-
-
-    onRender: ->
-      $('.search-navigator').addClass 'sticky search-navigator-extended-view'
-      top = $('.search-navigator').offset().top
-      @$('.search-navigator-workspace-header').css top: top
-      @$('.search-navigator-side').css({ top: top }).isolatedScroll()
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/models/condition.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/models/condition.coffee
deleted file mode 100644 (file)
index 7cf8ca0..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define ->
-
-  class Condition extends Backbone.Model
-
-    url: ->
-      "#{baseUrl}/api/qualitygates/create_condition"
-
-
-    save: ->
-      method = unless @isNew() then 'update' else 'create'
-      data =
-        metric: @get('metric').key
-        op: @get('op')
-        warning: @get('warning')
-        error: @get('error')
-
-      unless @get('period') == '0'
-        data.period = @get('period')
-
-      unless @isNew()
-        data.id = @id
-      else
-        data.gateId = @get('gateId')
-
-      jQuery.ajax({
-        url: "#{baseUrl}/api/qualitygates/#{method}_condition"
-        type: 'POST'
-        data: data
-      }).done (r) =>
-        @set 'id', r.id
-
-
-    delete: ->
-      jQuery.ajax
-        url: "#{baseUrl}/api/qualitygates/delete_condition"
-        type: 'POST'
-        data: id: @id
-
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/models/quality-gate.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/models/quality-gate.coffee
deleted file mode 100644 (file)
index 3b6dc6d..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define ->
-
-  class QualityGate extends Backbone.Model
-
-    url: ->
-      "#{baseUrl}/api/qualitygates/show?id=#{@get('id')}"
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/router.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/router.coffee
deleted file mode 100644 (file)
index 31fc35a..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  './models/quality-gate',
-  './views/quality-gate-detail-view',
-  './views/quality-gate-detail-header-view',
-], (
-  QualityGate,
-  QualityGateDetailView,
-  QualityGateDetailHeaderView
-) ->
-
-  class QualityGateRouter extends Backbone.Router
-
-    routes:
-      '': 'index'
-      'show/:id': 'show'
-
-
-    initialize: (options) ->
-      @app = options.app
-
-
-    index: ->
-
-
-    show: (id) ->
-      qualityGate = @app.qualityGates.get id
-      if qualityGate
-        @app.qualityGateSidebarListView.highlight id
-
-        qualityGateDetailHeaderView = new QualityGateDetailHeaderView
-          app: @app
-          model: qualityGate
-        @app.layout.headerRegion.show qualityGateDetailHeaderView
-
-        qualityGateDetailView = new QualityGateDetailView
-          app: @app
-          model: qualityGate
-        @app.layout.detailsRegion.show qualityGateDetailView
-        qualityGateDetailView.$el.hide()
-
-        qualityGate.fetch().done ->
-          qualityGateDetailView.$el.show()
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/templates/_quality-gate-intro.hbs b/server/sonar-web/src/main/coffee/apps/quality-gate/templates/_quality-gate-intro.hbs
deleted file mode 100644 (file)
index 6f194fb..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<div class="">
-  <p class="spacer-bottom">Quality Gates are collections of simple boolean thresholds set on project measures. A project
-    must pass each of the thresholds in order to pass the Quality Gate as a whole.</p>
-
-  <p>It is possible to set a default Quality Gate, which will be applied to all projects not explicitly assigned to some
-    other gate.</p>
-</div>
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-actions.hbs b/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-actions.hbs
deleted file mode 100644 (file)
index 6826aa9..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<h1 class="page-title">{{t 'quality_gates.page'}}</h1>
-{{#if canEdit}}
-  <div class="page-actions">
-    <div class="button-group">
-      <button id="quality-gate-add">{{t 'create'}}</button>
-    </div>
-  </div>
-{{/if}}
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail-condition.hbs b/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail-condition.hbs
deleted file mode 100644 (file)
index 9be2680..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-<td nowrap>
-  {{metric.name}}
-  {{#if metric.hidden}}
-    <span class="deprecated">{{t 'deprecated'}}</span>
-  {{/if}}
-</td>
-<td width="20%" nowrap>
-  {{#if canEdit}}
-    <select name="period" style="width: 100%">
-      {{#unless isDiffMetric}}<option value="0">{{t 'value'}}</option>{{/unless}}
-      {{#each periods}}<option value="{{key}}">&Delta; {{text}}</option>{{/each}}
-    </select>
-  {{else}}
-    {{#if periodText}}&Delta; {{periodText}}
-    {{else}}{{t 'value'}}
-    {{/if}}
-  {{/if}}
-</td>
-<td width="10%" nowrap>
-  {{#if canEdit}}
-    <select name="operator">
-      {{#operators metric.type}}
-        <option value="{{this}}">{{t 'quality_gates.operator' this}}</option>
-      {{/operators}}
-    </select>
-  {{else}}
-    {{t 'quality_gates.operator' op}}
-  {{/if}}
-</td>
-<td width="1" class="nowrap">
-  <i class="icon-alert-warn" title="{{t 'quality_gates.warning_tooltip'}}"></i>
-  {{#if canEdit}}
-    <input name="warning" class="input-small" data-type="{{metric.type}}" placeholder="{{metric.placeholder}}"
-           type="text">
-  {{else}}
-    {{warning}}
-  {{/if}}
-</td>
-<td width="1" class="nowrap">
-  <i class="icon-alert-error" title="{{t 'quality_gates.error_tooltip'}}"></i>
-  {{#if canEdit}}
-    <input name="error" class="input-small" data-type="{{metric.type}}" placeholder="{{metric.placeholder}}"
-           type="text">
-  {{else}}
-    {{error}}
-  {{/if}}
-</td>
-<td class="quality-gate-condition-actions" width="120px" nowrap>
-  {{#if canEdit}}
-    {{#if id}}
-      <div class="button-group">
-        <button class="update-condition" disabled>{{t 'update_verb'}}</button>
-        <button class="button-red delete-condition">{{t 'delete'}}</button>
-      </div>
-    {{else}}
-      <div class="button-group">
-        <button class="add-condition">{{t 'add_verb'}}</button>
-        <a class="action cancel-add-condition">{{t 'cancel'}}</a>
-      </div>
-    {{/if}}
-  {{/if}}
-</td>
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail-conditions-empty.hbs b/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail-conditions-empty.hbs
deleted file mode 100644 (file)
index 40c9266..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-<td colspan="6">
-  {{t 'quality_gates.no_conditions'}}
-</td>
\ No newline at end of file
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail-conditions.hbs b/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail-conditions.hbs
deleted file mode 100644 (file)
index 7488887..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-<div class="quality-gate-section-name">{{t 'quality_gates.conditions'}}</div>
-
-<div class="quality-gate-introduction">
-  <p>{{t 'quality_gates.introduction'}}
-    <a class="link-action quality-gate-introduction-show-more">{{t 'more'}}</a>
-  </p>
-  <div class="quality-gate-introduction-more inline-help">
-    {{t 'quality_gates.health_icons'}}
-    <ul>
-      <li>
-        <i class="icon-alert-ok"></i>
-        {{t 'alerts.notes.ok'}}
-      </li>
-      <li>
-        <i class="icon-alert-warn"></i>
-        {{t 'alerts.notes.warn'}}
-      </li>
-      <li>
-        <i class="icon-alert-error"></i>
-        {{t 'alerts.notes.error'}}
-      </li>
-    </ul>
-  </div>
-</div>
-
-{{#if canEdit}}
-  <div class="quality-gate-new-condition">
-    <label for="quality-gate-new-condition-metric">{{t 'quality_gates.add_condition'}}:</label>
-    <select id="quality-gate-new-condition-metric">
-      <option></option>
-      {{#each metricGroups}}
-        <optgroup label="{{domain}}">
-            {{#each metrics}}<option value="{{key}}">{{name}}</option>{{/each}}
-        </optgroup>
-      {{/each}}
-    </select>
-  </div>
-{{/if}}
-
-<div class="quality-gate-conditions-wrap">
-  <table class="data zebra width100 marginbottom10 spaced quality-gate-conditions">
-    <thead><tr></tr></thead>
-    <tbody></tbody>
-  </table>
-</div>
\ No newline at end of file
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail-header.hbs b/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail-header.hbs
deleted file mode 100644 (file)
index 71f7e3b..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<h2 class="search-navigator-header-component">{{name}}</h2>
-
-{{#if canEdit}}
-  <div class="search-navigator-header-actions">
-    <div class="button-group">
-      <button id="quality-gate-rename">{{t 'rename'}}</button>
-      <button id="quality-gate-copy">{{t 'copy'}}</button>
-      {{#if default}}
-        <button id="quality-gate-unset-as-default">{{t 'unset_as_default'}}</button>
-      {{else}}
-        <button id="quality-gate-set-as-default">{{t 'set_as_default'}}</button>
-      {{/if}}
-      <button id="quality-gate-delete" class="button-red">{{t 'delete'}}</button>
-    </div>
-  </div>
-{{/if}}
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail-projects.hbs b/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail-projects.hbs
deleted file mode 100644 (file)
index 989cae0..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<div class="quality-gate-section-name">{{t 'quality_gates.projects'}}</div>
-
-{{#if default}}
-  <p class="quality-gate-default-message">
-  {{#if canEdit}}
-    {{t 'quality_gates.projects_for_default.edit'}}
-  {{else}}
-    {{t 'quality_gates.projects_for_default'}}
-  {{/if}}
-  </p>
-{{else}}
-  <div id="select-list-projects"></div>
-{{/if}}
\ No newline at end of file
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail.hbs b/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-detail.hbs
deleted file mode 100644 (file)
index 0c5467b..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-<div id="quality-gate-conditions" class="quality-gate-section"></div>
-<div id="quality-gate-projects" class="quality-gate-section"></div>
\ No newline at end of file
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-edit.hbs b/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-edit.hbs
deleted file mode 100644 (file)
index 5aeb891..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<form>
-  <div class="modal-head">
-    {{#if method}}
-      <h2>{{t 'quality_gates' method }}</h2>
-    {{/if}}
-  </div>
-
-  <div class="modal-body">
-    <div class="alert alert-danger"></div>
-    <div class="modal-field">
-      <label for="quality-gate-edit-name">{{t 'name'}} <em class="mandatory">*</em></label>
-      <input id="quality-gate-edit-name" type="text" size="50" maxlength="100" value="{{name}}">
-    </div>
-  </div>
-
-  <div class="modal-foot">
-    {{#eq method "rename"}}<button>{{t 'save'}}</button>{{/eq}}
-    {{#eq method "copy"}}<button>{{t 'copy'}}</button>{{/eq}}
-    {{#eq method "create"}}<button>{{t 'create'}}</button>{{/eq}}
-    <a id="quality-gate-cancel-create" class="action">{{t 'cancel'}}</a>
-  </div>
-</form>
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-sidebar-list-empty.hbs b/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-sidebar-list-empty.hbs
deleted file mode 100644 (file)
index 8b017f0..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<div class="line line-nowrap">{{t 'quality_gates.noQualityGates'}}</div>
\ No newline at end of file
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-sidebar-list-item.hbs b/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gate-sidebar-list-item.hbs
deleted file mode 100644 (file)
index 2e92b2e..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<table>
-  <tr>
-    <td class="text-top">{{name}}</td>
-    <td class="text-top thin nowrap spacer-left">
-      {{#if default}}
-        <span class="badge pull-right">{{t 'default'}}</span>
-      {{/if}}
-    </td>
-  </tr>
-</table>
-
-
-
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gates-layout.hbs b/server/sonar-web/src/main/coffee/apps/quality-gate/templates/quality-gates-layout.hbs
deleted file mode 100644 (file)
index 3428ac4..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<div class="search-navigator-side search-navigator-side-light">
-  <div class="search-navigator-filters"></div>
-  <div class="quality-gates-results panel"></div>
-</div>
-
-<div class="search-navigator-workspace">
-  <div class="search-navigator-workspace-header">&nbsp;</div>
-  <div class="search-navigator-workspace-details">{{> '_quality-gate-intro'}}</div>
-</div>
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-actions-view.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-actions-view.coffee
deleted file mode 100644 (file)
index 46e5f3e..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  '../models/quality-gate'
-  '../templates'
-], (
-  QualityGate
-) ->
-
-  class QualityGateActionsView extends Marionette.ItemView
-    template: Templates['quality-gate-actions']
-
-
-    events:
-      'click #quality-gate-add': 'add'
-
-
-    add: ->
-      qualityGate = new QualityGate()
-      @options.app.qualityGateEditView.method = 'create'
-      @options.app.qualityGateEditView.model = qualityGate
-      @options.app.qualityGateEditView.show()
-
-
-    serializeData: ->
-      _.extend super, canEdit: @options.app.canEdit
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-condition-view.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-condition-view.coffee
deleted file mode 100644 (file)
index 54c1061..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  '../templates'
-], ->
-
-  class QualityGateDetailConditionView extends Marionette.ItemView
-    tagName: 'tr'
-    template: Templates['quality-gate-detail-condition']
-    spinner: '<i class="spinner"></i>'
-
-
-    modelEvents:
-      'change:id': 'render'
-
-
-    ui:
-      periodSelect: '[name=period]'
-      operatorSelect: '[name=operator]'
-      warningInput: '[name=warning]'
-      errorInput: '[name=error]'
-      actionsBox: '.quality-gate-condition-actions'
-      updateButton: '.update-condition'
-      deleteButton: '.delete-condition'
-
-
-    events:
-      'click @ui.updateButton': 'saveCondition'
-      'click @ui.deleteButton': 'deleteCondition'
-      'click .add-condition': 'saveCondition'
-      'click .cancel-add-condition': 'cancelAddCondition'
-      'keyup :input': 'enableUpdate'
-      'change :input': 'enableUpdate'
-
-
-    initialize: ->
-      @populateMetric()
-
-
-    populateMetric: ->
-      metricKey = @model.get('metric')
-      metric = _.findWhere @options.app.metrics, key: metricKey
-      if metric?
-        switch metric.type
-          when 'WORK_DUR' then metric.placeholder = '1d 7h 59min'
-          when 'RATING' then metric.placeholder = 'A'
-      @model.set { metric: metric }, { silent: true }
-      @model.set { isDiffMetric: metric.key.indexOf('new_') == 0 }, { silent: true }
-
-
-    onRender: ->
-      @ui.periodSelect.val @model.get('period') || '0'
-      @ui.operatorSelect.val @model.get('op')
-      @ui.warningInput.val @model.get('warning')
-      @ui.errorInput.val @model.get('error')
-
-      @ui.periodSelect.select2
-        allowClear: false
-        minimumResultsForSearch: 999
-        width: '100%'
-
-      @ui.operatorSelect.select2
-        allowClear: false
-        minimumResultsForSearch: 999
-        width: '150px'
-
-      @ui.periodSelect.select2('open') if @model.isNew()
-
-
-    showSpinner: ->
-      jQuery(@spinner).prependTo @ui.actionsBox
-      @ui.actionsBox.find(':not(.spinner)').hide()
-
-
-    hideSpinner: ->
-      @ui.actionsBox.find('.spinner').remove()
-      @ui.actionsBox.find(':not(.spinner)').show()
-
-
-    saveCondition: ->
-      @showSpinner()
-      @model.set
-        period: @ui.periodSelect.val()
-        op: @ui.operatorSelect.val()
-        warning: @ui.warningInput.val()
-        error: @ui.errorInput.val()
-      @model.save()
-        .always =>
-          @ui.updateButton.prop 'disabled', true
-          @hideSpinner()
-        .done =>
-          @options.collectionView.updateConditions()
-
-
-    deleteCondition: ->
-      confirmDialog
-        title: t 'quality_gates.delete_condition'
-        html: tp 'quality_gates.delete_condition.confirm.message', @model.get('metric').name
-        yesLabel: t 'delete'
-        noLabel: t 'cancel'
-        yesHandler: =>
-          @showSpinner()
-          @model.delete().done =>
-            @options.collectionView.collection.remove @model
-            @options.collectionView.updateConditions()
-            @close()
-        always: => @ui.deleteButton.blur()
-
-
-    cancelAddCondition: ->
-      @close()
-
-
-    enableUpdate: ->
-      @ui.updateButton.prop 'disabled', false
-
-
-    serializeData: ->
-      period = _.findWhere(@options.app.periods, key: this.model.get('period'))
-      data = _.extend super,
-        canEdit: @options.app.canEdit
-        periods: @options.app.periods
-        periodText: period?.text
-      unless @options.app.canEdit
-        _.extend data,
-          warning: jQuery('<input>').data('type', @model.get('metric').type).val(@model.get('warning')).originalVal()
-          error: jQuery('<input>').data('type', @model.get('metric').type).val(@model.get('error')).originalVal()
-      data
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-conditions-empty-view.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-conditions-empty-view.coffee
deleted file mode 100644 (file)
index b285ca0..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  '../templates'
-], ->
-
-  class QualityGateDetailConditionsView extends Marionette.ItemView
-    tagName: 'tr'
-    template: Templates['quality-gate-detail-conditions-empty']
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-conditions-view.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-conditions-view.coffee
deleted file mode 100644 (file)
index 29843cb..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  '../models/condition',
-  './quality-gate-detail-condition-view',
-  './quality-gate-detail-conditions-empty-view'
-  '../templates'
-], (
-  Condition,
-  QualityGateDetailConditionView,
-  QualityGateDetailConditionsEmptyView,
-) ->
-
-  class QualityGateDetailConditionsView extends Marionette.CompositeView
-    template: Templates['quality-gate-detail-conditions']
-    itemView: QualityGateDetailConditionView
-    emptyView: QualityGateDetailConditionsEmptyView
-    itemViewContainer: '.quality-gate-conditions tbody'
-
-
-    ui:
-      metricSelect: '#quality-gate-new-condition-metric'
-      introductionShowMore: '.quality-gate-introduction-show-more'
-      introductionMore: '.quality-gate-introduction-more'
-
-
-    events:
-      'click @ui.introductionShowMore': 'showMoreIntroduction'
-      'change @ui.metricSelect': 'addCondition'
-
-
-    itemViewOptions: ->
-      app: @options.app
-      collectionView: @
-
-
-    appendHtml: (compositeView, itemView) ->
-      if (compositeView.isBuffering)
-        compositeView.elBuffer.appendChild itemView.el
-        compositeView._bufferedChildren.push itemView
-      else
-        container = @getItemViewContainer compositeView
-        container.prepend itemView.el
-
-
-    onRender: ->
-      @ui.introductionMore.hide()
-      @ui.metricSelect.select2
-        allowClear: false,
-        width: '250px',
-        placeholder: t('alerts.select_metric')
-
-
-    groupedMetrics: ->
-      metrics = @options.app.metrics
-      metrics = _.filter metrics, (metric) ->
-        !metric.hidden
-      metrics = _.groupBy metrics, 'domain'
-      metrics = _.map metrics, (metrics, domain) ->
-        domain: domain, metrics: _.sortBy metrics, 'short_name'
-      _.sortBy metrics, 'domain'
-
-
-    serializeData: ->
-      _.extend super,
-        canEdit: @options.app.canEdit
-        metricGroups: @groupedMetrics()
-
-
-    showMoreIntroduction: ->
-      @ui.introductionShowMore.hide()
-      @ui.introductionMore.show()
-
-
-    addCondition: ->
-      metric = @ui.metricSelect.val()
-      @ui.metricSelect.select2('val', '')
-      condition = new Condition
-        metric: metric
-        gateId: @options.gateId
-      @collection.unshift condition
-
-
-    updateConditions: ->
-      conditions = @collection.map (item) -> _.extend item.toJSON(),
-        metric: item.get('metric').key
-      @options.qualityGate.set { conditions: conditions }, { silent: true }
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-header-view.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-header-view.coffee
deleted file mode 100644 (file)
index 1980b9e..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  '../models/quality-gate'
-  '../templates',
-], (
-  QualityGate
-) ->
-
-  class QualityGateDetailHeaderView extends Marionette.ItemView
-    template: Templates['quality-gate-detail-header']
-    spinner: '<i class="spinner"></i>'
-
-
-    modelEvents:
-      'change': 'render'
-
-
-    ui:
-      deleteButton: '#quality-gate-delete'
-
-
-    events:
-      'click #quality-gate-rename': 'renameQualityGate'
-      'click #quality-gate-copy': 'copyQualityGate'
-      'click @ui.deleteButton': 'deleteQualityGate'
-      'click #quality-gate-set-as-default': 'setAsDefault'
-      'click #quality-gate-unset-as-default': 'unsetAsDefault'
-
-
-    renameQualityGate: ->
-      @options.app.qualityGateEditView.method = 'rename'
-      @options.app.qualityGateEditView.model = @model
-      @options.app.qualityGateEditView.show()
-
-
-    copyQualityGate: ->
-      copiedModel = new QualityGate @model.toJSON()
-      copiedModel.set 'default', false
-      @options.app.qualityGateEditView.method = 'copy'
-      @options.app.qualityGateEditView.model = copiedModel
-      @options.app.qualityGateEditView.show()
-
-
-    deleteQualityGate: ->
-      message = if @model.get 'default' then 'quality_gates.delete.confirm.default' else 'quality_gates.delete.confirm.message'
-      message = tp message, @model.get('name')
-      confirmDialog
-        title: t 'quality_gates.delete'
-        html: message
-        yesLabel: t 'delete'
-        noLabel: t 'cancel'
-        yesHandler: =>
-          jQuery.ajax
-            type: 'POST'
-            url: "#{baseUrl}/api/qualitygates/destroy"
-            data: id: @model.id
-          .done => @options.app.deleteQualityGate @model.id
-        always: => @ui.deleteButton.blur()
-
-
-    changeDefault: (set) ->
-      data = if set then { id: @model.id } else {}
-      method = if set then 'set_as_default' else 'unset_default'
-      jQuery.ajax
-        type: 'POST'
-        url: "#{baseUrl}/api/qualitygates/#{method}"
-        data: data
-      .done =>
-        @options.app.unsetDefaults @model.id
-        @model.set 'default', !@model.get('default')
-
-
-    setAsDefault: ->
-      @changeDefault true
-
-
-    unsetAsDefault: ->
-      @changeDefault false
-
-
-    serializeData: ->
-      _.extend super, canEdit: @options.app.canEdit
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-projects-view.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-projects-view.coffee
deleted file mode 100644 (file)
index 726d95a..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  'components/common/select-list'
-  '../templates'
-], ->
-
-  class QualityGateDetailProjectsView extends Marionette.ItemView
-    template: Templates['quality-gate-detail-projects']
-
-
-    onRender: ->
-      unless @model.get('default')
-        new SelectList
-          el: @$('#select-list-projects')
-          width: '100%'
-          readOnly: !@options.app.canEdit
-          focusSearch: false
-          format: (item) -> item.name
-          searchUrl: "#{baseUrl}/api/qualitygates/search?gateId=#{@options.gateId}"
-          selectUrl: "#{baseUrl}/api/qualitygates/select"
-          deselectUrl: "#{baseUrl}/api/qualitygates/deselect"
-          extra:
-            gateId: @options.gateId
-          selectParameter: 'projectId'
-          selectParameterValue: 'id'
-          labels:
-            selected: t('quality_gates.projects.with')
-            deselected: t('quality_gates.projects.without')
-            all: t('quality_gates.projects.all')
-            noResults: t('quality_gates.projects.noResults')
-          tooltips:
-            select: t('quality_gates.projects.select_hint')
-            deselect: t('quality_gates.projects.deselect_hint')
-
-    serializeData: ->
-      _.extend super, canEdit: @options.app.canEdit
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-view.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-detail-view.coffee
deleted file mode 100644 (file)
index 10b4b6e..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  '../collections/conditions',
-  './quality-gate-detail-header-view',
-  './quality-gate-detail-conditions-view',
-  './quality-gate-detail-projects-view'
-  '../templates',
-], (
-  Conditions,
-  QualityGateDetailHeaderView,
-  QualityGateDetailConditionsView,
-  QualityGateDetailProjectsView
-) ->
-
-  class QualityGateDetailView extends Marionette.Layout
-    template: Templates['quality-gate-detail']
-
-
-    regions:
-      conditionsRegion: '#quality-gate-conditions'
-      projectsRegion: '#quality-gate-projects'
-
-
-    modelEvents:
-      'change': 'render'
-
-
-    onRender: ->
-      @showConditions()
-      @showProjects()
-
-
-    showConditions: ->
-      conditions = new Conditions @model.get('conditions')
-      view = new QualityGateDetailConditionsView
-        app: @options.app
-        collection: conditions
-        gateId: @model.id
-        qualityGate: @model
-      @conditionsRegion.show view
-
-
-    showProjects: ->
-      view = new QualityGateDetailProjectsView
-        app: @options.app
-        model: @model
-        gateId: @model.id
-      @projectsRegion.show view
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-edit-view.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-edit-view.coffee
deleted file mode 100644 (file)
index 67adfec..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  '../templates'
-], ->
-
-  class QualityGateEditView extends Marionette.ItemView
-    template: Templates['quality-gate-edit']
-
-
-    ui:
-      nameInput: '#quality-gate-edit-name'
-
-
-    events:
-      'submit form': 'onSubmit'
-      'click #quality-gate-cancel-create': 'hide'
-
-
-    onRender: ->
-      @$el.dialog
-        dialogClass: 'no-close',
-        width: '600px',
-        draggable: false,
-        autoOpen: false,
-        modal: true,
-        minHeight: 50,
-        resizable: false,
-        title: null
-
-
-    show: ->
-      @render()
-      @$el.dialog 'open'
-      @ui.nameInput.focus()
-
-
-    hide: ->
-      @$el.dialog 'close'
-
-
-    saveRequest: (data) ->
-      jQuery.ajax
-        type: 'POST'
-        url: "#{baseUrl}/api/qualitygates/#{@method}"
-        data: data
-      .done => @hide()
-
-
-    onSubmit: (e) ->
-      e.preventDefault()
-      switch @method
-        when 'create' then @createQualityGate()
-        when 'copy' then @copyQualityGate()
-        when 'rename' then @saveQualityGate()
-        else
-
-
-    createQualityGate: ->
-      data = name: @ui.nameInput.val()
-      @saveRequest(data).done (r) =>
-        @model.set id: r.id, name: r.name
-        @options.app.qualityGates.add @model
-        @options.app.router.navigate "show/#{r.id}", trigger: true
-
-
-    saveQualityGate: ->
-      data = id: @model.id, name: @ui.nameInput.val()
-      @saveRequest(data).done (r) =>
-        @model.set name: r.name
-
-
-    copyQualityGate: ->
-      data = id: @model.id, name: @ui.nameInput.val()
-      @saveRequest(data).done (r) =>
-        @model.set id: r.id, name: r.name
-        @options.app.qualityGates.add @model
-        @options.app.router.navigate "show/#{r.id}", trigger: true
-
-
-    serializeData: ->
-      if @model
-        _.extend @model.toJSON(), method: @method
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-sidebar-list-empty-view.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-sidebar-list-empty-view.coffee
deleted file mode 100644 (file)
index 9ec9393..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  '../templates'
-], ->
-
-  class QualityGateSidebarListEmptyView extends Marionette.ItemView
-    tagName: 'li'
-    className: 'empty'
-    template: Templates['quality-gate-sidebar-list-empty']
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-sidebar-list-item-view.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-sidebar-list-item-view.coffee
deleted file mode 100644 (file)
index 3527a6c..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  '../templates'
-], ->
-
-  class QualityGateSidebarListItemView extends Marionette.ItemView
-    tagName: 'a'
-    className: 'list-group-item'
-    template: Templates['quality-gate-sidebar-list-item']
-
-
-    modelEvents:
-      'change': 'render'
-
-
-    events:
-      'click': 'showQualityGate'
-
-
-    onRender: ->
-      @$el.toggleClass 'active', @options.highlighted
-
-
-    showQualityGate: ->
-      @options.app.router.navigate "show/#{@model.id}", trigger: true
diff --git a/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-sidebar-list-view.coffee b/server/sonar-web/src/main/coffee/apps/quality-gate/views/quality-gate-sidebar-list-view.coffee
deleted file mode 100644 (file)
index 7fd40da..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  './quality-gate-sidebar-list-item-view',
-  './quality-gate-sidebar-list-empty-view'
-], (
-  QualityGateSidebarListItemView,
-  QualityGateSidebarListEmptyView,
-) ->
-
-  class QualityGateSidebarListView extends Marionette.CollectionView
-    className: 'list-group'
-    itemView: QualityGateSidebarListItemView
-    emptyView: QualityGateSidebarListEmptyView
-
-
-    itemViewOptions: (model) ->
-      app: @options.app
-      highlighted: model.get('id') == +@highlighted
-
-
-    highlight: (id) ->
-      @highlighted = id
-      @render()
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/actions-view.js b/server/sonar-web/src/main/js/apps/quality-gates/actions-view.js
new file mode 100644 (file)
index 0000000..156c7d8
--- /dev/null
@@ -0,0 +1,27 @@
+define([
+  './create-view',
+  './templates'
+], function (CreateView) {
+
+  return Marionette.ItemView.extend({
+    template: Templates['quality-gate-actions'],
+
+    events: {
+      'click #quality-gate-add': 'add'
+    },
+
+    add: function (e) {
+      e.preventDefault();
+      new CreateView({
+        collection: this.collection
+      }).render();
+    },
+
+    serializeData: function () {
+      return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
+        canEdit: this.options.canEdit
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/app.js b/server/sonar-web/src/main/js/apps/quality-gates/app.js
new file mode 100644 (file)
index 0000000..f5d521f
--- /dev/null
@@ -0,0 +1,69 @@
+define([
+  './gates',
+  './gates-view',
+  './actions-view',
+  './router',
+  './layout',
+  './controller'
+], function (Gates, GatesView, ActionsView, Router, Layout, Controller) {
+
+  var $ = jQuery,
+      App = new Marionette.Application();
+
+  var init = function (options) {
+    // Layout
+    this.layout = new Layout({ el: options.el });
+    this.layout.render();
+    $('#footer').addClass('search-navigator-footer');
+
+    // Gates List
+    this.gates = new Gates();
+
+    // Controller
+    this.controller = new Controller({ app: this });
+
+    // Header
+    this.actionsView = new ActionsView({
+      canEdit: this.canEdit,
+      collection: this.gates
+    });
+    this.layout.actionsRegion.show(this.actionsView);
+
+    // List
+    this.gatesView = new GatesView({
+      canEdit: this.canEdit,
+      collection: this.gates
+    });
+    this.layout.resultsRegion.show(this.gatesView);
+
+    // Router
+    this.router = new Router({ app: this });
+    Backbone.history.start({
+      pushState: true,
+      root: getRoot()
+    });
+  };
+
+  var appXHR = $.get(baseUrl + '/api/qualitygates/app')
+      .done(function (r) {
+        App.canEdit = r.edit;
+        App.periods = r.periods;
+        App.metrics = r.metrics;
+      });
+
+  App.on('start', function (options) {
+    $.when(window.requestMessages(), appXHR).done(function () {
+      init.call(App, options);
+    });
+  });
+
+  function getRoot () {
+    var ROOT = '/quality_gates',
+        path = window.location.pathname,
+        pos = path.indexOf(ROOT);
+    return path.substr(0, pos + ROOT.length);
+  }
+
+  return App;
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/condition.js b/server/sonar-web/src/main/js/apps/quality-gates/condition.js
new file mode 100644 (file)
index 0000000..9ca451e
--- /dev/null
@@ -0,0 +1,47 @@
+define(function () {
+
+  return Backbone.Model.extend({
+
+    defaults: {
+      period: 0
+    },
+
+    url: function () {
+      return baseUrl + '/api/qualitygates';
+    },
+
+    createUrl: function () {
+      return this.url() + '/create_condition';
+    },
+
+    updateUrl: function () {
+      return this.url() + '/update_condition';
+    },
+
+    deleteUrl: function () {
+      return this.url() + '/delete_condition';
+    },
+
+    sync: function (method, model, options) {
+      var opts = options || {};
+      opts.type = 'POST';
+      if (method === 'create') {
+        opts.url = this.createUrl();
+        opts.data = model.toJSON();
+      }
+      if (method === 'update') {
+        opts.url = this.updateUrl();
+        opts.data = model.toJSON();
+      }
+      if (method === 'delete') {
+        opts.url = this.deleteUrl();
+        opts.data = { id: model.id };
+      }
+      if (opts.data.period === '0') {
+        delete opts.data.period;
+      }
+      return Backbone.ajax(opts);
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/conditions.js b/server/sonar-web/src/main/js/apps/quality-gates/conditions.js
new file mode 100644 (file)
index 0000000..49cbb23
--- /dev/null
@@ -0,0 +1,10 @@
+define([
+  './condition'
+], function (Condition) {
+
+  return Backbone.Collection.extend({
+    model: Condition,
+    comparator: 'metric'
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/controller.js b/server/sonar-web/src/main/js/apps/quality-gates/controller.js
new file mode 100644 (file)
index 0000000..0b03a0a
--- /dev/null
@@ -0,0 +1,65 @@
+define([
+  './gate',
+  './details-view',
+  './header-view'
+], function (Gate, DetailsView, HeaderView) {
+
+  return Marionette.Controller.extend({
+
+    initialize: function (options) {
+      this.app = options.app;
+      this.canEdit = this.app.canEdit;
+      this.listenTo(this.app.gates, 'select', this.onSelect);
+      this.listenTo(this.app.gates, 'destroy', this.onDestroy);
+    },
+
+    index: function () {
+      this.app.gates.fetch();
+    },
+
+    show: function (id) {
+      var that = this;
+      this.app.gates.fetch().done(function () {
+        var gate = that.app.gates.get(id);
+        if (gate != null) {
+          gate.trigger('select', gate, { trigger: false });
+        }
+      });
+    },
+
+    onSelect: function (gate, options) {
+      var that = this,
+          route = 'show/' + gate.id,
+          opts = _.defaults(options || {}, { trigger: true });
+      if (opts.trigger) {
+        this.app.router.navigate(route);
+      }
+      this.app.gatesView.highlight(gate.id);
+      gate.fetch().done(function () {
+        var headerView = new HeaderView({
+          model: gate,
+          canEdit: that.canEdit
+        });
+        that.app.layout.headerRegion.show(headerView);
+
+        var detailsView = new DetailsView({
+          model: gate,
+          canEdit: that.canEdit,
+          metrics: that.app.metrics,
+          periods: that.app.periods
+        });
+        that.app.layout.detailsRegion.show(detailsView);
+      });
+    },
+
+    onDestroy: function () {
+      this.app.router.navigate('');
+      this.app.layout.headerRegion.reset();
+      this.app.layout.detailsRegion.reset();
+      //this.app.layout.renderIntro();
+      this.app.gatesView.highlight(null);
+    }
+
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/copy-view.js b/server/sonar-web/src/main/js/apps/quality-gates/copy-view.js
new file mode 100644 (file)
index 0000000..fdd6c97
--- /dev/null
@@ -0,0 +1,24 @@
+define([
+  './form-view'
+], function (FormView) {
+
+  return FormView.extend({
+    method: 'copy',
+
+    prepareRequest: function () {
+      var that = this;
+      var url = baseUrl + '/api/qualitygates/copy',
+          name = this.$('#quality-gate-form-name').val(),
+          options = {
+            url: url,
+            data: { id: this.model.id, name: name }
+          };
+      return this.sendRequest(options)
+          .done(function (r) {
+            var gate = that.addGate(r);
+            gate.trigger('select', gate);
+          });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/create-view.js b/server/sonar-web/src/main/js/apps/quality-gates/create-view.js
new file mode 100644 (file)
index 0000000..f58863b
--- /dev/null
@@ -0,0 +1,24 @@
+define([
+  './form-view'
+], function (FormView) {
+
+  return FormView.extend({
+    method: 'create',
+
+    prepareRequest: function () {
+      var that = this;
+      var url = baseUrl + '/api/qualitygates/create',
+          name = this.$('#quality-gate-form-name').val(),
+          options = {
+            url: url,
+            data: { name: name }
+          };
+      return this.sendRequest(options)
+          .done(function (r) {
+            var gate = that.addGate(r);
+            gate.trigger('select', gate);
+          });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/delete-view.js b/server/sonar-web/src/main/js/apps/quality-gates/delete-view.js
new file mode 100644 (file)
index 0000000..7939344
--- /dev/null
@@ -0,0 +1,33 @@
+define([
+  'components/common/modal-form',
+  './templates'
+], function (ModalForm) {
+
+  return ModalForm.extend({
+    template: Templates['quality-gates-delete'],
+
+    onFormSubmit: function () {
+      ModalForm.prototype.onFormSubmit.apply(this, arguments);
+      this.disableForm();
+      this.sendRequest();
+    },
+
+    sendRequest: function () {
+      var that = this,
+          options = {
+            statusCode: {
+              // do not show global error
+              400: null
+            }
+          };
+      return this.model.destroy(options)
+          .done(function () {
+            that.close();
+          }).fail(function (jqXHR) {
+            that.enableForm();
+            that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
+          });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/details-view.js b/server/sonar-web/src/main/js/apps/quality-gates/details-view.js
new file mode 100644 (file)
index 0000000..df05471
--- /dev/null
@@ -0,0 +1,46 @@
+define([
+  './conditions',
+  './gate-conditions-view',
+  './gate-projects-view',
+  './templates'
+], function (Conditions, DetailConditionsView, ProjectsView) {
+
+  return Marionette.Layout.extend({
+    template: Templates['quality-gate-detail'],
+
+    regions: {
+      conditionsRegion: '#quality-gate-conditions',
+      projectsRegion: '#quality-gate-projects'
+    },
+
+    modelEvents: {
+      'change': 'render'
+    },
+
+    onRender: function () {
+      this.showConditions();
+      this.showProjects();
+    },
+
+    showConditions: function () {
+      var conditions = new Conditions(this.model.get('conditions')),
+          view = new DetailConditionsView({
+            canEdit: this.options.canEdit,
+            collection: conditions,
+            model: this.model,
+            metrics: this.options.metrics,
+            periods: this.options.periods
+          });
+      this.conditionsRegion.show(view);
+    },
+
+    showProjects: function () {
+      var view = new ProjectsView({
+        canEdit: this.options.canEdit,
+        model: this.model
+      });
+      this.projectsRegion.show(view);
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/form-view.js b/server/sonar-web/src/main/js/apps/quality-gates/form-view.js
new file mode 100644 (file)
index 0000000..5da9dfd
--- /dev/null
@@ -0,0 +1,47 @@
+define([
+  'components/common/modal-form',
+  './gate',
+  './templates'
+], function (ModalForm, Gate) {
+
+  return ModalForm.extend({
+    template: Templates['quality-gate-form'],
+
+    onFormSubmit: function () {
+      ModalForm.prototype.onFormSubmit.apply(this, arguments);
+      this.disableForm();
+      this.prepareRequest();
+    },
+
+    sendRequest: function (options) {
+      var that = this,
+          opts = _.defaults(options || {}, {
+            type: 'POST',
+            statusCode: {
+              // do not show global error
+              400: null
+            }
+          });
+      return Backbone.ajax(opts)
+          .done(function () {
+            that.close();
+          }).fail(function (jqXHR) {
+            that.enableForm();
+            that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
+          });
+    },
+
+    addGate: function (attrs) {
+      var gate = new Gate(attrs);
+      this.collection.add(gate, { merge: true });
+      return gate;
+    },
+
+    serializeData: function () {
+      return _.extend(ModalForm.prototype.serializeData.apply(this, arguments), {
+        method: this.method
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate-condition-view.js b/server/sonar-web/src/main/js/apps/quality-gates/gate-condition-view.js
new file mode 100644 (file)
index 0000000..4979771
--- /dev/null
@@ -0,0 +1,100 @@
+define([
+  './gate-conditions-delete-view',
+  './templates'
+], function (DeleteConditionView) {
+
+  return Marionette.ItemView.extend({
+    tagName: 'tr',
+    template: Templates['quality-gate-detail-condition'],
+
+    modelEvents: {
+      'change': 'render'
+    },
+
+    ui: {
+      periodSelect: '[name=period]',
+      operatorSelect: '[name=operator]',
+      warningInput: '[name=warning]',
+      errorInput: '[name=error]',
+      actionsBox: '.quality-gate-condition-actions',
+      updateButton: '.update-condition',
+      deleteButton: '.delete-condition'
+    },
+
+    events: {
+      'click @ui.updateButton': 'saveCondition',
+      'click @ui.deleteButton': 'deleteCondition',
+      'click .add-condition': 'saveCondition',
+      'click .cancel-add-condition': 'cancelAddCondition',
+      'keyup :input': 'enableUpdate',
+      'change :input': 'enableUpdate'
+    },
+
+    onRender: function () {
+      this.ui.warningInput.val(this.model.get('warning'));
+      this.ui.errorInput.val(this.model.get('error'));
+
+      this.ui.periodSelect.select2({
+        allowClear: false,
+        minimumResultsForSearch: 999
+      });
+
+      this.ui.operatorSelect.select2({
+        allowClear: false,
+        minimumResultsForSearch: 999
+      });
+
+      if (this.model.isNew()) {
+        this.ui.periodSelect.select2('open');
+      }
+    },
+
+    saveCondition: function () {
+      var attrs = {
+        gateId: this.model.isNew() ? this.options.gate.id : void 0,
+        period: this.ui.periodSelect.val(),
+        op: this.ui.operatorSelect.val(),
+        warning: this.ui.warningInput.val(),
+        error: this.ui.errorInput.val()
+      };
+      this.model.save(attrs, { wait: true });
+    },
+
+    deleteCondition: function () {
+      new DeleteConditionView({
+        model: this.model,
+        metric: this.getMetric()
+      }).render();
+    },
+
+    cancelAddCondition: function () {
+      this.close();
+    },
+
+    enableUpdate: function () {
+      this.ui.updateButton.prop('disabled', false);
+    },
+
+    getMetric: function () {
+      var key = this.model.get('metric');
+      return _.findWhere(this.options.metrics, { key: key });
+    },
+
+    isDiffMetric: function () {
+      var key = this.model.get('metric');
+      return key.indexOf('new_') === 0;
+    },
+
+    serializeData: function () {
+      var period = _.findWhere(this.options.periods, { key: this.model.get('period') });
+      return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
+        canEdit: this.options.canEdit,
+        periods: this.options.periods,
+        periodText: period ? period.text : t('value'),
+        metric: this.getMetric(),
+        isDiffMetric: this.isDiffMetric()
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-delete-view.js b/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-delete-view.js
new file mode 100644 (file)
index 0000000..83e32ee
--- /dev/null
@@ -0,0 +1,39 @@
+define([
+  'components/common/modal-form',
+  './templates'
+], function (ModalForm) {
+
+  return ModalForm.extend({
+    template: Templates['quality-gates-condition-delete'],
+
+    onFormSubmit: function () {
+      ModalForm.prototype.onFormSubmit.apply(this, arguments);
+      this.disableForm();
+      this.sendRequest();
+    },
+
+    sendRequest: function () {
+      var that = this,
+          options = {
+            statusCode: {
+              // do not show global error
+              400: null
+            }
+          };
+      return this.model.destroy(options)
+          .done(function () {
+            that.close();
+          }).fail(function (jqXHR) {
+            that.enableForm();
+            that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
+          });
+    },
+
+    serializeData: function () {
+      return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
+        metric: this.options.metric
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-empty-view.js b/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-empty-view.js
new file mode 100644 (file)
index 0000000..03390ac
--- /dev/null
@@ -0,0 +1,16 @@
+define([
+  './templates'
+], function () {
+
+  return Marionette.ItemView.extend({
+    tagName: 'tr',
+    template: Templates['quality-gate-detail-conditions-empty'],
+
+    serializeData: function () {
+      return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
+        canEdit: this.options.canEdit
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-view.js b/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-view.js
new file mode 100644 (file)
index 0000000..5c8353b
--- /dev/null
@@ -0,0 +1,75 @@
+define([
+  './condition',
+  './gate-condition-view',
+  './gate-conditions-empty-view',
+  './templates'
+], function (Condition, ConditionView, ConditionsEmptyView) {
+
+  return Marionette.CompositeView.extend({
+    template: Templates['quality-gate-detail-conditions'],
+    itemView: ConditionView,
+    emptyView: ConditionsEmptyView,
+    itemViewContainer: '.js-conditions',
+
+    ui: {
+      metricSelect: '#quality-gate-new-condition-metric'
+    },
+
+    events: {
+      'click .js-show-more': 'showMoreIntroduction',
+      'change @ui.metricSelect': 'addCondition'
+    },
+
+    itemViewOptions: function () {
+      return {
+        canEdit: this.options.canEdit,
+        gate: this.model,
+        collectionView: this,
+        metrics: this.options.metrics,
+        periods: this.options.periods
+      };
+    },
+
+    onRender: function () {
+      this.ui.metricSelect.select2({
+        allowClear: false,
+        width: '250px',
+        placeholder: t('alerts.select_metric')
+      });
+    },
+
+    showMoreIntroduction: function () {
+      this.$('.js-show-more').addClass('hidden');
+      this.$('.js-more').removeClass('hidden');
+    },
+
+    addCondition: function () {
+      var metric = this.ui.metricSelect.val();
+      this.ui.metricSelect.select2('val', '');
+      var condition = new Condition({ metric: metric });
+      this.collection.add(condition);
+    },
+
+    groupedMetrics: function () {
+      var metrics = this.options.metrics.filter(function (metric) {
+        return !metric.hidden;
+      });
+      metrics = _.groupBy(metrics, 'domain');
+      metrics = _.map(metrics, function (metrics, domain) {
+        return {
+          domain: domain,
+          metrics: _.sortBy(metrics, 'short_name')
+        };
+      });
+      return _.sortBy(metrics, 'domain');
+    },
+
+    serializeData: function () {
+      return _.extend(Marionette.CompositeView.prototype.serializeData.apply(this, arguments), {
+        canEdit: this.options.canEdit,
+        metricGroups: this.groupedMetrics()
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate-projects-view.js b/server/sonar-web/src/main/js/apps/quality-gates/gate-projects-view.js
new file mode 100644 (file)
index 0000000..c188c47
--- /dev/null
@@ -0,0 +1,48 @@
+define([
+  'components/common/select-list',
+  './templates'
+], function () {
+
+  return Marionette.ItemView.extend({
+    template: Templates['quality-gate-detail-projects'],
+
+    onRender: function () {
+      if (!this.model.isDefault()) {
+        new window.SelectList({
+          el: this.$('#select-list-projects'),
+          width: '100%',
+          readOnly: !this.options.canEdit,
+          focusSearch: false,
+          format: function (item) {
+            return item.name;
+          },
+          searchUrl: baseUrl + '/api/qualitygates/search?gateId=' + this.model.id,
+          selectUrl: baseUrl + '/api/qualitygates/select',
+          deselectUrl: baseUrl + '/api/qualitygates/deselect',
+          extra: {
+            gateId: this.model.id
+          },
+          selectParameter: 'projectId',
+          selectParameterValue: 'id',
+          labels: {
+            selected: t('quality_gates.projects.with'),
+            deselected: t('quality_gates.projects.without'),
+            all: t('quality_gates.projects.all'),
+            noResults: t('quality_gates.projects.noResults')
+          },
+          tooltips: {
+            select: t('quality_gates.projects.select_hint'),
+            deselect: t('quality_gates.projects.deselect_hint')
+          }
+        });
+      }
+    },
+
+    serializeData: function () {
+      return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
+        canEdit: this.options.canEdit
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate-view.js b/server/sonar-web/src/main/js/apps/quality-gates/gate-view.js
new file mode 100644 (file)
index 0000000..30036a3
--- /dev/null
@@ -0,0 +1,29 @@
+define([
+  './templates'
+], function () {
+
+  return Marionette.ItemView.extend({
+    tagName: 'a',
+    className: 'list-group-item',
+    template: Templates['quality-gates-gate'],
+
+    modelEvents: {
+      'change': 'render'
+    },
+
+    events: {
+      'click': 'onClick'
+    },
+
+    onRender: function () {
+      this.$el.toggleClass('active', this.options.highlighted);
+      this.$el.attr('data-id', this.model.id);
+    },
+
+    onClick: function (e) {
+      e.preventDefault();
+      this.model.trigger('select', this.model);
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate.js b/server/sonar-web/src/main/js/apps/quality-gates/gate.js
new file mode 100644 (file)
index 0000000..54b4e6c
--- /dev/null
@@ -0,0 +1,54 @@
+define(function () {
+
+  return Backbone.Model.extend({
+
+    isDefault: function () {
+      return this.get('isDefault');
+    },
+
+    url: function () {
+      return baseUrl + '/api/qualitygates';
+    },
+
+    showUrl: function () {
+      return this.url() + '/show';
+    },
+
+    deleteUrl: function () {
+      return this.url() + '/destroy';
+    },
+
+    toggleDefaultUrl: function () {
+      var method = this.isDefault() ? 'unset_default' : 'set_as_default';
+      return this.url() + '/' + method;
+    },
+
+    sync: function (method, model, options) {
+      var opts = options || {};
+      opts.data = opts.data || {};
+      opts.data.id = model.id;
+      if (method === 'read') {
+        opts.url = this.showUrl();
+      }
+      if (method === 'delete') {
+        opts.url = this.deleteUrl();
+        opts.type = 'POST';
+      }
+      return Backbone.ajax(opts);
+    },
+
+    toggleDefault: function () {
+      var that = this;
+      var opts = {
+        type: 'POST',
+        url: this.toggleDefaultUrl(),
+        data: { id: this.id }
+      };
+      return Backbone.ajax(opts).done(function () {
+        that.collection.toggleDefault(that);
+      });
+    }
+
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gates-view.js b/server/sonar-web/src/main/js/apps/quality-gates/gates-view.js
new file mode 100644 (file)
index 0000000..2fa1ceb
--- /dev/null
@@ -0,0 +1,25 @@
+define([
+  './gate-view',
+  './templates'
+], function (ItemView) {
+
+  return Marionette.CompositeView.extend({
+    className: 'list-group',
+    template: Templates['quality-gates-gates'],
+    itemView: ItemView,
+    itemViewContainer: '.js-list',
+
+    itemViewOptions: function (model) {
+      return {
+        collectionView: this,
+        highlighted: model.id === this.highlighted
+      };
+    },
+
+    highlight: function (id) {
+      this.highlighted = id;
+      this.render();
+    },
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gates.js b/server/sonar-web/src/main/js/apps/quality-gates/gates.js
new file mode 100644 (file)
index 0000000..d33b35b
--- /dev/null
@@ -0,0 +1,30 @@
+define([
+  './gate'
+], function (Gate) {
+
+  return Backbone.Collection.extend({
+    model: Gate,
+
+    url: function () {
+      return baseUrl + '/api/qualitygates/list';
+    },
+
+    parse: function (r) {
+      return r.qualitygates.map(function (gate) {
+        return _.extend(gate, { isDefault: gate.id === r.default });
+      });
+    },
+
+    comparator: function (item) {
+      return item.get('name').toLowerCase();
+    },
+
+    toggleDefault: function (gate) {
+      var isDefault = gate.isDefault();
+      this.forEach(function (model) {
+        model.set({ isDefault: gate.id === model.id ? !isDefault : false });
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/header-view.js b/server/sonar-web/src/main/js/apps/quality-gates/header-view.js
new file mode 100644 (file)
index 0000000..b86b2c9
--- /dev/null
@@ -0,0 +1,52 @@
+define([
+  './rename-view',
+  './copy-view',
+  './delete-view',
+  './templates'
+], function (RenameView, CopyView, DeleteView) {
+
+  return Marionette.ItemView.extend({
+    template: Templates['quality-gate-detail-header'],
+
+    modelEvents: {
+      'change': 'render'
+    },
+
+    events: {
+      'click #quality-gate-rename': 'renameQualityGate',
+      'click #quality-gate-copy': 'copyQualityGate',
+      'click #quality-gate-delete': 'deleteQualityGate',
+      'click #quality-gate-toggle-default': 'toggleDefault'
+    },
+
+    renameQualityGate: function () {
+      new RenameView({
+        model: this.model
+      }).render();
+    },
+
+    copyQualityGate: function () {
+      new CopyView({
+        model: this.model,
+        collection: this.model.collection
+      }).render();
+    },
+
+    deleteQualityGate: function () {
+      new DeleteView({
+        model: this.model
+      }).render();
+    },
+
+    toggleDefault: function () {
+      this.model.toggleDefault();
+    },
+
+    serializeData: function () {
+      return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
+        canEdit: this.options.canEdit
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/layout.js b/server/sonar-web/src/main/js/apps/quality-gates/layout.js
new file mode 100644 (file)
index 0000000..8d695fd
--- /dev/null
@@ -0,0 +1,24 @@
+define([
+  './templates'
+], function () {
+
+  var $ = jQuery;
+
+  return Marionette.Layout.extend({
+    template: Templates['quality-gates-layout'],
+
+    regions: {
+      headerRegion: '.search-navigator-workspace-header',
+      actionsRegion: '.search-navigator-filters',
+      resultsRegion: '.quality-gates-results',
+      detailsRegion: '.search-navigator-workspace-details'
+    },
+
+    onRender: function () {
+      var top = $('.search-navigator').offset().top;
+      this.$('.search-navigator-workspace-header').css({ top: top });
+      this.$('.search-navigator-side').css({ top: top }).isolatedScroll();
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/rename-view.js b/server/sonar-web/src/main/js/apps/quality-gates/rename-view.js
new file mode 100644 (file)
index 0000000..d9b5946
--- /dev/null
@@ -0,0 +1,23 @@
+define([
+  './form-view'
+], function (FormView) {
+
+  return FormView.extend({
+    method: 'rename',
+
+    prepareRequest: function () {
+      var that = this;
+      var url = baseUrl + '/api/qualitygates/rename',
+          name = this.$('#quality-gate-form-name').val(),
+          options = {
+            url: url,
+            data: { id: this.model.id, name: name }
+          };
+      return this.sendRequest(options)
+          .done(function (r) {
+            that.model.set(r);
+          });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/router.js b/server/sonar-web/src/main/js/apps/quality-gates/router.js
new file mode 100644 (file)
index 0000000..582c473
--- /dev/null
@@ -0,0 +1,22 @@
+define(function () {
+
+  return Backbone.Router.extend({
+    routes: {
+      '': 'index',
+      'show/:id': 'show'
+    },
+
+    initialize: function (options) {
+      this.app = options.app;
+    },
+
+    index: function () {
+      this.app.controller.index();
+    },
+
+    show: function (id) {
+      this.app.controller.show(id);
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/_quality-gate-intro.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/_quality-gate-intro.hbs
new file mode 100644 (file)
index 0000000..6f194fb
--- /dev/null
@@ -0,0 +1,7 @@
+<div class="">
+  <p class="spacer-bottom">Quality Gates are collections of simple boolean thresholds set on project measures. A project
+    must pass each of the thresholds in order to pass the Quality Gate as a whole.</p>
+
+  <p>It is possible to set a default Quality Gate, which will be applied to all projects not explicitly assigned to some
+    other gate.</p>
+</div>
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-actions.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-actions.hbs
new file mode 100644 (file)
index 0000000..6826aa9
--- /dev/null
@@ -0,0 +1,8 @@
+<h1 class="page-title">{{t 'quality_gates.page'}}</h1>
+{{#if canEdit}}
+  <div class="page-actions">
+    <div class="button-group">
+      <button id="quality-gate-add">{{t 'create'}}</button>
+    </div>
+  </div>
+{{/if}}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-condition.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-condition.hbs
new file mode 100644 (file)
index 0000000..72a5088
--- /dev/null
@@ -0,0 +1,69 @@
+<td class="text-middle nowrap">
+  {{metric.name}}
+  {{#if metric.hidden}}
+    <span class="text-danger little-spacer-left">{{t 'deprecated'}}</span>
+  {{/if}}
+</td>
+
+<td class="thin text-middle nowrap">
+  {{#if canEdit}}
+    <select class="input-medium" name="period">
+      {{#unless isDiffMetric}}
+        <option value="0" {{#eq period 0}}selected{{/eq}}>{{t 'value'}}</option>
+      {{/unless}}
+      {{#each periods}}
+        <option value="{{key}}" {{#eq key ../period}}selected{{/eq}}>&Delta; {{text}}</option>
+      {{/each}}
+    </select>
+  {{else}}
+    &Delta; {{periodText}}
+  {{/if}}
+</td>
+
+<td class="thin text-middle nowrap">
+  {{#if canEdit}}
+    <select class="input-small" name="operator">
+      {{#operators}}
+        <option value="{{this}}" {{#eq this ../op}}selected{{/eq}}>{{t 'quality_gates.operator' this}}</option>
+      {{/operators}}
+    </select>
+  {{else}}
+    {{t 'quality_gates.operator' op}}
+  {{/if}}
+</td>
+
+<td class="thin text-middle nowrap">
+  <i class="icon-alert-warn" title="{{t 'quality_gates.warning_tooltip'}}"></i>
+  {{#if canEdit}}
+    <input name="warning" type="text" class="input-tiny text-middle" data-type="{{metric.type}}"
+           placeholder="{{metric.placeholder}}">
+  {{else}}
+    {{formatMeasure warning metric.type}}
+  {{/if}}
+</td>
+
+<td class="thin text-middle nowrap">
+  <i class="icon-alert-error" title="{{t 'quality_gates.error_tooltip'}}"></i>
+  {{#if canEdit}}
+    <input name="error" type="text" class="input-tiny text-middle" data-type="{{metric.type}}"
+           placeholder="{{metric.placeholder}}">
+  {{else}}
+    {{formatMeasure error metric.type}}
+  {{/if}}
+</td>
+
+{{#if canEdit}}
+  <td class="thin text-middle nowrap">
+    {{#if id}}
+      <div class="button-group">
+        <button class="update-condition" disabled>{{t 'update_verb'}}</button>
+        <button class="button-red delete-condition">{{t 'delete'}}</button>
+      </div>
+    {{else}}
+      <div class="button-group">
+        <button class="add-condition">{{t 'add_verb'}}</button>
+        <a class="action cancel-add-condition">{{t 'cancel'}}</a>
+      </div>
+    {{/if}}
+  </td>
+{{/if}}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-conditions-empty.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-conditions-empty.hbs
new file mode 100644 (file)
index 0000000..80a12b4
--- /dev/null
@@ -0,0 +1,3 @@
+<td colspan="{{#if canEdit}}6{{else}}5{{/if}}">
+  {{t 'quality_gates.no_conditions'}}
+</td>
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-conditions.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-conditions.hbs
new file mode 100644 (file)
index 0000000..350aa94
--- /dev/null
@@ -0,0 +1,39 @@
+<h3 class="spacer-bottom">{{t 'quality_gates.conditions'}}</h3>
+
+<div class="alert alert-info">
+  <div>
+    {{t 'quality_gates.introduction'}}
+    <a class="js-show-more pull-right spacer-left">{{t 'more'}}&nbsp;<i class="icon-dropdown"></i></a>
+  </div>
+  <div class="js-more spacer-top hidden">
+    {{t 'quality_gates.health_icons'}}
+    <ul>
+      <li class="little-spacer-top">
+        <i class="icon-alert-ok"></i> {{t 'alerts.notes.ok'}}
+      </li>
+      <li class="little-spacer-top">
+        <i class="icon-alert-warn"></i> {{t 'alerts.notes.warn'}}
+      </li>
+      <li class="little-spacer-top">
+        <i class="icon-alert-error"></i> {{t 'alerts.notes.error'}}
+      </li>
+    </ul>
+  </div>
+</div>
+
+{{#if canEdit}}
+  <form class="big-spacer-top spacer-bottom">
+    <label for="quality-gate-new-condition-metric" class="text-middle">{{t 'quality_gates.add_condition'}}</label>
+    <select id="quality-gate-new-condition-metric" class="text-middle">
+      <option></option>
+      {{#each metricGroups}}
+        <optgroup label="{{domain}}">
+          {{#each metrics}}
+            <option value="{{key}}">{{name}}</option>{{/each}}
+        </optgroup>
+      {{/each}}
+    </select>
+  </form>
+{{/if}}
+
+<table class="js-conditions data zebra width-100"></table>
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-header.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-header.hbs
new file mode 100644 (file)
index 0000000..c7fcf9a
--- /dev/null
@@ -0,0 +1,14 @@
+<h2 class="search-navigator-header-component">{{name}}</h2>
+
+{{#if canEdit}}
+  <div class="search-navigator-header-actions">
+    <div class="button-group">
+      <button id="quality-gate-rename">{{t 'rename'}}</button>
+      <button id="quality-gate-copy">{{t 'copy'}}</button>
+      <button id="quality-gate-toggle-default">
+        {{#if isDefault}}{{t 'unset_as_default'}}{{else}}{{t 'set_as_default'}}{{/if}}
+      </button>
+      <button id="quality-gate-delete" class="button-red">{{t 'delete'}}</button>
+    </div>
+  </div>
+{{/if}}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-projects.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-projects.hbs
new file mode 100644 (file)
index 0000000..1622542
--- /dev/null
@@ -0,0 +1,13 @@
+<div class="quality-gate-section-name">{{t 'quality_gates.projects'}}</div>
+
+{{#if isDefault}}
+  <p class="quality-gate-default-message">
+  {{#if canEdit}}
+    {{t 'quality_gates.projects_for_default.edit'}}
+  {{else}}
+    {{t 'quality_gates.projects_for_default'}}
+  {{/if}}
+  </p>
+{{else}}
+  <div id="select-list-projects"></div>
+{{/if}}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail.hbs
new file mode 100644 (file)
index 0000000..0c5467b
--- /dev/null
@@ -0,0 +1,2 @@
+<div id="quality-gate-conditions" class="quality-gate-section"></div>
+<div id="quality-gate-projects" class="quality-gate-section"></div>
\ No newline at end of file
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-form.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-form.hbs
new file mode 100644 (file)
index 0000000..bc6e623
--- /dev/null
@@ -0,0 +1,20 @@
+<form id="quality-gate-form">
+  <div class="modal-head">
+    <h2>{{t 'quality_gates' method }}</h2>
+  </div>
+  <div class="modal-body">
+    <div class="js-modal-messages"></div>
+    <div class="modal-field">
+      <label for="quality-gate-form-name">{{t 'name'}} <em class="mandatory">*</em></label>
+      <input id="quality-gate-form-name" type="text" size="50" maxlength="100" value="{{name}}" required>
+    </div>
+  </div>
+  <div class="modal-foot">
+    <button id="quality-gate-form-submit">
+      {{#eq method 'rename'}}{{t 'save'}}{{/eq}}
+      {{#eq method 'copy'}}{{t 'copy'}}{{/eq}}
+      {{#eq method 'create'}}{{t 'create'}}{{/eq}}
+    </button>
+    <a id="quality-gate-form-cancel" href="#" class="js-modal-close">{{t 'cancel'}}</a>
+  </div>
+</form>
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-condition-delete.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-condition-delete.hbs
new file mode 100644 (file)
index 0000000..221d038
--- /dev/null
@@ -0,0 +1,13 @@
+<form id="delete-condition-form">
+  <div class="modal-head">
+    <h2>{{t 'quality_gates.delete_condition'}}</h2>
+  </div>
+  <div class="modal-body">
+    <div class="js-modal-messages"></div>
+    {{tp 'quality_gates.delete_condition.confirm.message' metric.name}}
+  </div>
+  <div class="modal-foot">
+    <button id="delete-condition-submit">{{t 'delete'}}</button>
+    <a href="#" class="js-modal-close" id="delete-condition-cancel">{{t 'cancel'}}</a>
+  </div>
+</form>
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-delete.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-delete.hbs
new file mode 100644 (file)
index 0000000..c146c58
--- /dev/null
@@ -0,0 +1,17 @@
+<form id="delete-gate-form">
+  <div class="modal-head">
+    <h2>{{t 'quality_gates.delete'}}</h2>
+  </div>
+  <div class="modal-body">
+    <div class="js-modal-messages"></div>
+    {{#if isDefault}}
+      {{tp 'quality_gates.delete.confirm.default' name}}
+    {{else}}
+      {{tp 'quality_gates.delete.confirm.message' name}}
+    {{/if}}
+  </div>
+  <div class="modal-foot">
+    <button id="delete-gate-submit">{{t 'delete'}}</button>
+    <a href="#" class="js-modal-close" id="delete-gate-cancel">{{t 'cancel'}}</a>
+  </div>
+</form>
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-gate.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-gate.hbs
new file mode 100644 (file)
index 0000000..d39ea68
--- /dev/null
@@ -0,0 +1,13 @@
+<table>
+  <tr>
+    <td class="text-top">{{name}}</td>
+    <td class="text-top thin nowrap spacer-left">
+      {{#if isDefault}}
+        <span class="badge pull-right">{{t 'default'}}</span>
+      {{/if}}
+    </td>
+  </tr>
+</table>
+
+
+
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-gates.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-gates.hbs
new file mode 100644 (file)
index 0000000..8022059
--- /dev/null
@@ -0,0 +1 @@
+<div class="js-list"></div>
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-layout.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-layout.hbs
new file mode 100644 (file)
index 0000000..3428ac4
--- /dev/null
@@ -0,0 +1,9 @@
+<div class="search-navigator-side search-navigator-side-light">
+  <div class="search-navigator-filters"></div>
+  <div class="quality-gates-results panel"></div>
+</div>
+
+<div class="search-navigator-workspace">
+  <div class="search-navigator-workspace-header">&nbsp;</div>
+  <div class="search-navigator-workspace-details">{{> '_quality-gate-intro'}}</div>
+</div>
index a92f2d1902621e2b09a1d0f13144c158f210ed41..cec8c0c725b0aa0d3c66a329cd40a7db7b8de4da 100644 (file)
     }, '');
   });
 
-  Handlebars.registerHelper('operators', function (metricType, options) {
+  Handlebars.registerHelper('operators', function (options) {
     var ops = ['LT', 'GT', 'EQ', 'NE'];
 
     return ops.reduce(function (prev, current) {
index 4403489ccf6c55e4e95c0f42904a7c58d60d7c4c..656491eb0b6f2a4d9ac122ea32d01c73e3bf6cca 100644 (file)
@@ -178,9 +178,10 @@ input[type=button] {
   }
 }
 
-.input-small {
-  width: 80px;
-}
+.input-tiny   { width: 60px; }
+.input-small  { width: 100px; }
+.input-medium { width: 150px; }
+.input-large  { width: 200px; }
 
 em.mandatory {
   color: #990000;
index e94718f0f4f72b4c5e79412962c6108c15fa6a9f..ebe75c89e221c9c9c3d17eef9f1ea9831893ae90 100644 (file)
@@ -178,21 +178,25 @@ a[class^="icon-"], a[class*=" icon-"] {
  * Alert
  */
 
+.icon-alert-ok { font-size: @iconFontSize; }
 .icon-alert-ok:before {
   content: "\f013";
   color: @green;
   font-size: @iconFontSize;
 }
+.icon-alert-warn { font-size: @iconFontSize; }
 .icon-alert-warn:before {
   content: "\f000";
   color: @orange;
   font-size: @iconFontSize;
 }
+.icon-alert-error { font-size: @iconFontSize; }
 .icon-alert-error:before {
   content: "\f057";
   color: @red;
   font-size: @iconFontSize;
 }
+.icon-alert-none { font-size: @iconFontSize; }
 .icon-alert-none:before {
   content: "\f056";
   color: @middleGrey;
index 3cc8526e54d2723d1488ba1e8a06e4552b6ceef0..e369019dc77770b6a9699aa0899af8221c3dd00c 100644 (file)
@@ -79,6 +79,7 @@ td.spacer-top    { padding-top: 8px; }
 .width-25 { width: 25%; }
 .width-20 { width: 20%; }
 .width-15 { width: 15%; }
+.width-10 { width: 10%; }
 
 .abs-width-400 { width: 400px; }
 
index aca9725e9657e74732196926b9fb022a8ca98703..991005bde79f6c4d1ce62f7c7653f47a882a7545 100644 (file)
@@ -91,7 +91,7 @@ table.data.zebra tbody tr:nth-child(odd) {
 }
 
 .data input, .data select, .data button {
-  vertical-align: baseline;
+  vertical-align: middle;
 }
 
 .data td.category {
index 588f7c3ba25eb5e81546be2cab518a32d24ab7b7..749c6aeba6a389618f54541e58cad8b9e0bf12d6 100644 (file)
@@ -1,5 +1,6 @@
-<% content_for :script do %>
-  <script>require(['apps/quality-gate/app']);</script>
-<% end %>
-
-<div class="search-navigator" id="quality-gates"></div>
+<div class="search-navigator sticky search-navigator-extended-view" id="quality-gates"></div>
+<script>
+  require(['apps/quality-gates/app'], function (App) {
+    App.start({ el: '#quality-gates' });
+  });
+</script>
index 6c1d34cd6f5e886f3112c047a061be2b6e0ffba0..207e8a2cbec935170f380faf0d3ef7e272e4921a 100644 (file)
@@ -38,21 +38,23 @@ casper.test.begin(testName('Should Show List'), 5, function (test) {
 
       .then(function () {
         casper.evaluate(function () {
-          require(['apps/quality-gate/app']);
+          require(['apps/quality-gates/app'], function (App) {
+            App.start({ el: '#quality-gates' });
+          });
         });
       })
 
       .then(function () {
-        casper.waitForSelector('.quality-gates-results .list-group-item');
+        casper.waitForSelector('.js-list .list-group-item');
       })
 
       .then(function () {
-        test.assertElementCount('.quality-gates-results .list-group-item', 3);
-        test.assertSelectorContains('.quality-gates-results .list-group-item', 'SonarQube way');
-        test.assertSelectorContains('.quality-gates-results .list-group-item', 'Simple Gate');
-        test.assertSelectorContains('.quality-gates-results .list-group-item', 'Another Gate');
+        test.assertElementCount('.js-list .list-group-item', 3);
+        test.assertSelectorContains('.js-list .list-group-item', 'SonarQube way');
+        test.assertSelectorContains('.js-list .list-group-item', 'Simple Gate');
+        test.assertSelectorContains('.js-list .list-group-item', 'Another Gate');
 
-        test.assertElementCount('.quality-gates-results .badge', 1);
+        test.assertElementCount('.js-list .badge', 1);
       })
 
       .then(function () {
@@ -65,35 +67,36 @@ casper.test.begin(testName('Should Show List'), 5, function (test) {
 });
 
 
-casper.test.begin(testName('Should Show Details', 'Anonymous'), 12, function (test) {
+casper.test.begin(testName('Should Show Details', 'Anonymous'), 14, function (test) {
   casper
       .start(lib.buildUrl('quality_gates'), function () {
         lib.setDefaultViewport();
 
         lib.mockRequestFromFile('/api/qualitygates/app', 'app-anonymous.json');
         lib.mockRequestFromFile('/api/qualitygates/list', 'list.json');
-        lib.mockRequestFromFile('/api/qualitygates/show?id=1', 'show.json');
+        lib.mockRequestFromFile('/api/qualitygates/show', 'show.json', { data: { id: '1' } });
       })
 
       .then(function () {
         casper.evaluate(function () {
-          require(['apps/quality-gate/app']);
+          require(['apps/quality-gates/app'], function (App) {
+            App.start({ el: '#quality-gates' });
+          });
         });
       })
 
       .then(function () {
-        casper.waitForSelector('.quality-gates-results .list-group-item');
+        casper.waitForSelector('.js-list .list-group-item');
       })
 
       .then(function () {
-        // FIXME use better selector
-        casper.click('.quality-gates-results .list-group-item:last-child');
+        casper.click('.js-list .list-group-item[data-id="1"]');
         casper.waitForSelector('.search-navigator-header-component');
       })
 
       .then(function () {
-        test.assertElementCount('.quality-gates-results .list-group-item.active', 1);
-        test.assertSelectorContains('.quality-gates-results .list-group-item.active', 'SonarQube way');
+        test.assertElementCount('.js-list .list-group-item.active', 1);
+        test.assertSelectorContains('.js-list .list-group-item.active', 'SonarQube way');
 
         test.assertSelectorContains('.search-navigator-workspace-header', 'SonarQube way');
         test.assertDoesntExist('#quality-gate-rename');
@@ -101,14 +104,20 @@ casper.test.begin(testName('Should Show Details', 'Anonymous'), 12, function (te
         test.assertDoesntExist('#quality-gate-unset-as-default');
         test.assertDoesntExist('#quality-gate-delete');
 
-        test.assertExists('.quality-gate-conditions');
-        test.assertElementCount('.quality-gate-conditions tbody tr', 8);
-        test.assertDoesntExist('.quality-gate-conditions .update-condition');
-        test.assertDoesntExist('.quality-gate-conditions .delete-condition');
+        test.assertExists('.js-conditions');
+        test.assertElementCount('.js-conditions tbody tr', 8);
+        test.assertDoesntExist('.js-conditions .update-condition');
+        test.assertDoesntExist('.js-conditions .delete-condition');
 
         test.assertExists('.quality-gate-default-message');
       })
 
+      .then(function () {
+        test.assertNotVisible('.js-more');
+        casper.click('.js-show-more');
+        test.assertVisible('.js-more');
+      })
+
       .then(function () {
         lib.sendCoverage();
       })
@@ -126,39 +135,40 @@ casper.test.begin(testName('Should Show Details', 'Admin'), 12, function (test)
 
         lib.mockRequestFromFile('/api/qualitygates/app', 'app.json');
         lib.mockRequestFromFile('/api/qualitygates/list', 'list.json');
-        lib.mockRequestFromFile('/api/qualitygates/show?id=1', 'show.json');
+        lib.mockRequestFromFile('/api/qualitygates/show', 'show.json', { data: { id: '1' } });
       })
 
       .then(function () {
         casper.evaluate(function () {
-          require(['apps/quality-gate/app']);
+          require(['apps/quality-gates/app'], function (App) {
+            App.start({ el: '#quality-gates' });
+          });
         });
       })
 
       .then(function () {
-        casper.waitForSelector('.quality-gates-results .list-group-item');
+        casper.waitForSelector('.js-list .list-group-item');
       })
 
       .then(function () {
-        // FIXME use better selector
-        casper.click('.quality-gates-results .list-group-item:last-child');
+        casper.click('.js-list .list-group-item[data-id="1"]');
         casper.waitForSelector('.search-navigator-header-component');
       })
 
       .then(function () {
-        test.assertElementCount('.quality-gates-results .list-group-item.active', 1);
-        test.assertSelectorContains('.quality-gates-results .list-group-item.active', 'SonarQube way');
+        test.assertElementCount('.js-list .list-group-item.active', 1);
+        test.assertSelectorContains('.js-list .list-group-item.active', 'SonarQube way');
 
         test.assertSelectorContains('.search-navigator-workspace-header', 'SonarQube way');
         test.assertExists('#quality-gate-rename');
         test.assertExists('#quality-gate-copy');
-        test.assertExists('#quality-gate-unset-as-default');
+        test.assertExists('#quality-gate-toggle-default');
         test.assertExists('#quality-gate-delete');
 
-        test.assertExists('.quality-gate-conditions');
-        test.assertElementCount('.quality-gate-conditions tbody tr', 8);
-        test.assertElementCount('.quality-gate-conditions .update-condition', 8);
-        test.assertElementCount('.quality-gate-conditions .delete-condition', 8);
+        test.assertExists('.js-conditions');
+        test.assertElementCount('.js-conditions tbody tr', 8);
+        test.assertElementCount('.js-conditions .update-condition', 8);
+        test.assertElementCount('.js-conditions .delete-condition', 8);
 
         test.assertExists('.quality-gate-default-message');
       })
@@ -173,6 +183,44 @@ casper.test.begin(testName('Should Show Details', 'Admin'), 12, function (test)
 });
 
 
+casper.test.begin(testName('Should Show Projects'), 2, function (test) {
+  casper
+      .start(lib.buildUrl('quality_gates#show/5'), function () {
+        lib.setDefaultViewport();
+
+        lib.mockRequestFromFile('/api/qualitygates/app', 'app-anonymous.json');
+        lib.mockRequestFromFile('/api/qualitygates/list', 'list.json');
+        lib.mockRequestFromFile('/api/qualitygates/show', 'show-another.json', { data: { id: '5' } });
+        lib.mockRequestFromFile('/api/qualitygates/search?gateId=5', 'projects.json');
+      })
+
+      .then(function () {
+        casper.evaluate(function () {
+          require(['apps/quality-gates/app'], function (App) {
+            App.start({ el: '#quality-gates' });
+          });
+        });
+      })
+
+      .then(function () {
+        casper.waitForSelector('.select-list-list li');
+      })
+
+      .then(function () {
+        test.assertElementCount('.select-list-list li', 1);
+        test.assertSelectorContains('.select-list-list li', 'SonarQube');
+      })
+
+      .then(function () {
+        lib.sendCoverage();
+      })
+
+      .run(function () {
+        test.done();
+      });
+});
+
+
 casper.test.begin(testName('Should Rename'), 2, function (test) {
   casper
       .start(lib.buildUrl('quality_gates#show/1'), function () {
@@ -180,13 +228,15 @@ casper.test.begin(testName('Should Rename'), 2, function (test) {
 
         lib.mockRequestFromFile('/api/qualitygates/app', 'app.json');
         lib.mockRequestFromFile('/api/qualitygates/list', 'list.json');
-        lib.mockRequestFromFile('/api/qualitygates/show?id=1', 'show.json');
+        lib.mockRequestFromFile('/api/qualitygates/show', 'show.json', { data: { id: '1' } });
         lib.mockRequestFromFile('/api/qualitygates/rename', 'rename.json', { data: { id: '1', name: 'New Name' } });
       })
 
       .then(function () {
         casper.evaluate(function () {
-          require(['apps/quality-gate/app']);
+          require(['apps/quality-gates/app'], function (App) {
+            App.start({ el: '#quality-gates' });
+          });
           jQuery.ajaxSetup({ dataType: 'json' });
         });
       })
@@ -197,12 +247,12 @@ casper.test.begin(testName('Should Rename'), 2, function (test) {
 
       .then(function () {
         casper.click('#quality-gate-rename');
-        casper.waitUntilVisible('#quality-gate-edit-name');
+        casper.waitUntilVisible('#quality-gate-form-name');
       })
 
       .then(function () {
         casper.evaluate(function () {
-          jQuery('#quality-gate-edit-name').val('New Name');
+          jQuery('#quality-gate-form-name').val('New Name');
         });
         casper.click('.modal-foot button');
         casper.waitForSelectorTextChange('.search-navigator-header-component');
@@ -210,7 +260,7 @@ casper.test.begin(testName('Should Rename'), 2, function (test) {
 
       .then(function () {
         test.assertSelectorContains('.search-navigator-header-component', 'New Name');
-        test.assertSelectorContains('.quality-gates-results .list-group-item.active', 'New Name');
+        test.assertSelectorContains('.js-list .list-group-item.active', 'New Name');
       })
 
       .then(function () {
@@ -230,13 +280,16 @@ casper.test.begin(testName('Should Copy'), 3, function (test) {
 
         lib.mockRequestFromFile('/api/qualitygates/app', 'app.json');
         lib.mockRequestFromFile('/api/qualitygates/list', 'list.json');
-        lib.mockRequestFromFile('/api/qualitygates/show?id=1', 'show.json');
+        lib.mockRequestFromFile('/api/qualitygates/show', 'show.json', { data: { id: '1' } });
+        lib.mockRequestFromFile('/api/qualitygates/show', 'show-created.json', { data: { id: '6' } });
         lib.mockRequestFromFile('/api/qualitygates/copy', 'copy.json', { data: { id: '1', name: 'New Name' } });
       })
 
       .then(function () {
         casper.evaluate(function () {
-          require(['apps/quality-gate/app']);
+          require(['apps/quality-gates/app'], function (App) {
+            App.start({ el: '#quality-gates' });
+          });
           jQuery.ajaxSetup({ dataType: 'json' });
         });
       })
@@ -247,12 +300,12 @@ casper.test.begin(testName('Should Copy'), 3, function (test) {
 
       .then(function () {
         casper.click('#quality-gate-copy');
-        casper.waitUntilVisible('#quality-gate-edit-name');
+        casper.waitUntilVisible('#quality-gate-form-name');
       })
 
       .then(function () {
         casper.evaluate(function () {
-          jQuery('#quality-gate-edit-name').val('New Name');
+          jQuery('#quality-gate-form-name').val('New Name');
         });
         casper.click('.modal-foot button');
         casper.waitForSelectorTextChange('.search-navigator-header-component');
@@ -260,8 +313,8 @@ casper.test.begin(testName('Should Copy'), 3, function (test) {
 
       .then(function () {
         test.assertSelectorContains('.search-navigator-header-component', 'New Name');
-        test.assertSelectorContains('.quality-gates-results .list-group-item.active', 'New Name');
-        test.assertSelectorContains('.quality-gates-results .list-group-item', 'SonarQube way');
+        test.assertSelectorContains('.js-list .list-group-item.active', 'New Name');
+        test.assertSelectorContains('.js-list .list-group-item', 'SonarQube way');
       })
 
       .then(function () {
@@ -281,13 +334,15 @@ casper.test.begin(testName('Should Set As Default'), 4, function (test) {
 
         lib.mockRequestFromFile('/api/qualitygates/app', 'app.json');
         lib.mockRequestFromFile('/api/qualitygates/list', 'list.json');
-        lib.mockRequestFromFile('/api/qualitygates/show?id=5', 'show-another.json');
+        lib.mockRequestFromFile('/api/qualitygates/show', 'show-another.json', { data: { id: '5' } });
         lib.mockRequest('/api/qualitygates/set_as_default', '{}', { data: { id: '5' } });
       })
 
       .then(function () {
         casper.evaluate(function () {
-          require(['apps/quality-gate/app']);
+          require(['apps/quality-gates/app'], function (App) {
+            App.start({ el: '#quality-gates' });
+          });
           jQuery.ajaxSetup({ dataType: 'json' });
         });
       })
@@ -297,15 +352,15 @@ casper.test.begin(testName('Should Set As Default'), 4, function (test) {
       })
 
       .then(function () {
-        test.assertDoesntExist('.quality-gates-results .list-group-item.active .badge');
+        test.assertDoesntExist('.js-list .list-group-item.active .badge');
         test.assertDoesntExist('.quality-gate-default-message');
-        casper.click('#quality-gate-set-as-default');
-        casper.waitForSelector('.quality-gates-results .list-group-item.active .badge');
+        casper.click('#quality-gate-toggle-default');
+        casper.waitForSelector('.js-list .list-group-item.active .badge');
       })
 
       .then(function () {
         test.assertExists('.quality-gate-default-message');
-        test.assertElementCount('.quality-gates-results .badge', 1);
+        test.assertElementCount('.js-list .badge', 1);
       })
 
       .then(function () {
@@ -325,13 +380,15 @@ casper.test.begin(testName('Should Unset As Default'), 4, function (test) {
 
         lib.mockRequestFromFile('/api/qualitygates/app', 'app.json');
         lib.mockRequestFromFile('/api/qualitygates/list', 'list.json');
-        lib.mockRequestFromFile('/api/qualitygates/show?id=1', 'show.json');
-        lib.mockRequest('/api/qualitygates/unset_default', '{}');
+        lib.mockRequestFromFile('/api/qualitygates/show', 'show.json', { data: { id: '1' } });
+        lib.mockRequest('/api/qualitygates/unset_default', '{}', { data: { id: '1' } });
       })
 
       .then(function () {
         casper.evaluate(function () {
-          require(['apps/quality-gate/app']);
+          require(['apps/quality-gates/app'], function (App) {
+            App.start({ el: '#quality-gates' });
+          });
           jQuery.ajaxSetup({ dataType: 'json' });
         });
       })
@@ -341,15 +398,15 @@ casper.test.begin(testName('Should Unset As Default'), 4, function (test) {
       })
 
       .then(function () {
-        test.assertExists('.quality-gates-results .list-group-item.active .badge');
+        test.assertExists('.js-list .list-group-item.active .badge');
         test.assertExists('.quality-gate-default-message');
-        casper.click('#quality-gate-unset-as-default');
-        casper.waitWhileSelector('.quality-gates-results .list-group-item.active .badge');
+        casper.click('#quality-gate-toggle-default');
+        casper.waitWhileSelector('.js-list .list-group-item.active .badge');
       })
 
       .then(function () {
         test.assertDoesntExist('.quality-gate-default-message');
-        test.assertDoesntExist('.quality-gates-results .badge');
+        test.assertDoesntExist('.js-list .badge');
       })
 
       .then(function () {
@@ -369,29 +426,41 @@ casper.test.begin(testName('Should Create'), 2, function (test) {
 
         lib.mockRequestFromFile('/api/qualitygates/app', 'app.json');
         lib.mockRequestFromFile('/api/qualitygates/list', 'list.json');
+        lib.mockRequest('/api/qualitygates/create', '{"errors":[{"msg": "error"}]}',
+            { status: 400, data: { name: 'Bad' } });
         lib.mockRequestFromFile('/api/qualitygates/create', 'create.json', { data: { name: 'New Name' } });
-        lib.mockRequestFromFile('/api/qualitygates/show?id=6', 'show-created.json');
+        lib.mockRequestFromFile('/api/qualitygates/show', 'show-created.json', { data: { id: '6' } });
       })
 
       .then(function () {
         casper.evaluate(function () {
-          require(['apps/quality-gate/app']);
+          require(['apps/quality-gates/app'], function (App) {
+            App.start({ el: '#quality-gates' });
+          });
           jQuery.ajaxSetup({ dataType: 'json' });
         });
       })
 
       .then(function () {
-        casper.waitForSelector('.quality-gates-results .list-group-item');
+        casper.waitForSelector('.js-list .list-group-item');
       })
 
       .then(function () {
         casper.click('#quality-gate-add');
-        casper.waitUntilVisible('#quality-gate-edit-name');
+        casper.waitUntilVisible('#quality-gate-form-name');
+      })
+
+      .then(function () {
+        casper.evaluate(function () {
+          jQuery('#quality-gate-form-name').val('Bad');
+        });
+        casper.click('.modal-foot button');
+        casper.waitForSelector('.alert-danger');
       })
 
       .then(function () {
         casper.evaluate(function () {
-          jQuery('#quality-gate-edit-name').val('New Name');
+          jQuery('#quality-gate-form-name').val('New Name');
         });
         casper.click('.modal-foot button');
         casper.waitForSelector('.search-navigator-header-component');
@@ -399,7 +468,7 @@ casper.test.begin(testName('Should Create'), 2, function (test) {
 
       .then(function () {
         test.assertSelectorContains('.search-navigator-header-component', 'New Name');
-        test.assertSelectorContains('.quality-gates-results .list-group-item.active', 'New Name');
+        test.assertSelectorContains('.js-list .list-group-item.active', 'New Name');
       })
 
       .then(function () {
@@ -419,13 +488,16 @@ casper.test.begin(testName('Should Delete'), 2, function (test) {
 
         lib.mockRequestFromFile('/api/qualitygates/app', 'app.json');
         lib.mockRequestFromFile('/api/qualitygates/list', 'list.json');
-        lib.mockRequestFromFile('/api/qualitygates/show?id=5', 'show-another.json');
-        lib.mockRequest('/api/qualitygates/destroy', '{}', { data: { id: '5' } });
+        lib.mockRequestFromFile('/api/qualitygates/show', 'show-another.json', { data: { id: '5' } });
+        this.deleteMock = lib.mockRequest('/api/qualitygates/destroy', '{"errors":[{"msg": "error"}]}',
+            { status: 400 });
       })
 
       .then(function () {
         casper.evaluate(function () {
-          require(['apps/quality-gate/app']);
+          require(['apps/quality-gates/app'], function (App) {
+            App.start({ el: '#quality-gates' });
+          });
           jQuery.ajaxSetup({ dataType: 'json' });
         });
       })
@@ -435,18 +507,26 @@ casper.test.begin(testName('Should Delete'), 2, function (test) {
       })
 
       .then(function () {
-        test.assertElementCount('.quality-gates-results .list-group-item', 3);
+        test.assertElementCount('.js-list .list-group-item', 3);
         casper.click('#quality-gate-delete');
-        casper.waitForSelector('button[data-confirm="yes"]');
+        casper.waitForSelector('#delete-gate-submit');
+      })
+
+      .then(function () {
+        casper.click('#delete-gate-submit');
+        casper.waitForSelector('.alert-danger');
       })
 
       .then(function () {
-        casper.click('button[data-confirm="yes"]');
+        lib.clearRequestMock(this.deleteMock);
+        lib.mockRequest('/api/qualitygates/destroy', '{}', { data: { id: '5' } });
+
+        casper.click('#delete-gate-submit');
         casper.waitWhileSelector('.search-navigator-header-component');
       })
 
       .then(function () {
-        test.assertElementCount('.quality-gates-results .list-group-item', 2);
+        test.assertElementCount('.js-list .list-group-item', 2);
       })
 
       .then(function () {
@@ -466,14 +546,16 @@ casper.test.begin(testName('Should Add Condition'), 6, function (test) {
 
         lib.mockRequestFromFile('/api/qualitygates/app', 'app.json');
         lib.mockRequestFromFile('/api/qualitygates/list', 'list.json');
-        lib.mockRequestFromFile('/api/qualitygates/show?id=5', 'show-another.json');
+        lib.mockRequestFromFile('/api/qualitygates/show', 'show-another.json', { data: { id: '5' } });
         lib.mockRequestFromFile('/api/qualitygates/create_condition', 'create-condition.json',
-            { data: { metric: 'complexity', op: 'GT', period: '1', warning: '3', error: '4' } });
+            { data: { gateId: '5', metric: 'complexity', op: 'GT', period: '1', warning: '3', error: '4' } });
       })
 
       .then(function () {
         casper.evaluate(function () {
-          require(['apps/quality-gate/app']);
+          require(['apps/quality-gates/app'], function (App) {
+            App.start({ el: '#quality-gates' });
+          });
           jQuery.ajaxSetup({ dataType: 'json' });
         });
       })
@@ -483,12 +565,12 @@ casper.test.begin(testName('Should Add Condition'), 6, function (test) {
       })
 
       .then(function () {
-        test.assertElementCount('.quality-gate-conditions [name="error"]', 0);
+        test.assertElementCount('.js-conditions [name="error"]', 0);
 
         casper.evaluate(function () {
           jQuery('#quality-gate-new-condition-metric').val('complexity').change();
         });
-        test.assertElementCount('.quality-gate-conditions [name="error"]', 1);
+        test.assertElementCount('.js-conditions [name="error"]', 1);
       })
 
       .then(function () {
@@ -497,12 +579,12 @@ casper.test.begin(testName('Should Add Condition'), 6, function (test) {
       })
 
       .then(function () {
-        test.assertElementCount('.quality-gate-conditions [name="error"]', 0);
+        test.assertElementCount('.js-conditions [name="error"]', 0);
 
         casper.evaluate(function () {
           jQuery('#quality-gate-new-condition-metric').val('complexity').change();
         });
-        test.assertElementCount('.quality-gate-conditions [name="error"]', 1);
+        test.assertElementCount('.js-conditions [name="error"]', 1);
 
         casper.evaluate(function () {
           jQuery('[name="period"]').val('1');
@@ -536,14 +618,16 @@ casper.test.begin(testName('Should Update Condition'), 3, function (test) {
 
         lib.mockRequestFromFile('/api/qualitygates/app', 'app.json');
         lib.mockRequestFromFile('/api/qualitygates/list', 'list.json');
-        lib.mockRequestFromFile('/api/qualitygates/show?id=1', 'show.json');
+        lib.mockRequestFromFile('/api/qualitygates/show', 'show.json', { data: { id: '1' } });
         lib.mockRequestFromFile('/api/qualitygates/update_condition', 'update-condition.json',
             { data: { id: '1', warning: '173' } });
       })
 
       .then(function () {
         casper.evaluate(function () {
-          require(['apps/quality-gate/app']);
+          require(['apps/quality-gates/app'], function (App) {
+            App.start({ el: '#quality-gates' });
+          });
           jQuery.ajaxSetup({ dataType: 'json' });
         });
       })
@@ -553,17 +637,17 @@ casper.test.begin(testName('Should Update Condition'), 3, function (test) {
       })
 
       .then(function () {
-        test.assertExists('.quality-gate-conditions tr:first-child .update-condition[disabled]');
+        test.assertExists('.js-conditions tr:first-child .update-condition[disabled]');
         casper.evaluate(function () {
-          jQuery('.quality-gate-conditions tr:first-child [name="warning"]').val('173').change();
+          jQuery('.js-conditions tr:first-child [name="warning"]').val('173').change();
         });
-        test.assertDoesntExist('.quality-gate-conditions tr:first-child .update-condition[disabled]');
-        casper.click('.quality-gate-conditions tr:first-child .update-condition');
-        casper.waitWhileSelector('.quality-gate-conditions tr:first-child .update-condition:not([disabled])');
+        test.assertDoesntExist('.js-conditions tr:first-child .update-condition[disabled]');
+        casper.click('.js-conditions tr:first-child .update-condition');
+        casper.waitWhileSelector('.js-conditions tr:first-child .update-condition:not([disabled])');
       })
 
       .then(function () {
-        test.assertExists('.quality-gate-conditions tr:first-child .update-condition[disabled]');
+        test.assertExists('.js-conditions tr:first-child .update-condition[disabled]');
       })
 
       .then(function () {
@@ -583,13 +667,16 @@ casper.test.begin(testName('Should Delete Condition'), 2, function (test) {
 
         lib.mockRequestFromFile('/api/qualitygates/app', 'app.json');
         lib.mockRequestFromFile('/api/qualitygates/list', 'list.json');
-        lib.mockRequestFromFile('/api/qualitygates/show?id=1', 'show.json');
-        lib.mockRequest('/api/qualitygates/delete_condition', '{}', { data: { id: '1' } });
+        lib.mockRequestFromFile('/api/qualitygates/show', 'show.json', { data: { id: '1' } });
+        this.deleteMock = lib.mockRequest('/api/qualitygates/delete_condition', '{"errors":[{"msg": "error"}]}',
+            { status: 400 });
       })
 
       .then(function () {
         casper.evaluate(function () {
-          require(['apps/quality-gate/app']);
+          require(['apps/quality-gates/app'], function (App) {
+            App.start({ el: '#quality-gates' });
+          });
           jQuery.ajaxSetup({ dataType: 'json' });
         });
       })
@@ -601,12 +688,19 @@ casper.test.begin(testName('Should Delete Condition'), 2, function (test) {
       .then(function () {
         test.assertElementCount('.delete-condition', 8);
 
-        casper.click('.quality-gate-conditions tr:first-child .delete-condition');
-        casper.waitForSelector('button[data-confirm="yes"]');
+        casper.click('.js-conditions tr:first-child .delete-condition');
+        casper.waitForSelector('#delete-condition-submit');
+      })
+
+      .then(function () {
+        casper.click('#delete-condition-submit');
+        casper.waitForSelector('.alert-danger');
       })
 
       .then(function () {
-        casper.click('button[data-confirm="yes"]');
+        lib.clearRequestMock(this.deleteMock);
+        lib.mockRequest('/api/qualitygates/delete_condition', '{}', { data: { id: '1' } });
+        casper.click('#delete-condition-submit');
         lib.waitForElementCount('.delete-condition', 7);
       })
 
diff --git a/server/sonar-web/src/test/json/quality-gates-spec/projects.json b/server/sonar-web/src/test/json/quality-gates-spec/projects.json
new file mode 100644 (file)
index 0000000..1d75a5e
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "more": false,
+  "results": [
+    {
+      "id": 191,
+      "name": "SonarQube",
+      "selected": true
+    }
+  ]
+}
index c7cbf2c021a0d6128011f4ac4c5b5fd9d11ad0aa..946edd3b8fbb41b490f7ad6d06112c47e7cf8770 100644 (file)
@@ -2,4 +2,4 @@ extends layouts/main
 
 block body
   #content
-    .search-navigator#quality-gates
+    .search-navigator.sticky.search-navigator-extended-view#quality-gates