]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5718 Add a new issues page
authorStas Vilchik <vilchiks@gmail.com>
Wed, 29 Oct 2014 16:56:05 +0000 (17:56 +0100)
committerStas Vilchik <vilchiks@gmail.com>
Wed, 29 Oct 2014 17:05:31 +0000 (18:05 +0100)
93 files changed:
server/sonar-web/Gruntfile.coffee
server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-facets-view.coffee
server/sonar-web/src/main/coffee/issue/collections/issues.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issue/issue-view.coffee
server/sonar-web/src/main/coffee/issues/app.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/component-viewer/main.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/component-viewer/state.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/controller.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/facets-view.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/facets/assignee-facet.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/facets/base-facet.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/facets/project-facet.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/facets/resolution-facet.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/facets/rule-facet.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/facets/severity-facet.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/facets/status-facet.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/filters-view.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/issue-view.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/layout.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/models/facet.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/models/facets.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/models/filter.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/models/filters.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/models/issues.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/models/state.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/router.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/workspace-header-view.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/workspace-list-item-view.coffee [new file with mode: 0644]
server/sonar-web/src/main/coffee/issues/workspace-list-view.coffee [new file with mode: 0644]
server/sonar-web/src/main/hbs/coding-rules/coding-rules-facets.hbs
server/sonar-web/src/main/hbs/component-viewer/cw-layout.hbs
server/sonar-web/src/main/hbs/issues-old/filter-bar.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues-old/issue-detail.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues-old/issues-actions.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues-old/issues-details-favorite-filter.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues-old/issues-header.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues-old/issues.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues-old/no-issues.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues/facets/_issues-facet-header.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues/facets/issues-assignee-facet.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues/facets/issues-base-facet.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues/facets/issues-creation-date-facet.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues/facets/issues-resolution-facet.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues/facets/issues-severity-facet.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues/facets/issues-status-facet.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues/filter-bar.hbs [deleted file]
server/sonar-web/src/main/hbs/issues/issue-detail.hbs [deleted file]
server/sonar-web/src/main/hbs/issues/issues-actions.hbs [deleted file]
server/sonar-web/src/main/hbs/issues/issues-component-viewer.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues/issues-details-favorite-filter.hbs [deleted file]
server/sonar-web/src/main/hbs/issues/issues-filters.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues/issues-header.hbs [deleted file]
server/sonar-web/src/main/hbs/issues/issues-issue.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues/issues-layout.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues/issues-workspace-header.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues/issues-workspace-list-item.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues/issues-workspace-list.hbs [new file with mode: 0644]
server/sonar-web/src/main/hbs/issues/issues.hbs [deleted file]
server/sonar-web/src/main/hbs/issues/no-issues.hbs [deleted file]
server/sonar-web/src/main/js/common/handlebars-extensions.js
server/sonar-web/src/main/js/issues-old/app.js [new file with mode: 0644]
server/sonar-web/src/main/js/issues-old/extra.js [new file with mode: 0644]
server/sonar-web/src/main/js/issues/app.js [deleted file]
server/sonar-web/src/main/js/issues/extra.js [deleted file]
server/sonar-web/src/main/less/component-viewer-source-colorizer.less
server/sonar-web/src/main/less/component-viewer.less
server/sonar-web/src/main/less/components.less [new file with mode: 0644]
server/sonar-web/src/main/less/components/code-source.less [new file with mode: 0644]
server/sonar-web/src/main/less/components/component-issues.less [new file with mode: 0644]
server/sonar-web/src/main/less/components/facets.less [new file with mode: 0644]
server/sonar-web/src/main/less/icons.less
server/sonar-web/src/main/less/issues.less [new file with mode: 0644]
server/sonar-web/src/main/less/navigator/base.less
server/sonar-web/src/main/less/style.less
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issues2_controller.rb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/models/navigation.rb
server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/search.html.erb
server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_bulk_change_form.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_copy_form.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_edit_form.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_favourites.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_save_as_form.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_shared_form.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_list.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations_ajax.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search_ajax.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/manage.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/search.html.erb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 69b39a66782612cdce64cb793a403502d315f7e5..5892d28aa2319de35b6b943cb56a6e5e7df541f5 100644 (file)
@@ -254,6 +254,9 @@ module.exports = (grunt) ->
           '<%= pkg.assets %>js/templates/issues.js': [
             '<%= pkg.sources %>hbs/issues/**/*.hbs'
           ]
+          '<%= pkg.assets %>js/templates/issues-old.js': [
+            '<%= pkg.sources %>hbs/issues-old/**/*.hbs'
+          ]
           '<%= pkg.assets %>js/templates/api-documentation.js': [
             '<%= pkg.sources %>hbs/api-documentation/**/*.hbs'
           ]
index 55b3f0b57201e0531faa5d9104df8542b4a04196..a83094f70844b3bdb8135a6444e012461500220a 100644 (file)
@@ -12,7 +12,7 @@ define [
 
     ui:
       facets: '.navigator-facets-list-item'
-      options: '.navigator-facets-list-item-option'
+      options: '.facet'
 
 
     events:
@@ -50,4 +50,4 @@ define [
         property = jQuery(@).data 'property'
         if !!params[property]
           _(params[property].split(',')).map (value) ->
-            jQuery('.navigator-facets-list-item[data-property="' + property + '"] .navigator-facets-list-item-option[data-key="' + value + '"]').addClass 'active'
+            jQuery('.navigator-facets-list-item[data-property="' + property + '"] .facet[data-key="' + value + '"]').addClass 'active'
diff --git a/server/sonar-web/src/main/coffee/issue/collections/issues.coffee b/server/sonar-web/src/main/coffee/issue/collections/issues.coffee
new file mode 100644 (file)
index 0000000..7dc4da7
--- /dev/null
@@ -0,0 +1,46 @@
+define [
+  'backbone'
+  'issue/models/issue'
+], (
+  Backbone
+  Issue
+) ->
+
+  class extends Backbone.Collection
+    model: Issue
+
+    url: ->
+      "#{baseUrl}/api/issues/search"
+
+
+    parse: (r) ->
+      find = (source, key, keyField) ->
+        searchDict = {}
+        searchDict[keyField || 'key'] = key
+        _.findWhere(source, searchDict) || key
+
+      @paging =
+        p: r.p
+        ps: r.ps
+        total: r.total
+        maxResultsReached: r.p * r.ps >= r.total
+
+      r.issues.map (issue) ->
+        component = find r.components, issue.component
+        project = find r.projects, issue.project
+        rule = find r.rules, issue.rule
+
+        if component
+          _.extend issue,
+            componentLongName: component.longName
+            componentQualifier: component.qualifier
+
+        if project
+          _.extend issue,
+            projectLongName: project.longName
+
+        if rule
+          _.extend issue,
+            ruleName: rule.name
+
+        issue
index 2be7d3120e2d8ab5144400a830d4871f02e819a8..1618fe8d26a40d8574061bd7f47330cebcb375fd 100644 (file)
@@ -38,7 +38,6 @@ define [
 
 
   class IssueView extends Marionette.Layout
-    className: 'code-issues'
     template: Templates['issue']
 
 
diff --git a/server/sonar-web/src/main/coffee/issues/app.coffee b/server/sonar-web/src/main/coffee/issues/app.coffee
new file mode 100644 (file)
index 0000000..29bcf6f
--- /dev/null
@@ -0,0 +1,117 @@
+requirejs.config
+  baseUrl: "#{baseUrl}/js"
+
+  paths:
+    'backbone': 'third-party/backbone'
+    'backbone.marionette': 'third-party/backbone.marionette'
+    'handlebars': 'third-party/handlebars'
+
+  shim:
+    'backbone.marionette':
+      deps: ['backbone']
+      exports: 'Marionette'
+    'backbone':
+      exports: 'Backbone'
+    'handlebars':
+      exports: 'Handlebars'
+
+
+requirejs [
+  'backbone', 'backbone.marionette'
+
+  'issues/models/state'
+  'issues/layout'
+  'issues/models/issues'
+  'issues/models/facets'
+  'issues/models/filters'
+
+  'issues/controller'
+  'issues/router'
+
+  'issues/workspace-list-view'
+  'issues/workspace-header-view'
+
+  'issues/facets-view'
+  'issues/filters-view'
+
+  'common/handlebars-extensions'
+], (
+  Backbone, Marionette
+
+  State
+  Layout
+  Issues
+  Facets
+  Filters
+
+  Controller
+  Router
+
+  WorkspaceListView
+  WorkspaceHeaderView
+
+  FacetsView
+  FiltersView
+) ->
+
+  $ = jQuery
+  App = new Marionette.Application
+
+
+  App.addInitializer ->
+    @layout = new Layout()
+    $('.issues').empty().append @layout.render().el
+
+
+  App.addInitializer ->
+    @state = new State()
+    @issues = new Issues()
+    @facets = new Facets()
+    @filters = new Filters()
+
+
+  App.addInitializer ->
+    @controller = new Controller app: @
+
+
+  App.addInitializer ->
+    @controller.fetchFilters()
+
+
+  App.addInitializer ->
+    @issuesView = new WorkspaceListView
+      app: @
+      collection: @issues
+    @layout.workspaceListRegion.show @issuesView
+    @issuesView.bindScrollEvents()
+
+
+  App.addInitializer ->
+    @workspaceHeaderView = new WorkspaceHeaderView
+      app: @
+      collection: @issues
+    @layout.workspaceHeaderRegion.show @workspaceHeaderView
+
+
+  App.addInitializer ->
+    @facetsView = new FacetsView
+      app: @
+      collection: @facets
+    @layout.facetsRegion.show @facetsView
+
+
+  App.addInitializer ->
+    @filtersView = new FiltersView
+      app: @
+      collection: @filters
+    @layout.filtersRegion.show @filtersView
+
+
+  App.addInitializer ->
+    key.setScope 'list'
+    @router = new Router app: @
+    Backbone.history.start()
+
+
+  l10nXHR = window.requestMessages()
+  jQuery.when(l10nXHR).done -> App.start()
diff --git a/server/sonar-web/src/main/coffee/issues/component-viewer/main.coffee b/server/sonar-web/src/main/coffee/issues/component-viewer/main.coffee
new file mode 100644 (file)
index 0000000..a51aaf1
--- /dev/null
@@ -0,0 +1,191 @@
+define [
+  'backbone'
+  'backbone.marionette'
+  'templates/issues'
+  'issues/models/issues'
+  'issues/issue-view'
+], (
+  Backbone
+  Marionette
+  Templates
+  Issues
+  IssueView
+) ->
+
+  $ = jQuery
+
+  API_SOURCES = "#{baseUrl}/api/sources/show"
+  LINES_AROUND = 500
+
+
+  class extends Marionette.ItemView
+    template: Templates['issues-component-viewer']
+
+
+    ui:
+      sourceBeforeSpinner: '.js-component-viewer-source-before'
+      sourceAfterSpinner: '.js-component-viewer-source-after'
+
+
+    events:
+      'click .js-close-component-viewer': 'closeComponentViewer'
+
+
+    initialize: (options) ->
+      @source = new Backbone.Model
+        source: []
+        formattedSource: []
+      @component = new Backbone.Model()
+      @issues = new Issues()
+      @listenTo @issues, 'add', @addIssue
+      @listenTo options.app.state, 'change:selectedIndex', @select
+      @bindShortcuts()
+      @loadSourceBeforeThrottled = _.throttle @loadSourceBefore, 1000
+      @loadSourceAfterThrottled = _.throttle @loadSourceAfter, 1000
+
+
+    bindShortcuts: ->
+      key 'delete,backspace', 'componentViewer', =>
+        @options.app.controller.closeComponentViewer()
+        false
+
+
+    bindScrollEvents: ->
+      $(window).on 'scroll.issues-component-viewer', (=> @onScroll())
+
+
+    unbindScrollEvents: ->
+      $(window).off 'scroll.issues-component-viewer'
+
+
+    onScroll: ->
+      if @model.get('hasSourceBefore') && $(window).scrollTop() <= @ui.sourceBeforeSpinner.offset().top
+        @loadSourceBeforeThrottled()
+
+      if @model.get('hasSourceAfter') && $(window).scrollTop() + $(window).height() >= @ui.sourceAfterSpinner.offset().top
+        @loadSourceAfterThrottled()
+
+
+    loadSourceBefore: ->
+      @unbindScrollEvents()
+      formattedSource = @source.get 'formattedSource'
+      firstLine = _.first(formattedSource).lineNumber
+      @requestSources(firstLine - LINES_AROUND, firstLine - 1).done (data) =>
+        newFormattedSource = _.map data.sources, (item) => lineNumber: item[0], code: item[1]
+        formattedSource = newFormattedSource.concat formattedSource
+        @source.set formattedSource: formattedSource
+        @model.set hasSourceBefore: newFormattedSource.length == LINES_AROUND
+        @render()
+        @scrollToLine firstLine
+        @bindScrollEvents() if @model.get('hasSourceBefore') || @model.get('hasSourceAfter')
+
+
+    loadSourceAfter: ->
+      @unbindScrollEvents()
+      formattedSource = @source.get 'formattedSource'
+      lastLine = _.last(formattedSource).lineNumber
+      @requestSources(lastLine + 1, lastLine + LINES_AROUND)
+      .done (data) =>
+        newFormattedSource = _.map data.sources, (item) => lineNumber: item[0], code: item[1]
+        formattedSource = formattedSource.concat newFormattedSource
+        @source.set formattedSource: formattedSource
+        @model.set hasSourceAfter: newFormattedSource.length == LINES_AROUND
+        @render()
+        @bindScrollEvents() if @model.get('hasSourceBefore') || @model.get('hasSourceAfter')
+      .fail =>
+        @source.set formattedSource: []
+        @model.set hasSourceAfter: false
+        @render()
+
+
+    onRender: ->
+      @renderIssues()
+
+
+    renderIssues: ->
+      @issues.forEach (issue) =>
+        @renderIssue issue
+
+
+    addIssue: (issue) ->
+      @renderIssue issue
+
+
+    renderIssue: (issue) ->
+      line = issue.get 'line' || 0
+      row = @$("[data-line-number=#{line}]")
+      issueView = new IssueView model: issue
+      if row.length == 0
+        issueView.render().$el.insertBefore @$('.issues-workspace-component-viewer-code')
+      else
+        row.find('.line').addClass 'issue'
+        barRow = $('<tr></tr>').insertAfter row
+        barCell = $('<td colspan="2"></td>').appendTo barRow
+        issueView.render().$el.appendTo barCell
+
+
+    select: ->
+      selected = @options.app.state.get 'selectedIndex'
+      selectedKey = @options.app.issues.at(selected).get 'key'
+      @highlightIssue selectedKey
+
+
+    highlightIssue: (key) ->
+      @$("[data-issue-key]").removeClass 'highlighted'
+      @$("[data-issue-key='#{key}']").addClass 'highlighted'
+
+
+    openFileByIssue: (issue) ->
+      componentKey = issue.get 'component'
+      @issues.reset @options.app.issues.filter (issue) => issue.get('component') == componentKey
+
+      line = issue.get('line') || 0
+      @model.set key: componentKey, issueLine: line
+
+      @requestSources(line - LINES_AROUND, line + LINES_AROUND).done (data) =>
+        formattedSource = _.map data.sources, (item) => lineNumber: item[0], code: item[1]
+        @source.set
+          source: data.sources
+          formattedSource: formattedSource
+        @model.set hasSourceBefore: line > LINES_AROUND
+        @render()
+        @highlightIssue issue.get 'key'
+        @scrollToLine issue.get 'line'
+        @bindScrollEvents()
+        @requestIssues()
+
+
+    requestSources: (lineFrom, lineTo) ->
+      lineFrom = Math.max 0, lineFrom
+      $.get API_SOURCES, key: @model.get('key'), from: lineFrom, to: lineTo
+
+
+    requestIssues: ->
+      lastIssue = @options.app.issues.at @options.app.issues.length - 1
+      lastLine = _.last(@source.get('formattedSource')).lineNumber
+      needMore = lastIssue.get('component') == @model.get 'key'
+      needMore = needMore && (lastIssue.get('line') <= lastLine)
+      if needMore
+        @options.app.controller.fetchNextPage().done =>
+          @addNextIssuesPage()
+          @requestIssues()
+
+
+    addNextIssuesPage: ->
+      componentKey = @model.get 'key'
+      @issues.add @options.app.issues.filter (issue) => issue.get('component') == componentKey
+
+
+    scrollToLine: (line) ->
+      row = @$("[data-line-number=#{line}]")
+      goal = if row.length > 0 then row.offset().top - 40 else 0
+      $(window).scrollTop goal
+
+
+    closeComponentViewer: ->
+      @options.app.controller.closeComponentViewer()
+
+
+    serializeData: ->
+      _.extend super,
+        source: @source.get 'formattedSource'
diff --git a/server/sonar-web/src/main/coffee/issues/component-viewer/state.coffee b/server/sonar-web/src/main/coffee/issues/component-viewer/state.coffee
new file mode 100644 (file)
index 0000000..45e46c2
--- /dev/null
@@ -0,0 +1,12 @@
+define [
+  'backbone'
+], (
+  Backbone
+) ->
+
+  class extends Backbone.Model
+
+    defaults:
+      hasSourceBefore: true
+      hasSourceAfter: true
+
diff --git a/server/sonar-web/src/main/coffee/issues/controller.coffee b/server/sonar-web/src/main/coffee/issues/controller.coffee
new file mode 100644 (file)
index 0000000..a1a0584
--- /dev/null
@@ -0,0 +1,163 @@
+define [
+  'backbone.marionette'
+
+  'issues/component-viewer/main'
+  'issues/component-viewer/state'
+], (
+  Marionette
+
+  ComponentViewer
+  ComponentViewerState
+) ->
+
+  $ = jQuery
+  EXTRA_FIELDS = 'actions,transitions,assigneeName,reporterName,actionPlanName'
+  PAGE_SIZE = 50
+
+
+  class extends Marionette.Controller
+
+    initialize: (options) ->
+      @listenTo options.app.state, 'change:query', @fetchIssues
+
+
+    _issuesParameters: ->
+      p: @options.app.state.get 'page'
+      ps: PAGE_SIZE
+      s: 'FILE_LINE'
+      asc: true
+      extra_fields: EXTRA_FIELDS
+      facets: @options.app.state.get('facets').join()
+
+
+    _allFacets: ->
+      @options.app.state.get('allFacets').map (facet) -> { property: facet }
+
+
+    fetchIssues: (firstPage = true) ->
+      if firstPage
+        @options.app.state.set { selectedIndex: 0, page: 1 }, { silent: true }
+
+      data = @_issuesParameters()
+      _.extend data, @options.app.state.get 'query'
+
+      fetchIssuesProcess = window.process.addBackgroundProcess()
+      $.get "#{baseUrl}/api/issues/search", data, (r) =>
+        issues = @options.app.issues.parseIssues r
+        if firstPage
+          @options.app.issues.reset issues
+        else
+          @options.app.issues.add issues
+
+        _.extend @options.app.facets,
+          components: r.components
+          projects: r.projects
+          rules: r.rules
+          users: r.users
+          actionPlans: r.actionPlans
+        @options.app.facets.reset @_allFacets()
+        @options.app.facets.add r.facets, merge: true
+        @enableFacets @options.app.state.get 'facets'
+
+        @options.app.state.set
+          page: r.p
+          pageSize: r.ps
+          total: r.total
+          maxResultsReached: r.p * r.ps >= r.total
+
+        window.process.finishBackgroundProcess fetchIssuesProcess
+
+
+    fetchNextPage: ->
+      @options.app.state.nextPage()
+      @fetchIssues false
+
+
+    fetchFilters: ->
+      $.get "#{baseUrl}/api/issue_filters/app", (r) =>
+        @options.app.state.set
+          canBulkChange: r.canBulkChange
+          canManageFilters: r.canManageFilters
+        @options.app.filters.reset r.favorites
+
+
+    enableFacet: (facet) ->
+      @options.app.facets.get(facet).set enabled: true
+
+
+    enableFacets: (facets) ->
+      facets.forEach @enableFacet, @
+
+
+    newSearch: ->
+      @options.app.state.unset 'filter'
+      @options.app.state.setQuery resolved: 'false'
+
+
+    applyFilter: (filter) ->
+      query = @parseQuery filter.get 'query'
+      @options.app.state.setQuery query
+      @options.app.state.set filter: filter, changed: false
+
+
+    parseQuery: (query, separator = '|') ->
+      q = {}
+      (query || '').split(separator).forEach (t) ->
+        tokens = t.split('=')
+        if tokens[0] && tokens[1]?
+          q[tokens[0]] = decodeURIComponent tokens[1]
+      q
+
+
+    getQuery: (separator = '|') ->
+      filter = @options.app.state.get 'query'
+      route = []
+      _.map filter, (value, property) ->
+        route.push "#{property}=#{decodeURIComponent value}"
+      route.join separator
+
+
+    _prepareComponent: (issue) ->
+      key: issue.get 'component'
+      name: issue.get 'componentLongName'
+      qualifier: issue.get 'componentQualifier'
+      project: issue.get 'project'
+      projectName: issue.get 'projectLongName'
+
+
+    showComponentViewer: (issue) ->
+      key.setScope 'componentViewer'
+      @options.app.issuesView.unbindScrollEvents()
+      @options.app.state.set 'component', @_prepareComponent(issue)
+      @options.app.componentViewer = new ComponentViewer
+        app: @options.app
+        model: new ComponentViewerState()
+      @options.app.layout.workspaceComponentViewerRegion.show @options.app.componentViewer
+      @options.app.layout.showComponentViewer()
+      @options.app.componentViewer.openFileByIssue issue
+
+
+    closeComponentViewer: ->
+      key.setScope 'list'
+      @options.app.state.unset 'component'
+      @options.app.componentViewer.unbindScrollEvents()
+      @options.app.layout.workspaceComponentViewerRegion.reset()
+      @options.app.layout.hideComponentViewer()
+      @options.app.issuesView.bindScrollEvents()
+      @options.app.issuesView.scrollToIssue()
+
+
+    selectNextIssue: ->
+      index = @options.app.state.get('selectedIndex') + 1
+      if index < @options.app.issues.length
+        @options.app.state.set selectedIndex: index
+      else
+        unless @options.app.state.get('maxResultsReached')
+          @fetchNextPage().done =>
+            @options.app.state.set selectedIndex: index
+
+
+    selectPreviousIssue: ->
+      index = @options.app.state.get('selectedIndex') - 1
+      if index >= 0
+        @options.app.state.set selectedIndex: index
diff --git a/server/sonar-web/src/main/coffee/issues/facets-view.coffee b/server/sonar-web/src/main/coffee/issues/facets-view.coffee
new file mode 100644 (file)
index 0000000..e1e6e12
--- /dev/null
@@ -0,0 +1,43 @@
+define [
+  'backbone.marionette'
+
+  'issues/facets/base-facet'
+  'issues/facets/severity-facet'
+  'issues/facets/status-facet'
+  'issues/facets/project-facet'
+  'issues/facets/assignee-facet'
+  'issues/facets/rule-facet'
+  'issues/facets/resolution-facet'
+  'issues/facets/creation-date-facet'
+], (
+  Marionette
+  BaseFacet
+  SeverityFacet
+  StatusFacet
+  ProjectFacet
+  AssigneeFacet
+  RuleFacet
+  ResolutionFacet
+  CreationDateFacet
+) ->
+
+  class extends Marionette.CollectionView
+    className: 'issues-facets-list'
+
+
+    getItemView: (model) ->
+      switch model.get 'property'
+        when 'severities' then SeverityFacet
+        when 'statuses' then StatusFacet
+        when 'assignees' then AssigneeFacet
+        when 'resolutions' then ResolutionFacet
+        when 'created' then CreationDateFacet
+        when 'componentRootUuids' then ProjectFacet
+        when 'rules' then RuleFacet
+        when 'creationDate' then CreationDateFacet
+        else BaseFacet
+
+
+    itemViewOptions: ->
+      app: @options.app
+
diff --git a/server/sonar-web/src/main/coffee/issues/facets/assignee-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/assignee-facet.coffee
new file mode 100644 (file)
index 0000000..0c922e9
--- /dev/null
@@ -0,0 +1,52 @@
+define [
+  'issues/facets/base-facet'
+  'templates/issues'
+], (
+  BaseFacet
+  Templates
+) ->
+
+  $ = jQuery
+
+
+  class extends BaseFacet
+    template: Templates['issues-assignee-facet']
+
+
+    onRender: ->
+      super
+
+      value = @options.app.state.get('query')['assigned']
+      if value? && (!value || value == 'false')
+        @$('.js-issues-facet').filter("[data-unassigned]").addClass 'active'
+
+
+    toggleFacet: (e) ->
+      unassigned = $(e.currentTarget).is "[data-unassigned]"
+      if unassigned
+        $(e.currentTarget).toggleClass 'active'
+        checked = $(e.currentTarget).is '.active'
+        if checked
+          @options.app.state.updateFilter assigned: 'false', assignees: null
+        else
+          @options.app.state.updateFilter assigned: null, assignees: null
+      else
+        super
+
+
+    getValuesWithLabels: ->
+      values = @model.getValues()
+      users = @options.app.facets.users
+      values.forEach (v) =>
+        login = v.val
+        name = ''
+        if login
+          user = _.findWhere users, login: login
+          name = user.name if user?
+        v.label = name
+      values
+
+
+    serializeData: ->
+      _.extend super,
+        values: @getValuesWithLabels()
diff --git a/server/sonar-web/src/main/coffee/issues/facets/base-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/base-facet.coffee
new file mode 100644 (file)
index 0000000..190028b
--- /dev/null
@@ -0,0 +1,52 @@
+define [
+  'backbone.marionette'
+  'templates/issues'
+], (
+  Marionette
+  Templates
+) ->
+
+  $ = jQuery
+
+
+  class extends Marionette.ItemView
+    className: 'issues-facet-box'
+    template: Templates['issues-base-facet']
+
+
+    modelEvents: ->
+      'change': 'render'
+
+
+    events: ->
+      'click .js-issues-facet-toggle': 'toggle'
+      'click .js-issues-facet': 'toggleFacet'
+
+
+    onRender: ->
+      console.log @model.id, @model.get 'enabled'
+      @$el.toggleClass 'issues-facet-box-collapsed', !@model.get('enabled')
+
+      property = @model.get 'property'
+      value = @options.app.state.get('query')[property]
+      if typeof value == 'string'
+        value.split(',').forEach (s) =>
+          @$('.js-issues-facet').filter("[data-value='#{s}']").addClass 'active'
+
+
+    toggle: ->
+      @model.toggle()
+
+
+    getValue: ->
+      @$('.js-issues-facet.active').map(-> $(@).data 'value').get().join()
+
+
+    toggleFacet: (e) ->
+      $(e.currentTarget).toggleClass 'active'
+      property = @model.get 'property'
+      value = @getValue()
+      obj = {}
+      obj[property] = value
+      @options.app.state.updateFilter obj
+
diff --git a/server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee
new file mode 100644 (file)
index 0000000..5979ba1
--- /dev/null
@@ -0,0 +1,42 @@
+define [
+  'issues/facets/base-facet'
+  'templates/issues'
+], (
+  BaseFacet
+  Templates
+) ->
+
+  $ = jQuery
+
+
+  class extends BaseFacet
+    template: Templates['issues-creation-date-facet']
+
+
+    events: ->
+      _.extend super,
+        'change input': 'applyFacet'
+
+
+    onRender: ->
+      @$el.toggleClass 'issues-facet-box-collapsed', !@model.get('enabled')
+
+      @$('input').datepicker
+        dateFormat: 'yy-mm-dd'
+        changeMonth: true
+        changeYear: true
+
+      props = ['createdAfter', 'createdBefore', 'createdAt']
+      query = @options.app.state.get 'query'
+      props.forEach (prop) =>
+        value = query[prop]
+        @$("input[name=#{prop}]").val value if value?
+
+
+    applyFacet: ->
+      obj = {}
+      @$('input').each ->
+        property = $(@).prop 'name'
+        value = $(@).val()
+        obj[property] = value
+      @options.app.state.updateFilter obj
diff --git a/server/sonar-web/src/main/coffee/issues/facets/project-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/project-facet.coffee
new file mode 100644 (file)
index 0000000..4b44950
--- /dev/null
@@ -0,0 +1,25 @@
+define [
+  'issues/facets/base-facet'
+], (
+  BaseFacet
+) ->
+
+
+  class extends BaseFacet
+
+    getValuesWithLabels: ->
+      values = @model.getValues()
+      projects = @options.app.facets.projects
+      values.forEach (v) =>
+        uuid = v.val
+        label = ''
+        if uuid
+          project = _.findWhere projects, uuid: uuid
+          label = project.longName if project?
+        v.label = label
+      values
+
+
+    serializeData: ->
+      _.extend super,
+        values: @getValuesWithLabels()
diff --git a/server/sonar-web/src/main/coffee/issues/facets/resolution-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/resolution-facet.coffee
new file mode 100644 (file)
index 0000000..a51aa4a
--- /dev/null
@@ -0,0 +1,43 @@
+define [
+  'issues/facets/base-facet'
+  'templates/issues'
+], (
+  BaseFacet
+  Templates
+) ->
+
+  $ = jQuery
+
+
+  class extends BaseFacet
+    template: Templates['issues-resolution-facet']
+
+
+    onRender: ->
+      super
+
+      value = @options.app.state.get('query')['resolved']
+      if value? && (!value || value == 'false')
+        @$('.js-issues-facet').filter("[data-unresolved]").addClass 'active'
+
+
+    toggleFacet: (e) ->
+      unresolved = $(e.currentTarget).is "[data-unresolved]"
+      $(e.currentTarget).toggleClass 'active'
+      if unresolved
+        checked = $(e.currentTarget).is '.active'
+        value = if checked then 'false' else null
+        @options.app.state.updateFilter resolved: value, resolutions: null
+      else
+        @options.app.state.updateFilter resolved: null, resolutions: @getValue()
+
+
+    sortValues: (values) ->
+      order = ['FIXED', 'FALSE-POSITIVE', 'CLOSED']
+      _.sortBy values, (v) -> order.indexOf v.val
+
+
+    serializeData: ->
+      _.extend super,
+        values: @sortValues @model.getValues()
+
diff --git a/server/sonar-web/src/main/coffee/issues/facets/rule-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/rule-facet.coffee
new file mode 100644 (file)
index 0000000..df5b544
--- /dev/null
@@ -0,0 +1,25 @@
+define [
+  'issues/facets/base-facet'
+], (
+  BaseFacet
+) ->
+
+
+  class extends BaseFacet
+
+    getValuesWithLabels: ->
+      values = @model.getValues()
+      rules = @options.app.facets.rules
+      values.forEach (v) =>
+        key = v.val
+        label = ''
+        if key
+          rule = _.findWhere rules, key: key
+          label = rule.name if rule?
+        v.label = label
+      values
+
+
+    serializeData: ->
+      _.extend super,
+        values: @getValuesWithLabels()
diff --git a/server/sonar-web/src/main/coffee/issues/facets/severity-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/severity-facet.coffee
new file mode 100644 (file)
index 0000000..2f7dac4
--- /dev/null
@@ -0,0 +1,22 @@
+define [
+  'issues/facets/base-facet'
+  'templates/issues'
+], (
+  BaseFacet
+  Templates
+) ->
+
+
+  class extends BaseFacet
+    template: Templates['issues-severity-facet']
+
+
+    sortValues: (values) ->
+      order = ['BLOCKER', 'MINOR', 'CRITICAL', 'INFO', 'MAJOR']
+      _.sortBy values, (v) -> order.indexOf v.val
+
+
+    serializeData: ->
+      _.extend super,
+        values: @sortValues @model.getValues()
+
diff --git a/server/sonar-web/src/main/coffee/issues/facets/status-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/status-facet.coffee
new file mode 100644 (file)
index 0000000..ced0a9d
--- /dev/null
@@ -0,0 +1,21 @@
+define [
+  'issues/facets/base-facet'
+  'templates/issues'
+], (
+  BaseFacet
+  Templates
+) ->
+
+
+  class extends BaseFacet
+    template: Templates['issues-status-facet']
+
+
+    sortValues: (values) ->
+      order = ['OPEN', 'RESOLVED', 'REOPENED', 'CLOSED', 'CONFIRMED']
+      _.sortBy values, (v) -> order.indexOf v.val
+
+
+    serializeData: ->
+      _.extend super,
+        values: @sortValues @model.getValues()
diff --git a/server/sonar-web/src/main/coffee/issues/filters-view.coffee b/server/sonar-web/src/main/coffee/issues/filters-view.coffee
new file mode 100644 (file)
index 0000000..1d9f9e9
--- /dev/null
@@ -0,0 +1,77 @@
+define [
+  'backbone.marionette'
+  'templates/issues'
+], (
+  Marionette
+  Templates
+) ->
+
+  $ = jQuery
+
+
+  class extends Marionette.ItemView
+    template: Templates['issues-filters']
+
+
+    events:
+      'click .js-issues-toggle-filters': 'toggleFilters'
+      'click .js-issues-filter': 'applyFilter'
+      'click .js-issues-new-search': 'newSearch'
+      'click .js-issues-save-as': 'saveAs'
+      'click .js-issues-save': 'save'
+      'click .js-issues-copy': 'copy'
+      'click .js-issues-edit': 'edit'
+
+
+    initialize: (options) ->
+      @listenTo options.app.state, 'change:filter', @render
+      @listenTo options.app.state, 'change:changed', @render
+      @listenTo options.app.filters, 'all', @render
+      window.onSaveAs = window.onCopy = window.onEdit = (id) =>
+        $('#modal').dialog 'close'
+        @options.app.controller.fetchFilters().done =>
+          filter = @collection.get id
+          filter.fetch().done => @options.app.controller.applyFilter filter
+
+
+    toggleFilters: ->
+      @$('.issues-filters-list').toggle()
+
+
+    applyFilter: (e) ->
+      id = $(e.currentTarget).data 'id'
+      filter = @collection.get id
+      filter.fetch().done => @options.app.controller.applyFilter filter
+
+
+    newSearch: ->
+      @options.app.controller.newSearch()
+
+
+    saveAs: ->
+      query = @options.app.controller.getQuery '&'
+      url = "#{baseUrl}/issues/save_as_form?#{query}"
+      openModalWindow url, {}
+
+
+    save: ->
+      query = @options.app.controller.getQuery '&'
+      url = "#{baseUrl}/issues/save/#{@options.app.state.get('filter').id}?#{query}"
+      $.post(url).done =>
+        @options.app.state.set changed: false
+
+
+    copy: ->
+      url = "#{baseUrl}/issues/copy_form/#{@options.app.state.get('filter').id}"
+      openModalWindow url, {}
+
+
+    edit: ->
+      url = "#{baseUrl}/issues/edit_form/#{@options.app.state.get('filter').id}"
+      openModalWindow url, {}
+
+
+    serializeData: ->
+      _.extend super,
+        state: @options.app.state.toJSON()
+        filter: @options.app.state.get('filter')?.toJSON()
diff --git a/server/sonar-web/src/main/coffee/issues/issue-view.coffee b/server/sonar-web/src/main/coffee/issues/issue-view.coffee
new file mode 100644 (file)
index 0000000..624327d
--- /dev/null
@@ -0,0 +1,11 @@
+define [
+  'issue/issue-view'
+  'templates/issues'
+], (
+  IssueView
+  Templates
+) ->
+
+
+  class extends IssueView
+    template: Templates['issues-issue']
diff --git a/server/sonar-web/src/main/coffee/issues/layout.coffee b/server/sonar-web/src/main/coffee/issues/layout.coffee
new file mode 100644 (file)
index 0000000..c0649f2
--- /dev/null
@@ -0,0 +1,63 @@
+define [
+  'backbone.marionette'
+  'templates/issues'
+], (
+  Marionette
+  Templates
+) ->
+
+  $ = jQuery
+
+  # http://stackoverflow.com/questions/7600454/how-to-prevent-page-scrolling-when-scrolling-a-div-element
+  $.fn.isolatedScroll = ->
+    @on 'mousewheel DOMMouseScroll', (e) ->
+      delta = e.wheelDelta || (e.originalEvent && e.originalEvent.wheelDelta) || -e.detail
+      bottomOverflow = @scrollTop + $(@).outerHeight() - @scrollHeight >= 0
+      topOverflow = @scrollTop <= 0
+      e.preventDefault() if (delta < 0 && bottomOverflow) || (delta > 0 && topOverflow)
+    @
+
+
+  class extends Marionette.Layout
+    template: Templates['issues-layout']
+
+
+    regions:
+      filtersRegion: '.issues-filters'
+      facetsRegion: '.issues-facets'
+      workspaceHeaderRegion: '.issues-workspace-header'
+      workspaceListRegion: '.issues-workspace-list'
+      workspaceComponentViewerRegion: '.issues-workspace-component-viewer'
+
+
+    initialize: ->
+      $(window).on 'scroll.issues-layout', (=> @onScroll())
+
+
+    onClose: ->
+      $(window).off 'scroll.issues-layout'
+
+
+    onRender: ->
+      @$('.issues-side').isolatedScroll()
+
+
+    onScroll: ->
+      scrollTop = $(window).scrollTop()
+      $('.issues').toggleClass 'sticky', scrollTop >= 30
+      @$('.issues-side').css top: Math.max(0, Math.min(30 - scrollTop, 30))
+
+
+    showSpinner: (region) ->
+      @[region].show new Marionette.ItemView
+        template: _.template('<i class="spinner"></i>')
+
+
+    showComponentViewer: ->
+      @scroll = $(window).scrollTop()
+      $('.issues').addClass 'issues-extended-view'
+
+
+    hideComponentViewer: ->
+      $('.issues').removeClass 'issues-extended-view'
+      $(window).scrollTop @scroll if @scroll?
diff --git a/server/sonar-web/src/main/coffee/issues/models/facet.coffee b/server/sonar-web/src/main/coffee/issues/models/facet.coffee
new file mode 100644 (file)
index 0000000..fa7cb81
--- /dev/null
@@ -0,0 +1,22 @@
+define [
+  'backbone'
+], (
+  Backbone
+) ->
+
+  class extends Backbone.Model
+    idAttribute: 'property'
+
+
+    defaults:
+      enabled: false
+
+
+    getValues: ->
+      console.log @toJSON()
+      @get('values') || []
+
+
+    toggle: ->
+      enabled = @get 'enabled'
+      @set enabled: !enabled
diff --git a/server/sonar-web/src/main/coffee/issues/models/facets.coffee b/server/sonar-web/src/main/coffee/issues/models/facets.coffee
new file mode 100644 (file)
index 0000000..b0be4ad
--- /dev/null
@@ -0,0 +1,10 @@
+define [
+  'backbone'
+  'issues/models/facet'
+], (
+  Backbone
+  Facet
+) ->
+
+  class extends Backbone.Collection
+    model: Facet
diff --git a/server/sonar-web/src/main/coffee/issues/models/filter.coffee b/server/sonar-web/src/main/coffee/issues/models/filter.coffee
new file mode 100644 (file)
index 0000000..bbb881f
--- /dev/null
@@ -0,0 +1,14 @@
+define [
+  'backbone'
+], (
+  Backbone
+) ->
+
+  class extends Backbone.Model
+
+    url: ->
+      "/api/issue_filters/show/#{@id}"
+
+
+    parse: (r) ->
+      if r.filter? then r.filter else r
diff --git a/server/sonar-web/src/main/coffee/issues/models/filters.coffee b/server/sonar-web/src/main/coffee/issues/models/filters.coffee
new file mode 100644 (file)
index 0000000..b6c8f9d
--- /dev/null
@@ -0,0 +1,10 @@
+define [
+  'backbone'
+  'issues/models/filter'
+], (
+  Backbone
+  Filter
+) ->
+
+  class extends Backbone.Collection
+    model: Filter
diff --git a/server/sonar-web/src/main/coffee/issues/models/issues.coffee b/server/sonar-web/src/main/coffee/issues/models/issues.coffee
new file mode 100644 (file)
index 0000000..7174163
--- /dev/null
@@ -0,0 +1,61 @@
+define [
+  'backbone'
+  'issue/models/issue'
+], (
+  Backbone
+  Issue
+) ->
+
+  class extends Backbone.Collection
+    model: Issue
+
+    url: ->
+      "#{baseUrl}/api/issues/search"
+
+
+    # Used to parse /api/issues/search response
+    parseIssues: (r) ->
+      find = (source, key, keyField) ->
+        searchDict = {}
+        searchDict[keyField || 'key'] = key
+        _.findWhere(source, searchDict) || key
+
+      r.issues.map (issue) ->
+        component = find r.components, issue.component
+        project = find r.projects, issue.project
+        rule = find r.rules, issue.rule
+
+        if component
+          _.extend issue,
+            componentLongName: component.longName
+            componentQualifier: component.qualifier
+
+        if project
+          _.extend issue,
+            projectLongName: project.longName
+
+        if rule
+          _.extend issue,
+            ruleName: rule.name
+
+        if _.isArray(issue.sources) && issue.sources.length > 0
+          source = ''
+          issue.sources.forEach (line) ->
+            source = line[1] if line[0] == issue.line
+          _.extend issue, source: source
+
+
+        if _.isArray(issue.scm) && issue.scm.length > 0
+          scmAuthor = ''
+          scmDate = ''
+
+          issue.scm.forEach (line) ->
+            if line[0] == issue.line
+              scmAuthor = line[1]
+              scmDate = line[2]
+
+          _.extend issue,
+            scmAuthor: scmAuthor
+            scmDate: scmDate
+
+        issue
diff --git a/server/sonar-web/src/main/coffee/issues/models/state.coffee b/server/sonar-web/src/main/coffee/issues/models/state.coffee
new file mode 100644 (file)
index 0000000..6ef3f10
--- /dev/null
@@ -0,0 +1,42 @@
+define [
+  'backbone'
+], (
+  Backbone
+) ->
+
+  class extends Backbone.Model
+
+    defaults:
+      page: 1
+      maxResultsReached: false
+
+      query: {}
+
+      facets: ['severities', 'statuses', 'resolutions', 'componentRootUuids']
+      allFacets: ['severities', 'statuses', 'resolutions', 'componentRootUuids', 'assignees', 'reporters', 'rule',
+                  'languages', 'actionPlan', 'creationDate']
+
+
+    nextPage: ->
+      page = @get 'page'
+      @set page: page + 1
+
+
+    cleanQuery: (query) ->
+      q = {}
+      Object.keys(query).forEach (key) ->
+        q[key] = query[key] if query[key]
+      q
+
+
+    updateFilter: (obj) ->
+      filter = @get 'query'
+      _.extend filter, obj
+      @setQuery @cleanQuery filter
+
+
+    setQuery: (query) ->
+      @set { query: query }, { silent: true }
+      @trigger 'change:query'
+      @set changed: true
+
diff --git a/server/sonar-web/src/main/coffee/issues/router.coffee b/server/sonar-web/src/main/coffee/issues/router.coffee
new file mode 100644 (file)
index 0000000..b89b9e5
--- /dev/null
@@ -0,0 +1,32 @@
+define [
+  'backbone'
+], (
+  Backbone
+) ->
+
+  class extends Backbone.Router
+    routeSeparator: '|'
+
+    routes:
+      '': 'emptyQuery'
+      ':query': 'index'
+
+
+    initialize: (options) ->
+      @options = options
+      @listenTo @options.app.state, 'change:query', @updateRoute
+
+
+    emptyQuery: ->
+      @navigate 'resolved=false', { trigger: true, replace: true }
+
+
+    index: (query) ->
+      filter = @options.app.controller.parseQuery query
+      @options.app.state.setQuery filter
+
+
+    updateRoute: ->
+      route = @options.app.controller.getQuery()
+      @navigate route
+
diff --git a/server/sonar-web/src/main/coffee/issues/workspace-header-view.coffee b/server/sonar-web/src/main/coffee/issues/workspace-header-view.coffee
new file mode 100644 (file)
index 0000000..fd2e92e
--- /dev/null
@@ -0,0 +1,70 @@
+define [
+  'backbone.marionette'
+  'templates/issues'
+], (
+  Marionette
+  Templates
+) ->
+
+  $ = jQuery
+
+
+  class extends Marionette.ItemView
+    template: Templates['issues-workspace-header']
+
+
+    collectionEvents:
+      'all': 'render'
+
+
+    events:
+      'click .js-back': 'returnToList'
+      'click .js-issues-bulk-change': 'bulkChange'
+      'click .js-issues-next': 'selectNextIssue'
+      'click .js-issues-prev': 'selectPrevIssue'
+
+
+    initialize: (options) ->
+      @listenTo options.app.state, 'change', @render
+      @_onBulkIssues = window.onBulkIssues
+      window.onBulkIssues = =>
+        $('#modal').dialog 'close'
+        @options.app.controller.fetchIssues()
+      @bindShortcuts()
+
+
+    onClose: ->
+      window.onBulkIssues = @_onBulkIssues
+
+
+    returnToList: ->
+      @options.app.controller.closeComponentViewer()
+
+
+    bulkChange: ->
+      query = @options.app.controller.getQuery '&'
+      url = "#{baseUrl}/issues/bulk_change_form?#{query}"
+      openModalWindow url, {}
+
+
+    selectNextIssue: ->
+      @options.app.controller.selectNextIssue()
+
+
+    selectPrevIssue: ->
+      @options.app.controller.selectPreviousIssue()
+
+
+    bindShortcuts: ->
+      key 'j,up', =>
+        @options.app.controller.selectPreviousIssue()
+        false
+      key 'k,down', =>
+        @options.app.controller.selectNextIssue()
+        false
+
+
+    serializeData: ->
+      _.extend super,
+        state: @options.app.state.toJSON()
+
diff --git a/server/sonar-web/src/main/coffee/issues/workspace-list-item-view.coffee b/server/sonar-web/src/main/coffee/issues/workspace-list-item-view.coffee
new file mode 100644 (file)
index 0000000..1d5f67c
--- /dev/null
@@ -0,0 +1,42 @@
+define [
+  'backbone.marionette'
+  'templates/issues'
+  'issues/issue-view'
+], (
+  Marionette
+  Templates
+  IssueBoxView
+) ->
+
+  class extends Marionette.ItemView
+    tagName: 'li'
+    className: 'issue-box'
+    template: Templates['issues-workspace-list-item']
+
+
+    events:
+      'click .js-issues-snippet': 'openComponentViewer'
+
+
+    initialize: (options) ->
+      @listenTo options.app.state, 'change:selectedIndex', @select
+
+
+    onRender: ->
+      @issueBoxView = new IssueBoxView model: @model
+      @$('.issue-box-details').append @issueBoxView.render().el
+      @select()
+
+
+    select: ->
+      selected = @options.index == @options.app.state.get 'selectedIndex'
+      @$el.toggleClass 'selected', selected
+
+
+    onClose: ->
+      @issueBoxView?.close()
+
+
+    openComponentViewer: ->
+      @options.app.state.set selectedIndex: @options.index
+      @options.app.controller.showComponentViewer @model
diff --git a/server/sonar-web/src/main/coffee/issues/workspace-list-view.coffee b/server/sonar-web/src/main/coffee/issues/workspace-list-view.coffee
new file mode 100644 (file)
index 0000000..18413b1
--- /dev/null
@@ -0,0 +1,93 @@
+define [
+  'backbone.marionette'
+  'templates/issues'
+  'issues/workspace-list-item-view'
+], (
+  Marionette
+  Templates
+  IssueView
+) ->
+
+  $ = jQuery
+
+  TOP_OFFSET = 38
+  BOTTOM_OFFSET = 10
+
+
+  class extends Marionette.CompositeView
+    template: Templates['issues-workspace-list']
+    itemView: IssueView
+    itemViewContainer: 'ul'
+
+
+    ui:
+      loadMore: '.js-issues-more'
+
+
+    itemViewOptions: (_, index) ->
+      app: @options.app
+      index: index
+
+
+    collectionEvents:
+      'reset': 'scrollToTop'
+
+
+    initialize: ->
+      @loadMoreThrottled = _.throttle @loadMore, 1000
+      @listenTo @options.app.state, 'change:maxResultsReached', @toggleLoadMore
+      @listenTo @options.app.state, 'change:selectedIndex', @scrollToIssue
+      @bindShortcuts()
+
+
+    onClose: ->
+      @unbindScrollEvents()
+
+
+    toggleLoadMore: ->
+      @ui.loadMore.toggle !@options.app.state.get 'maxResultsReached'
+
+
+    bindScrollEvents: ->
+      $(window).on 'scroll.issues-workspace-list', (=> @onScroll())
+
+
+    unbindScrollEvents: ->
+      $(window).off 'scroll.issues-workspace-list'
+
+
+    bindShortcuts: ->
+      key 'return', 'list', =>
+        selectedIssue = @collection.at @options.app.state.get 'selectedIndex'
+        @options.app.controller.showComponentViewer selectedIssue
+        return false
+
+
+    loadMore: ->
+      unless @options.app.state.get 'maxResultsReached'
+        @unbindScrollEvents()
+        @options.app.controller.fetchNextPage().done => @bindScrollEvents()
+
+
+    onScroll: ->
+      if $(window).scrollTop() + $(window).height() >= @ui.loadMore.offset().top
+        @loadMoreThrottled()
+
+
+    scrollToTop: ->
+      @$el.scrollParent().scrollTop 0
+
+
+    scrollToIssue: ->
+      selectedIssue = @collection.at @options.app.state.get 'selectedIndex'
+      selectedIssueView = @children.findByModel selectedIssue
+      viewTop = selectedIssueView.$el.offset().top
+      viewBottom = viewTop + selectedIssueView.$el.outerHeight()
+      windowTop = $(window).scrollTop()
+      windowBottom = windowTop + $(window).height()
+      if viewTop < windowTop
+        $(window).scrollTop viewTop - TOP_OFFSET
+      if viewBottom > windowBottom
+        $(window).scrollTop $(window).scrollTop() - windowBottom + viewBottom + BOTTOM_OFFSET
+
+
index d2ea9d4fd56dd39cf87cfde89b5685ce5abc3907..c274ffff5130dd3343daf1b8b690d4beb84a485b 100644 (file)
@@ -14,9 +14,9 @@
       <div class="navigator-facets-list-item-options">
         {{#each values}}
           {{#if count}}
-          <a class="navigator-facets-list-item-option" data-key="{{val}}" data-property="{{../property}}">
-            <span class="navigator-facets-list-item-option-name">{{text}}</span>
-            <span class="navigator-facets-list-item-option-stat">{{count}}</span>
+          <a class="facet" data-key="{{val}}" data-property="{{../property}}">
+            <span class="facet-name">{{text}}</span>
+            <span class="facet-stat">{{count}}</span>
           </a>
           {{/if}}
         {{/each}}
index a010e4c4803a1e3e2d0742832364807ea884e9be..088eadcf56bd632911900f280bf711d200638ae2 100644 (file)
@@ -1,3 +1,3 @@
 <div class="component-viewer-header"></div>
 <div class="component-viewer-workspace"></div>
-<div class="component-viewer-source"></div>
\ No newline at end of file
+<div class="component-viewer-source code-source"></div>
diff --git a/server/sonar-web/src/main/hbs/issues-old/filter-bar.hbs b/server/sonar-web/src/main/hbs/issues-old/filter-bar.hbs
new file mode 100644 (file)
index 0000000..4f19079
--- /dev/null
@@ -0,0 +1,2 @@
+<div class="navigator-filters-list"></div>
+<button class="navigator-filter-submit">{{t 'search_verb'}}</button>
diff --git a/server/sonar-web/src/main/hbs/issues-old/issue-detail.hbs b/server/sonar-web/src/main/hbs/issues-old/issue-detail.hbs
new file mode 100644 (file)
index 0000000..c746f72
--- /dev/null
@@ -0,0 +1,20 @@
+<div class="line line-small">
+  {{severityIcon severity}}{{translate "severities" severity}}&nbsp;
+  {{statusIcon status}}{{translate "statuses" status}}
+  {{#if resolution}}({{translate "resolutions" resolution}}){{/if}}
+
+  <div class="line-right">
+    {{fUpdateAge}}
+  </div>
+</div>
+
+<div class="line line-nowrap" title="{{message}}">
+  {{message}}
+</div>
+
+<div class="line">
+  {{#unless singleProject}}
+    <div class="subtitle line-nowrap">{{default projectLongName projectName}}</div>
+  {{/unless}}
+  <div class="subtitle line-nowrap">{{componentLongName}}</div>
+</div>
diff --git a/server/sonar-web/src/main/hbs/issues-old/issues-actions.hbs b/server/sonar-web/src/main/hbs/issues-old/issues-actions.hbs
new file mode 100644 (file)
index 0000000..351d6d4
--- /dev/null
@@ -0,0 +1,26 @@
+{{#unless maxResultsReached}}
+  <div class="navigator-actions-order">
+    {{#if sorting}}
+      {{t 'issues.ordered_by'}} <strong class="navigator-actions-ordered-by">{{sorting.sortText}}</strong> {{#if sorting.asc}}<i class="icon-sort-asc"></i>{{else}}<i class="icon-sort-desc"></i>{{/if}}
+    {{else}}
+      Order
+    {{/if}}
+  </div>
+  <ul class="navigator-actions-order-choices">
+    <li data-sort="UPDATE_DATE" data-asc="true">{{t 'issues.sort.update_date'}} <i class="icon-sort-asc"></i></li>
+    <li data-sort="UPDATE_DATE" data-asc="false">{{t 'issues.sort.update_date'}} <i class="icon-sort-desc"></i></li>
+    <li data-sort="SEVERITY" data-asc="true">{{t 'issues.sort.severity'}} <i class="icon-sort-asc"></i></li>
+    <li data-sort="SEVERITY" data-asc="false">{{t 'issues.sort.severity'}} <i class="icon-sort-desc"></i></li>
+    <li data-sort="STATUS" data-asc="true">{{t 'issues.sort.status'}} <i class="icon-sort-asc"></i></li>
+    <li data-sort="STATUS" data-asc="false">{{t 'issues.sort.status'}} <i class="icon-sort-desc"></i></li>
+  </ul>
+{{/unless}}
+<div class="navigator-actions-total">
+  {{t 'issues.found'}}: <strong>{{paging.fTotal}}</strong>
+  {{#notEq paging.fTotal 0}}
+    {{#if appState.canBulkChange}}
+      <a href="{{bulkChangeUrl}}?{{query}}"
+         class="navigator-actions-bulk icon-bulk-change" title="{{t 'bulk_change'}}"></a>
+    {{/if}}
+  {{/notEq}}
+</div>
diff --git a/server/sonar-web/src/main/hbs/issues-old/issues-details-favorite-filter.hbs b/server/sonar-web/src/main/hbs/issues-old/issues-details-favorite-filter.hbs
new file mode 100644 (file)
index 0000000..84c2658
--- /dev/null
@@ -0,0 +1,14 @@
+{{#if items}}
+  <ul class="navigator-filter-select-list">
+    {{#each items}}
+      <li>
+        <label data-id="{{id}}">{{{name}}}</label>
+      </li>
+    {{/each}}
+  </ul>
+{{/if}}
+<ul class="navigator-filter-select-list">
+  <li class="manage">
+    <label id="manage-favorites">{{t 'manage'}}</label>
+  </li>
+</ul>
diff --git a/server/sonar-web/src/main/hbs/issues-old/issues-header.hbs b/server/sonar-web/src/main/hbs/issues-old/issues-header.hbs
new file mode 100644 (file)
index 0000000..bb44852
--- /dev/null
@@ -0,0 +1,39 @@
+<h1 class="navigator-header-title">
+  {{#if name}}
+    {{name}}
+    <span class="navigator-header-title-note">
+      {{#unless shared}}
+        [{{t 'issue_filter.private'}}]
+      {{else}}
+        {{#eq user currentUser}}
+          [{{t 'issue_filter.shared_with_all_users'}}]
+        {{else}}
+          {{#if user}}
+            [{{t 'issue_filter.shared'}}]
+          {{/if}}
+        {{/eq}}
+      {{/unless}}
+    </span>
+  {{else}}
+    {{t 'issues'}}
+  {{/if}}
+</h1>
+
+  <div class="navigator-header-actions button-group">
+    <button id="issues-new-search">{{t 'issue_filter.new_search'}}</button>
+
+    {{#if appState.canManageFilters}}
+      {{#if canModify}}
+        {{#if canSave}}<button id="issues-filter-save">{{t 'save'}}</button>{{/if}}
+      {{/if}}
+      {{#unless id}}<button id="issues-filter-save-as">{{t 'save_as'}}</button>{{/unless}}
+      {{#if id}}<button id="issues-filter-copy">{{t 'copy'}}</button>{{/if}}
+      {{#if canModify}}
+        {{#if id}}<button id="issues-filter-edit">{{t 'edit'}}</button>{{/if}}
+      {{/if}}
+    {{/if}}
+  </div>
+
+{{#if description}}
+  <div class="navigator-header-description">{{description}}</div>
+{{/if}}
diff --git a/server/sonar-web/src/main/hbs/issues-old/issues.hbs b/server/sonar-web/src/main/hbs/issues-old/issues.hbs
new file mode 100644 (file)
index 0000000..50dba46
--- /dev/null
@@ -0,0 +1,4 @@
+<ol class="navigator-results-list"></ol>
+<div class="navigator-results-loader">
+  <i class="spinner"></i>
+</div>
diff --git a/server/sonar-web/src/main/hbs/issues-old/no-issues.hbs b/server/sonar-web/src/main/hbs/issues-old/no-issues.hbs
new file mode 100644 (file)
index 0000000..33ec191
--- /dev/null
@@ -0,0 +1 @@
+{{t 'issue_filter.no_issues'}}
diff --git a/server/sonar-web/src/main/hbs/issues/facets/_issues-facet-header.hbs b/server/sonar-web/src/main/hbs/issues/facets/_issues-facet-header.hbs
new file mode 100644 (file)
index 0000000..9b59a84
--- /dev/null
@@ -0,0 +1,4 @@
+<a class="issues-facet-header js-issues-facet-toggle">
+  <i class="icon-checkbox {{#if enabled}}icon-checkbox-checked{{/if}}"></i>
+  {{t 'issues.facet' property}}
+</a>
diff --git a/server/sonar-web/src/main/hbs/issues/facets/issues-assignee-facet.hbs b/server/sonar-web/src/main/hbs/issues/facets/issues-assignee-facet.hbs
new file mode 100644 (file)
index 0000000..835b6a5
--- /dev/null
@@ -0,0 +1,18 @@
+{{> '_issues-facet-header'}}
+
+<div class="issues-facet-list">
+  {{#each values}}
+    {{#eq val ''}}
+      {{! unassigned }}
+      <a class="facet issues-facet js-issues-facet" data-unassigned>
+        <span class="facet-name">Unassigned</span>
+        <span class="facet-stat">{{count}}</span>
+      </a>
+    {{else}}
+      <a class="facet issues-facet js-issues-facet" data-value="{{val}}">
+        <span class="facet-name">{{label}}</span>
+        <span class="facet-stat">{{count}}</span>
+      </a>
+    {{/eq}}
+  {{/each}}
+</div>
diff --git a/server/sonar-web/src/main/hbs/issues/facets/issues-base-facet.hbs b/server/sonar-web/src/main/hbs/issues/facets/issues-base-facet.hbs
new file mode 100644 (file)
index 0000000..0c2bf49
--- /dev/null
@@ -0,0 +1,10 @@
+{{> '_issues-facet-header'}}
+
+<div class="issues-facet-list">
+  {{#each values}}
+    <a class="facet issues-facet js-issues-facet" data-value="{{val}}">
+      <span class="facet-name">{{default label val}}</span>
+      <span class="facet-stat">{{count}}</span>
+    </a>
+  {{/each}}
+</div>
diff --git a/server/sonar-web/src/main/hbs/issues/facets/issues-creation-date-facet.hbs b/server/sonar-web/src/main/hbs/issues/facets/issues-creation-date-facet.hbs
new file mode 100644 (file)
index 0000000..1d90cb2
--- /dev/null
@@ -0,0 +1,9 @@
+{{> '_issues-facet-header'}}
+
+<div class="issues-facet-container justify">
+  <input type="text" class="issues-facet-input" name="createdAfter" placeholder="From">
+  to
+  <input type="text" class="issues-facet-input" name="createdBefore" placeholder="To">
+</div>
+
+<input type="hidden" name="createdAt">
diff --git a/server/sonar-web/src/main/hbs/issues/facets/issues-resolution-facet.hbs b/server/sonar-web/src/main/hbs/issues/facets/issues-resolution-facet.hbs
new file mode 100644 (file)
index 0000000..2268710
--- /dev/null
@@ -0,0 +1,18 @@
+{{> '_issues-facet-header'}}
+
+<div class="issues-facet-list">
+  {{#each values}}
+    {{#eq val ''}}
+    {{! unresolved }}
+      <a class="facet issues-facet issues-facet-half js-issues-facet" data-unresolved>
+        <span class="facet-name">Unresolved</span>
+        <span class="facet-stat">{{count}}</span>
+      </a>
+    {{else}}
+      <a class="facet issues-facet issues-facet-half js-issues-facet" data-value="{{val}}">
+        <span class="facet-name">{{t 'issue.resolution' val}}</span>
+        <span class="facet-stat">{{count}}</span>
+      </a>
+    {{/eq}}
+  {{/each}}
+</div>
diff --git a/server/sonar-web/src/main/hbs/issues/facets/issues-severity-facet.hbs b/server/sonar-web/src/main/hbs/issues/facets/issues-severity-facet.hbs
new file mode 100644 (file)
index 0000000..66deef1
--- /dev/null
@@ -0,0 +1,10 @@
+{{> '_issues-facet-header'}}
+
+<div class="issues-facet-list">
+  {{#each values}}
+    <a class="facet issues-facet issues-facet-half js-issues-facet" data-value="{{val}}">
+      <span class="facet-name">{{severityIcon val}} {{t 'severity' val}}</span>
+      <span class="facet-stat">{{count}}</span>
+    </a>
+  {{/each}}
+</div>
diff --git a/server/sonar-web/src/main/hbs/issues/facets/issues-status-facet.hbs b/server/sonar-web/src/main/hbs/issues/facets/issues-status-facet.hbs
new file mode 100644 (file)
index 0000000..800b7cd
--- /dev/null
@@ -0,0 +1,10 @@
+{{> '_issues-facet-header'}}
+
+<div class="issues-facet-list">
+  {{#each values}}
+    <a class="facet issues-facet issues-facet-half js-issues-facet" data-value="{{val}}">
+      <span class="facet-name">{{statusIcon val}} {{t 'issue.status' val}}</span>
+      <span class="facet-stat">{{count}}</span>
+    </a>
+  {{/each}}
+</div>
diff --git a/server/sonar-web/src/main/hbs/issues/filter-bar.hbs b/server/sonar-web/src/main/hbs/issues/filter-bar.hbs
deleted file mode 100644 (file)
index 4f19079..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-<div class="navigator-filters-list"></div>
-<button class="navigator-filter-submit">{{t 'search_verb'}}</button>
diff --git a/server/sonar-web/src/main/hbs/issues/issue-detail.hbs b/server/sonar-web/src/main/hbs/issues/issue-detail.hbs
deleted file mode 100644 (file)
index c746f72..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<div class="line line-small">
-  {{severityIcon severity}}{{translate "severities" severity}}&nbsp;
-  {{statusIcon status}}{{translate "statuses" status}}
-  {{#if resolution}}({{translate "resolutions" resolution}}){{/if}}
-
-  <div class="line-right">
-    {{fUpdateAge}}
-  </div>
-</div>
-
-<div class="line line-nowrap" title="{{message}}">
-  {{message}}
-</div>
-
-<div class="line">
-  {{#unless singleProject}}
-    <div class="subtitle line-nowrap">{{default projectLongName projectName}}</div>
-  {{/unless}}
-  <div class="subtitle line-nowrap">{{componentLongName}}</div>
-</div>
diff --git a/server/sonar-web/src/main/hbs/issues/issues-actions.hbs b/server/sonar-web/src/main/hbs/issues/issues-actions.hbs
deleted file mode 100644 (file)
index 351d6d4..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-{{#unless maxResultsReached}}
-  <div class="navigator-actions-order">
-    {{#if sorting}}
-      {{t 'issues.ordered_by'}} <strong class="navigator-actions-ordered-by">{{sorting.sortText}}</strong> {{#if sorting.asc}}<i class="icon-sort-asc"></i>{{else}}<i class="icon-sort-desc"></i>{{/if}}
-    {{else}}
-      Order
-    {{/if}}
-  </div>
-  <ul class="navigator-actions-order-choices">
-    <li data-sort="UPDATE_DATE" data-asc="true">{{t 'issues.sort.update_date'}} <i class="icon-sort-asc"></i></li>
-    <li data-sort="UPDATE_DATE" data-asc="false">{{t 'issues.sort.update_date'}} <i class="icon-sort-desc"></i></li>
-    <li data-sort="SEVERITY" data-asc="true">{{t 'issues.sort.severity'}} <i class="icon-sort-asc"></i></li>
-    <li data-sort="SEVERITY" data-asc="false">{{t 'issues.sort.severity'}} <i class="icon-sort-desc"></i></li>
-    <li data-sort="STATUS" data-asc="true">{{t 'issues.sort.status'}} <i class="icon-sort-asc"></i></li>
-    <li data-sort="STATUS" data-asc="false">{{t 'issues.sort.status'}} <i class="icon-sort-desc"></i></li>
-  </ul>
-{{/unless}}
-<div class="navigator-actions-total">
-  {{t 'issues.found'}}: <strong>{{paging.fTotal}}</strong>
-  {{#notEq paging.fTotal 0}}
-    {{#if appState.canBulkChange}}
-      <a href="{{bulkChangeUrl}}?{{query}}"
-         class="navigator-actions-bulk icon-bulk-change" title="{{t 'bulk_change'}}"></a>
-    {{/if}}
-  {{/notEq}}
-</div>
diff --git a/server/sonar-web/src/main/hbs/issues/issues-component-viewer.hbs b/server/sonar-web/src/main/hbs/issues/issues-component-viewer.hbs
new file mode 100644 (file)
index 0000000..1fe6686
--- /dev/null
@@ -0,0 +1,18 @@
+<div class="issues-workspace-component-viewer-code code-source">
+  {{#if hasSourceBefore}}
+    <i class="spinner js-component-viewer-source-before"></i>
+  {{/if}}
+
+  <table class="code">
+    {{#each source}}
+      <tr class="row" data-line-number="{{lineNumber}}">
+        <td class="stat ">{{lineNumber}}</td>
+        <td class="line"><pre>{{{code}}}</pre></td>
+      </tr>
+    {{/each}}
+  </table>
+
+  {{#if hasSourceAfter}}
+    <i class="spinner js-component-viewer-source-after"></i>
+  {{/if}}
+</div>
diff --git a/server/sonar-web/src/main/hbs/issues/issues-details-favorite-filter.hbs b/server/sonar-web/src/main/hbs/issues/issues-details-favorite-filter.hbs
deleted file mode 100644 (file)
index 84c2658..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-{{#if items}}
-  <ul class="navigator-filter-select-list">
-    {{#each items}}
-      <li>
-        <label data-id="{{id}}">{{{name}}}</label>
-      </li>
-    {{/each}}
-  </ul>
-{{/if}}
-<ul class="navigator-filter-select-list">
-  <li class="manage">
-    <label id="manage-favorites">{{t 'manage'}}</label>
-  </li>
-</ul>
diff --git a/server/sonar-web/src/main/hbs/issues/issues-filters.hbs b/server/sonar-web/src/main/hbs/issues/issues-filters.hbs
new file mode 100644 (file)
index 0000000..7009e8b
--- /dev/null
@@ -0,0 +1,34 @@
+<div class="issues-filters-list">
+  {{#each items}}
+    <a class="issues-filters-button issues-filters-filter js-issues-filter" data-id="{{id}}">{{name}}</a>
+    <br>
+  {{/each}}
+</div>
+
+<div class="issues-filters-header">
+  {{#notEmpty items}}
+    <a class="issues-filters-button issues-filters-header-filters js-issues-toggle-filters">
+      {{default filter.name "Saved Filters"}}
+    </a>
+  {{/notEmpty}}
+
+  <span class="issues-filters-header-actions">
+    <a class="issues-filters-button js-issues-new-search">New Search</a>
+
+    {{#if state.canManageFilters}}
+      {{#if filter.canModify}}
+        {{#if state.changed}}
+          <a class="issues-filters-button js-issues-save">{{t 'save'}}</a>
+        {{/if}}
+      {{/if}}
+
+      {{#unless filter.id}}<a class="issues-filters-button js-issues-save-as">{{t 'save_as'}}</a>{{/unless}}
+
+      {{#if filter.id}}<a class="issues-filters-button js-issues-copy">{{t 'copy'}}</a>{{/if}}
+
+      {{#if filter.canModify}}
+        {{#if filter.id}}<a class="issues-filters-button js-issues-edit">{{t 'edit'}}</a>{{/if}}
+      {{/if}}
+    {{/if}}
+  </span>
+</div>
diff --git a/server/sonar-web/src/main/hbs/issues/issues-header.hbs b/server/sonar-web/src/main/hbs/issues/issues-header.hbs
deleted file mode 100644 (file)
index bb44852..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<h1 class="navigator-header-title">
-  {{#if name}}
-    {{name}}
-    <span class="navigator-header-title-note">
-      {{#unless shared}}
-        [{{t 'issue_filter.private'}}]
-      {{else}}
-        {{#eq user currentUser}}
-          [{{t 'issue_filter.shared_with_all_users'}}]
-        {{else}}
-          {{#if user}}
-            [{{t 'issue_filter.shared'}}]
-          {{/if}}
-        {{/eq}}
-      {{/unless}}
-    </span>
-  {{else}}
-    {{t 'issues'}}
-  {{/if}}
-</h1>
-
-  <div class="navigator-header-actions button-group">
-    <button id="issues-new-search">{{t 'issue_filter.new_search'}}</button>
-
-    {{#if appState.canManageFilters}}
-      {{#if canModify}}
-        {{#if canSave}}<button id="issues-filter-save">{{t 'save'}}</button>{{/if}}
-      {{/if}}
-      {{#unless id}}<button id="issues-filter-save-as">{{t 'save_as'}}</button>{{/unless}}
-      {{#if id}}<button id="issues-filter-copy">{{t 'copy'}}</button>{{/if}}
-      {{#if canModify}}
-        {{#if id}}<button id="issues-filter-edit">{{t 'edit'}}</button>{{/if}}
-      {{/if}}
-    {{/if}}
-  </div>
-
-{{#if description}}
-  <div class="navigator-header-description">{{description}}</div>
-{{/if}}
diff --git a/server/sonar-web/src/main/hbs/issues/issues-issue.hbs b/server/sonar-web/src/main/hbs/issues/issues-issue.hbs
new file mode 100644 (file)
index 0000000..7027e07
--- /dev/null
@@ -0,0 +1,142 @@
+<div class="code-issue code-issue-modern code-issue-collapsed" data-issue-key="{{key}}" data-issue-component="{{component}}" data-issue-rule="{{rule}}">
+  <div class="code-issue-name code-issue-toggle">
+    <div class="code-issue-name-rule">
+      {{severityIcon severity}}&nbsp;<span class="rulename">{{message}}</span>
+    </div>
+
+    <div class="code-issue-permalink">
+      <a target="_blank" href="{{permalink}}">
+        <i class="icon-link"></i>
+      </a>
+    </div>
+  </div>
+
+
+  <ul class="code-issue-actions code-issue-list">
+    {{#inArray actions "comment"}}
+      <li>
+        <a id="issue-comment" class="link-action">{{t 'issue.comment.formlink' }}</a>
+      </li>
+    {{/inArray}}
+
+
+    <li>
+      {{statusIcon status}}{{t 'issue.status' status}}
+      {{#if resolution}}({{t 'issue.resolution' resolution}}){{/if}}
+
+      {{#ifNotEmpty transitions}}
+        {{#each transitions}}
+          <a class="link-action issue-transition spacer-left" data-transition="{{this}}">{{t 'issue.transition' this}}</a>
+        {{/each}}
+      {{/ifNotEmpty}}
+    </li>
+
+
+    {{#inArray actions "assign"}}
+    <li>
+      {{#if assigneeName}}
+        <a id="issue-assign" class="link-action">{{t 'assigned_to'}}</a> {{assigneeName}}</li>
+      {{else}}
+        <a id="issue-assign" class="link-action">{{t 'issue.assign.formlink' }}</a>
+        {{#inArray actions "assign_to_me"}}
+          [<a id="issue-assign-to-me" class="link-action">{{t 'issue.assign.to_me' }}</a>]
+        {{/inArray}}
+      {{/if}}
+      </li>
+    {{else}}
+      {{#if assigneeName}}
+        <li>{{t 'assigned_to'}} <strong>{{assigneeName}}</strong></li>
+      {{/if}}
+    {{/inArray}}
+
+
+    {{#inArray actions "plan"}}
+      <li>
+        {{#if actionPlanName}}
+          <a id="issue-plan" class="link-action">{{t 'issue.planned_for'}}</a> {{actionPlanName}}
+        {{else}}
+          <a id="issue-plan" class="link-action">{{t 'issue.do_plan'}}</a>
+        {{/if}}
+      </li>
+    {{else}}
+      {{#if actionPlanName}}
+        <li>{{t 'issue.planned_for'}} <strong>{{actionPlanName}}</strong></li>
+      {{/if}}
+    {{/inArray}}
+
+
+    {{#ifHasExtraActions actions}}
+      <li>
+        <div class="dropdown">
+          <a class="link-action link-more" onclick="showDropdownMenuOnElement($j(this).next('.dropdown-menu')); return false;">{{t 'more_actions'}}</a>
+          <ul style="display: none" class="dropdown-menu">
+            {{#inArray actions "set_severity"}}
+              <li>
+                <a id="issue-set-severity" class="link-action spacer-right">{{t "issue.set_severity"}}</a>
+              </li>
+            {{/inArray}}
+            {{#pluginActions actions}}
+              <li>
+                <a class="link-action spacer-right issue-action" data-action="{{this}}">{{t "issue.action" this "formlink"}}</a>
+              </li>
+            {{/pluginActions}}
+          </ul>
+        </div>
+      </li>
+    {{else}}
+      {{#inArray actions "set_severity"}}
+        <li>
+          <a id="issue-set-severity" class="link-action">{{t "issue.set_severity"}}</a>
+        </li>
+      {{/inArray}}
+    {{/ifHasExtraActions}}
+
+    {{#if debt}}
+      <li>{{t 'issue.technical_debt_short'}}: {{debt}}</li>
+    {{/if}}
+
+    {{#if reporterName}}<li>{{t 'reporter'}}: {{reporterName}}</li>{{/if}}
+    {{#if author}}<li>{{t 'author'}}: {{author}}</li>{{/if}}
+  </ul>
+
+  <div class="code-issue-form" style="display: none;"></div>
+
+
+  <div class="code-issue-details">
+    <ul class="code-issue-tabs">
+      <li>
+        <a class="js-tab-link" href="#tab-issue-rule">{{t 'rule'}}</a>
+      </li>
+      <li>
+        <a class="js-tab-link" href="#tab-issue-changelog">{{t 'changelog'}}</a>
+      </li>
+    </ul>
+
+    <div id="tab-issue-rule" class="js-tab">
+      <div class="rule-desc"></div>
+    </div>
+
+    <div id="tab-issue-changelog" class="js-tab"></div>
+  </div>
+
+
+  <div class="code-issue-comments">
+    {{#each comments}}
+      <div class="code-issue-comment" data-comment-key="{{key}}">
+        <h4>
+          <i class="icon-comment"></i>
+          <b>{{userName}}</b>
+          ({{fromNow createdAt}})
+
+          {{#if updatable}}
+            &nbsp;&nbsp;
+            <a class="link-action issue-comment-edit">{{t 'edit'}}</a>&nbsp;
+            <a class="link-action link-red spacer-right issue-comment-delete"
+               data-confirm-msg="{{t 'issue.comment.delete_confirm_message'}}">{{t 'delete'}}</a>
+          {{/if}}
+        </h4>
+        <div class="markdown">{{{show html htmlText}}}</div>
+      </div>
+    {{/each}}
+  </div>
+</div>
diff --git a/server/sonar-web/src/main/hbs/issues/issues-layout.hbs b/server/sonar-web/src/main/hbs/issues/issues-layout.hbs
new file mode 100644 (file)
index 0000000..dfa704a
--- /dev/null
@@ -0,0 +1,10 @@
+<div class="issues-side">
+  <div class="issues-filters"></div>
+  <div class="issues-facets"></div>
+</div>
+
+<div class="issues-workspace">
+  <div class="issues-workspace-header issues-header"></div>
+  <div class="issues-workspace-list"></div>
+  <div class="issues-workspace-component-viewer"></div>
+</div>
diff --git a/server/sonar-web/src/main/hbs/issues/issues-workspace-header.hbs b/server/sonar-web/src/main/hbs/issues/issues-workspace-header.hbs
new file mode 100644 (file)
index 0000000..e2c8743
--- /dev/null
@@ -0,0 +1,21 @@
+{{#if state.component}}
+  <span class="issues-header-item">
+    <a class="js-back">Return to List</a>
+  </span>
+
+  <span class="issues-header-item">
+    {{state.component.projectName}} {{state.component.name}}
+  </span>
+{{/if}}
+
+<span class="issues-header-item issues-header-nav">
+  <a class="icon-bulk-change js-issues-bulk-change"></a>
+</span>
+
+{{#if state.total}}
+  <span class="issues-header-item issues-header-nav">
+    <a class="js-issues-prev icon-prev"></a>
+    <span class="current">{{sum state.selectedIndex 1}} / {{state.total}}</span>
+    <a class="js-issues-next icon-next"></a>
+  </span>
+{{/if}}
diff --git a/server/sonar-web/src/main/hbs/issues/issues-workspace-list-item.hbs b/server/sonar-web/src/main/hbs/issues/issues-workspace-list-item.hbs
new file mode 100644 (file)
index 0000000..35fe124
--- /dev/null
@@ -0,0 +1,24 @@
+<div class="issue-box-component subtitle">
+  <a href="{{dashboardUrl project}}">{{projectLongName}}</a>
+   &nbsp;&nbsp;&nbsp;
+  <a href="{{dashboardUrl component}}">{{componentLongName}}</a>
+</div>
+
+{{#if source}}
+  <div class="issue-box-snippet code-source js-issues-snippet">
+    <table class="code">
+      <tr class="row">
+        {{#if scmAuthor}}
+          <td class="stat scm">
+            <span class="scm-date">{{scmDate}}</span>
+            <span class="scm-author" title="{{scmAuthor}}">{{scmAuthor}}</span>
+          </td>
+        {{/if}}
+        <td class="stat lid">{{line}}</td>
+        <td class="line issue"><pre>{{{source}}}</pre></td>
+      </tr>
+    </table>
+  </div>
+{{/if}}
+
+<div class="issue-box-details"></div>
diff --git a/server/sonar-web/src/main/hbs/issues/issues-workspace-list.hbs b/server/sonar-web/src/main/hbs/issues/issues-workspace-list.hbs
new file mode 100644 (file)
index 0000000..74f2f7a
--- /dev/null
@@ -0,0 +1,5 @@
+<ul></ul>
+
+<div class="issues-workspace-list-more js-issues-more">
+  <i class="spinner"></i>
+</div>
diff --git a/server/sonar-web/src/main/hbs/issues/issues.hbs b/server/sonar-web/src/main/hbs/issues/issues.hbs
deleted file mode 100644 (file)
index 50dba46..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-<ol class="navigator-results-list"></ol>
-<div class="navigator-results-loader">
-  <i class="spinner"></i>
-</div>
diff --git a/server/sonar-web/src/main/hbs/issues/no-issues.hbs b/server/sonar-web/src/main/hbs/issues/no-issues.hbs
deleted file mode 100644 (file)
index 33ec191..0000000
+++ /dev/null
@@ -1 +0,0 @@
-{{t 'issue_filter.no_issues'}}
index 65a211bfc74cc8882cdef9764c381da85180c74f..e927143b7c18b75383197eeedf07529bc1147511 100644 (file)
@@ -206,9 +206,9 @@ define(['handlebars'], function (Handlebars) {
   });
 
   Handlebars.registerHelper('dashboardUrl', function(componentKey, componentQualifier) {
-    var url = baseUrl + '/dashboard/index/' + decodeURIComponent(componentKey);
+    var url = baseUrl + '/dashboard/index?id=' + encodeURIComponent(componentKey);
     if (componentQualifier === 'FIL' || componentQualifier === 'CLA') {
-      url += '?metric=sqale_index';
+      url += '&metric=sqale_index';
     }
     return url;
   });
@@ -268,6 +268,14 @@ define(['handlebars'], function (Handlebars) {
     }
   });
 
+  Handlebars.registerHelper('withLast', function(list, options) {
+    if (list && list.length > 0) {
+      return options.fn(list[list.length - 1]);
+    } else {
+      return '';
+    }
+  });
+
   Handlebars.registerHelper('withoutFirst', function(list, options) {
     if (list && list.length > 1) {
       return list.slice(1).reduce(function(prev, current) {
diff --git a/server/sonar-web/src/main/js/issues-old/app.js b/server/sonar-web/src/main/js/issues-old/app.js
new file mode 100644 (file)
index 0000000..2d45ffd
--- /dev/null
@@ -0,0 +1,551 @@
+requirejs.config({
+  baseUrl: baseUrl + '/js',
+
+  paths: {
+    'backbone': 'third-party/backbone',
+    'backbone.marionette': 'third-party/backbone.marionette',
+    'handlebars': 'third-party/handlebars'
+  },
+
+  shim: {
+    'backbone.marionette': {
+      deps: ['backbone'],
+      exports: 'Marionette'
+    },
+    'backbone': {
+      exports: 'Backbone'
+    },
+    'handlebars': {
+      exports: 'Handlebars'
+    }
+  }
+
+});
+
+requirejs(
+    [
+      'backbone', 'backbone.marionette', 'handlebars',
+      'issues-old/extra',
+      'navigator/filters/filter-bar',
+      'navigator/filters/base-filters',
+      'navigator/filters/checkbox-filters',
+      'navigator/filters/choice-filters',
+      'navigator/filters/ajax-select-filters',
+      'navigator/filters/favorite-filters',
+      'navigator/filters/range-filters',
+      'navigator/filters/context-filters',
+      'navigator/filters/read-only-filters',
+      'navigator/filters/action-plan-filters',
+      'navigator/filters/rule-filters',
+
+      'common/handlebars-extensions'
+    ],
+    function (Backbone, Marionette, Handlebars, Extra, FilterBar, BaseFilters, CheckboxFilterView,
+              ChoiceFilters, AjaxSelectFilters, FavoriteFilters, RangeFilters, ContextFilterView,
+              ReadOnlyFilterView, ActionPlanFilterView, RuleFilterView) {
+      Handlebars.registerPartial('detailInnerTemplate', jQuery('#issue-detail-inner-template').html());
+
+      var NavigatorApp = new Marionette.Application();
+
+
+      NavigatorApp.addRegions({
+        headerRegion: '.navigator-header',
+        filtersRegion: '.navigator-filters',
+        resultsRegion: '.navigator-results',
+        actionsRegion: '.navigator-actions',
+        detailsRegion: '.navigator-details'
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        jQuery('html').addClass('navigator-page issues-page');
+
+        this.appState = new Extra.AppState();
+        window.SS.appState = this.appState;
+
+        this.state = new Backbone.Model({
+          query: ''
+        });
+
+        this.issues = new Extra.Issues();
+        this.issues.sorting = {
+          sort: 'UPDATE_DATE',
+          asc: false
+        };
+        this.issuesPage = 1;
+
+        this.filters = new BaseFilters.Filters();
+
+        this.favoriteFilter = new Extra.FavoriteFilter();
+        this.issuesHeaderView = new Extra.IssuesHeaderView({
+          app: this,
+          model: this.favoriteFilter
+        });
+        this.headerRegion.show(this.issuesHeaderView);
+
+        this.issuesView = new Extra.IssuesView({
+          app: this,
+          collection: this.issues
+        });
+        this.resultsRegion.show(this.issuesView);
+
+        this.issuesActionsView = new Extra.IssuesActionsView({
+          app: this,
+          collection: this.issues
+        });
+        this.actionsRegion.show(this.issuesActionsView);
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        this.projectFilter = new BaseFilters.Filter({
+          name: window.SS.phrases.project,
+          property: 'componentRoots',
+          type: AjaxSelectFilters.ProjectFilterView,
+          enabled: true,
+          optional: false
+        });
+        this.filters.add(this.projectFilter);
+
+        this.assigneeChoices = {
+          '!assigned': window.SS.phrases.unassigned
+        };
+        this.reporterChoices = {};
+        if (window.SS.currentUser) {
+          this.assigneeChoices[window.SS.currentUser] = window.SS.currentUserName + ' (' + window.SS.currentUser + ')';
+          this.reporterChoices[window.SS.currentUser] = window.SS.currentUserName + ' (' + window.SS.currentUser + ')';
+        }
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        this.filters.add([
+          new BaseFilters.Filter({
+            name: window.SS.phrases.severity,
+            property: 'severities',
+            type: ChoiceFilters.ChoiceFilterView,
+            enabled: true,
+            optional: false,
+            choices: {
+              'BLOCKER': window.SS.phrases.severities.BLOCKER,
+              'CRITICAL': window.SS.phrases.severities.CRITICAL,
+              'MAJOR': window.SS.phrases.severities.MAJOR,
+              'MINOR': window.SS.phrases.severities.MINOR,
+              'INFO': window.SS.phrases.severities.INFO
+            },
+            choiceIcons: {
+              'BLOCKER': 'severity-blocker',
+              'CRITICAL': 'severity-critical',
+              'MAJOR': 'severity-major',
+              'MINOR': 'severity-minor',
+              'INFO': 'severity-info'
+            }
+          })
+        ]);
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        this.filters.add([
+          new BaseFilters.Filter({
+            name: window.SS.phrases.status,
+            property: 'statuses',
+            type: ChoiceFilters.ChoiceFilterView,
+            enabled: true,
+            optional: false,
+            choices: {
+              'OPEN': window.SS.phrases.statuses.OPEN,
+              'CONFIRMED': window.SS.phrases.statuses.CONFIRMED,
+              'REOPENED': window.SS.phrases.statuses.REOPENED,
+              'RESOLVED': window.SS.phrases.statuses.RESOLVED,
+              'CLOSED': window.SS.phrases.statuses.CLOSED
+            },
+            choiceIcons: {
+              'OPEN': 'status-open',
+              'CONFIRMED': 'status-confirmed',
+              'REOPENED': 'status-reopened',
+              'RESOLVED': 'status-resolved',
+              'CLOSED': 'status-closed'
+            }
+          })
+        ]);
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        this.filters.add([
+          new BaseFilters.Filter({
+            name: window.SS.phrases.assignee,
+            property: 'assignees',
+            type: AjaxSelectFilters.AssigneeFilterView,
+            enabled: true,
+            optional: false,
+            choices: this.assigneeChoices
+          })
+        ]);
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        this.filters.add([
+          new BaseFilters.Filter({
+            name: window.SS.phrases.resolution,
+            property: 'resolutions',
+            type: ChoiceFilters.ChoiceFilterView,
+            enabled: true,
+            optional: false,
+            choices: {
+              '!resolved=true': window.SS.phrases.resolutions.RESOLVED,
+              '!resolved=false': window.SS.phrases.resolutions.UNRESOLVED,
+              'FALSE-POSITIVE': window.SS.phrases.resolutions['FALSE-POSITIVE'],
+              'FIXED': window.SS.phrases.resolutions.FIXED,
+              'REMOVED': window.SS.phrases.resolutions.REMOVED
+            }
+          })
+        ]);
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        this.filters.add([
+          new BaseFilters.Filter({
+            name: window.SS.phrases.actionPlan,
+            property: 'actionPlans',
+            type: ActionPlanFilterView,
+            enabled: false,
+            optional: true,
+            projectFilter: this.projectFilter,
+            choices: {
+              '!planned': window.SS.phrases.unplanned
+            }
+          })
+        ]);
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        this.filters.add([
+          new BaseFilters.Filter({
+            name: window.SS.phrases.created,
+            propertyFrom: 'createdAfter',
+            propertyTo: 'createdBefore',
+            type: RangeFilters.DateRangeFilterView,
+            enabled: false,
+            optional: true
+          })
+        ]);
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        this.filters.add([
+          new BaseFilters.Filter({
+            name: window.SS.phrases.createdAt,
+            property: 'createdAt',
+            type: ReadOnlyFilterView,
+            enabled: false,
+            optional: true,
+            format: function(value) { return moment(value).format('YYYY-MM-DD HH:mm'); }
+          })
+        ]);
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        this.filters.add([
+          new BaseFilters.Filter({
+            name: window.SS.phrases.language,
+            property: 'languages',
+            type: ChoiceFilters.ChoiceFilterView,
+            enabled: false,
+            optional: true,
+            choices: window.SS.languages
+          })
+        ]);
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        this.filters.add([
+          new BaseFilters.Filter({
+            name: window.SS.phrases.reporter,
+            property: 'reporters',
+            type: AjaxSelectFilters.ReporterFilterView,
+            enabled: false,
+            optional: true,
+            choices: this.reporterChoices
+          })
+        ]);
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        this.filters.add([
+          new BaseFilters.Filter({
+            name: window.SS.phrases.rule,
+            property: 'rules',
+            type: RuleFilterView,
+            enabled: false,
+            optional: true
+          })
+        ]);
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        this.filterBarView = new Extra.IssuesFilterBarView({
+          app: this,
+          collection: this.filters,
+          extra: {
+            sort: '',
+            asc: false
+          }
+        });
+
+        this.filtersRegion.show(this.filterBarView);
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        var app = this;
+
+        jQuery.when(this.appState.fetch()).done(function () {
+
+          if (app.appState.get('favorites')) {
+            app.filters.unshift(
+                new BaseFilters.Filter({
+                  type: Extra.IssuesFavoriteFilterView,
+                  enabled: true,
+                  optional: false,
+                  choices: app.appState.get('favorites'),
+                  manageUrl: '/issues/manage'
+                })
+            );
+          }
+
+          app.router = new Extra.IssuesRouter({
+            app: app
+          });
+          Backbone.history.start();
+
+          app.favoriteFilter.on('change:query', function (model, query) {
+            app.router.navigate(query, { trigger: true, replace: true });
+          });
+        });
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        var app = this;
+
+        window.onBulkIssues = function () {
+          app.fetchFirstPage();
+          jQuery('#modal').dialog('close');
+        };
+
+        window.onSaveAs = window.onCopy = window.onEdit = function (id) {
+          jQuery('#modal').dialog('close');
+          app.appState.fetch();
+
+          var filter = new Extra.FavoriteFilter({ id: id });
+          filter.fetch({
+            success: function () {
+              app.state.set('search', false);
+              app.favoriteFilter.set(filter.toJSON());
+              app.fetchFirstPage();
+            }
+          });
+        };
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        this.onResize = function() {
+          var footerEl = jQuery('#footer'),
+              footerHeight = footerEl.outerHeight(true);
+
+          var resultsEl = jQuery('.navigator-results'),
+              resultsHeight = jQuery(window).height() - resultsEl.offset().top -
+                  parseInt(resultsEl.css('margin-bottom'), 10) - footerHeight;
+          resultsEl.height(resultsHeight);
+
+          var detailsEl = jQuery('.navigator-details'),
+              detailsWidth = jQuery(window).width() - detailsEl.offset().left -
+                  parseInt(detailsEl.css('margin-right'), 10) - 20,
+              detailsHeight = jQuery(window).height() - detailsEl.offset().top -
+                  parseInt(detailsEl.css('margin-bottom'), 10) - footerHeight;
+          detailsEl.width(detailsWidth).height(detailsHeight);
+
+          var resultsLoadingEl = jQuery('.navigator-results-loader');
+          resultsLoadingEl
+              .css('top', resultsEl.offset().top)
+              .css('left', resultsEl.offset().left)
+              .width(resultsEl.width() + 10)
+              .height(resultsEl.height() + 10);
+        };
+        jQuery(window).on('resize', this.onResize);
+        this.onResize();
+      });
+
+
+      NavigatorApp.addInitializer(function () {
+        var that = this;
+        jQuery('body')
+            .on('mousemove', function (e) {
+              that.processResize(e);
+            })
+            .on('mouseup', function () {
+              that.stopResize();
+            });
+        jQuery('.navigator-resizer').on('mousedown', function (e) {
+          that.startResize(e);
+        });
+
+        var resultsWidth = localStorage.getItem('issuesResultsWidth');
+        if (resultsWidth) {
+          jQuery('.navigator-results').width(+resultsWidth);
+          jQuery('.navigator-side').width(+resultsWidth + 20);
+          this.onResize();
+        }
+      });
+
+
+      NavigatorApp.startResize = function (e) {
+        this.isResize = true;
+        this.originalWidth = jQuery('.navigator-results').width();
+        this.x = e.clientX;
+        jQuery('html').attr('unselectable', 'on').css('user-select', 'none').on('selectstart', false);
+      };
+
+
+      NavigatorApp.processResize = function (e) {
+        if (this.isResize) {
+          var delta = e.clientX - this.x;
+          jQuery('.navigator-results').width(this.originalWidth + delta);
+          jQuery('.navigator-side').width(this.originalWidth + 20 + delta);
+          localStorage.setItem('issuesResultsWidth', jQuery('.navigator-results').width());
+          this.onResize();
+        }
+      };
+
+
+      NavigatorApp.stopResize = function() {
+        if (this.isResize) {
+          jQuery('html').attr('unselectable', 'off').css('user-select', 'text').off('selectstart');
+        }
+        this.isResize = false;
+      };
+
+
+      NavigatorApp.getQuery = function (withoutId) {
+        if (this.filterBarView) {
+          var query = this.filterBarView.getQuery();
+          if (!withoutId && this.favoriteFilter.id) {
+            query['id'] = this.favoriteFilter.id;
+          }
+          return query;
+        } else {
+          return {};
+        }
+      };
+
+
+      NavigatorApp.storeQuery = function (query, sorting) {
+        if (sorting) {
+          _.extend(query, {
+            sort: sorting.sort,
+            asc: '' + sorting.asc
+          });
+        }
+
+        var queryString = _.map(query,function (v, k) {
+          return [k, encodeURIComponent(v)].join('=');
+        }).join('|');
+        this.router.navigate(queryString, { replace: true });
+      };
+
+
+      NavigatorApp.restoreSorting = function (query) {
+        var sort = _.findWhere(query, { key: 'sort' }),
+            asc = _.findWhere(query, { key: 'asc' });
+
+        if (sort && asc) {
+          this.issues.sorting = {
+            sort: sort.value,
+            sortText: jQuery('[data-sort=' + sort.value + ']:first').text(),
+            asc: asc.value === 'true'
+          };
+        }
+      };
+
+
+      NavigatorApp.fetchIssues = function (firstPage) {
+        var query = this.getQuery(),
+            fetchQuery = _.extend({
+              pageIndex: this.issuesPage
+            }, query);
+
+        // SONAR-5086
+        if (fetchQuery['actionPlans'] && fetchQuery['componentRoots']) {
+          delete fetchQuery['componentRoots'];
+        }
+
+        if (this.issues.sorting) {
+          _.extend(fetchQuery, {
+            sort: this.issues.sorting.sort,
+            asc: this.issues.sorting.asc
+          });
+        }
+
+        _.extend(fetchQuery, {
+          hideRules: true
+        });
+
+        if (this.favoriteFilter.id) {
+          query['id'] = this.favoriteFilter.id;
+          fetchQuery['id'] = this.favoriteFilter.id;
+        }
+
+        this.storeQuery(query, this.issues.sorting);
+
+        var that = this;
+        jQuery('.navigator-results').addClass('fetching');
+        if (firstPage) {
+          this.issues.fetch({
+            data: fetchQuery,
+            success: function () {
+              jQuery('.navigator-results').removeClass('fetching');
+              that.issuesView.selectFirst();
+            }
+          });
+          this.detailsRegion.reset();
+        } else {
+          this.issues.fetch({
+            data: fetchQuery,
+            remove: false,
+            success: function () {
+              jQuery('.navigator-results').removeClass('fetching');
+            }
+          });
+        }
+      };
+
+
+      NavigatorApp.fetchFirstPage = function () {
+        this.issuesPage = 1;
+        this.fetchIssues(true);
+      };
+
+
+      NavigatorApp.fetchNextPage = function () {
+        if (this.issuesPage < this.issues.paging.pages) {
+          this.issuesPage++;
+          this.fetchIssues(false);
+        }
+      };
+
+      window.requestMessages().done(function () {
+        NavigatorApp.start();
+      });
+
+    });
diff --git a/server/sonar-web/src/main/js/issues-old/extra.js b/server/sonar-web/src/main/js/issues-old/extra.js
new file mode 100644 (file)
index 0000000..ba1c35a
--- /dev/null
@@ -0,0 +1,673 @@
+define(
+    [
+      'backbone',
+      'backbone.marionette',
+      'navigator/filters/filter-bar',
+      'navigator/filters/base-filters',
+      'navigator/filters/favorite-filters',
+      'navigator/filters/read-only-filters',
+      'component-viewer/main',
+      'templates/issues-old'
+    ],
+    function (Backbone, Marionette, FilterBarView, BaseFilters, FavoriteFiltersModule, ReadOnlyFilterView,
+              ComponentViewer, Templates) {
+
+      var AppState = Backbone.Model.extend({
+
+        defaults: {
+          canManageFilters: false,
+          canBulkChange: false
+        },
+
+
+        url: function () {
+          return baseUrl + '/api/issue_filters/app';
+        }
+
+      });
+
+
+      var Issue = Backbone.Model.extend({
+
+        url: function () {
+          return baseUrl + '/api/issues/show?key=' + this.get('key');
+        },
+
+
+        parse: function (r) {
+          return r.issue ? r.issue : r;
+        }
+
+      });
+
+
+      var Issues = Backbone.Collection.extend({
+        model: Issue,
+
+
+        url: function () {
+          return baseUrl + '/api/issues/search';
+        },
+
+
+        parse: function (r) {
+
+          function find(source, key, keyField) {
+            var searchDict = {};
+            searchDict[keyField || 'key'] = key;
+            return _.findWhere(source, searchDict) || key;
+          }
+
+          this.paging = r.paging;
+          this.maxResultsReached = r.maxResultsReached;
+
+          return r.issues.map(function (issue) {
+            var component = find(r.components, issue.component),
+                project = find(r.projects, issue.project),
+                rule = find(r.rules, issue.rule);
+
+            if (component) {
+              _.extend(issue, {
+                componentLongName: component.longName,
+                componentQualifier: component.qualifier
+              });
+            }
+
+            if (project) {
+              _.extend(issue, {
+                projectLongName: project.longName
+              });
+            }
+
+            if (rule) {
+              _.extend(issue, {
+                ruleName: rule.name
+              });
+            }
+
+            return issue;
+          });
+
+        }
+      });
+
+
+      var FavoriteFilter = Backbone.Model.extend({
+
+        url: function () {
+          return baseUrl + '/api/issue_filters/show/' + this.get('id');
+        },
+
+
+        parse: function (r) {
+          return r.filter ? r.filter : r;
+        }
+      });
+
+
+      var FavoriteFilters = Backbone.Collection.extend({
+        model: FavoriteFilter,
+
+
+        url: function () {
+          return baseUrl + '/api/issue_filters/favorites';
+        },
+
+
+        parse: function (r) {
+          return r.favoriteFilters;
+        }
+      });
+
+
+      var IssueView = Marionette.ItemView.extend({
+        template: Templates['issue-detail'],
+        tagName: 'li',
+
+
+        ui: {
+          component: '.component'
+        },
+
+
+        events: {
+          'click': 'showDetails'
+        },
+
+
+        modelEvents: {
+          'change': 'render'
+        },
+
+
+        showDetails: function () {
+          key.setScope('list');
+          this.options.issuesView.selected = this.$el.parent().children().index(this.$el);
+
+          this.options.issuesView.selectIssue(this.$el, false);
+
+          var that = this,
+              app = this.options.app,
+              settings = localStorage.getItem('componentViewerSettings'),
+              navigatorDetails = jQuery('.navigator-details'),
+              componentViewer = new ComponentViewer({
+                settings: settings,
+                shouldStoreSettings: true,
+                elementToFit: navigatorDetails,
+                component: {
+                  project: this.model.get('project'),
+                  projectLongName: this.model.get('projectLongName')
+                }
+              }),
+              showCallback = function () {
+                navigatorDetails.removeClass('navigator-fetching');
+                app.detailsRegion.show(componentViewer);
+                componentViewer.settings.set('issues', false);
+                componentViewer.open(that.model.get('component'));
+                componentViewer.on('loaded', function() {
+                  componentViewer.off('loaded');
+                  componentViewer.showIssues(false, that.model.toJSON());
+                });
+              };
+
+          navigatorDetails.empty().addClass('navigator-fetching');
+          var issueKey = this.model.get('key');
+          this.model.clear({ silent: true });
+          this.model.set({ key: issueKey }, { silent: true });
+          jQuery.when(this.model.fetch()).done(showCallback);
+        },
+
+
+        serializeData: function () {
+          var projectFilter = this.options.app.filters.findWhere({ property: 'componentRoots' }),
+              singleProject = _.isArray(projectFilter.get('value')) && projectFilter.get('value').length === 1;
+
+          return _.extend({
+            singleProject: singleProject
+          }, this.model.toJSON());
+        }
+      });
+
+
+      var NoIssuesView = Marionette.ItemView.extend({
+        tagName: 'li',
+        className: 'navigator-results-no-results',
+        template: Templates['no-issues']
+      });
+
+
+      var IssuesView = Marionette.CompositeView.extend({
+        template: Templates['issues'],
+        itemViewContainer: '.navigator-results-list',
+        itemView: IssueView,
+        emptyView: NoIssuesView,
+
+
+        initialize: function() {
+          var openIssue = function(el) {
+            el.click();
+          };
+          this.openIssue = _.debounce(openIssue, 300);
+        },
+
+
+        itemViewOptions: function () {
+          return {
+            issuesView: this,
+            app: this.options.app
+          };
+        },
+
+
+        selectIssue: function(el, open) {
+          this.$('.active').removeClass('active');
+          el.addClass('active');
+          if (open) {
+            this.openIssue(el);
+          }
+        },
+
+
+        selectFirst: function() {
+          this.selected = -1;
+          this.selectNext();
+        },
+
+
+        selectNext: function() {
+          if (this.selected < this.collection.length - 1) {
+            this.selected++;
+            var child = this.$(this.itemViewContainer).children().eq(this.selected),
+                container = jQuery('.navigator-results'),
+                containerHeight = container.height(),
+                bottom = child.position().top + child.outerHeight();
+            if (bottom > containerHeight) {
+              container.scrollTop(container.scrollTop() - containerHeight + bottom);
+            }
+            this.selectIssue(child, true);
+          }
+        },
+
+
+        selectPrev: function() {
+          if (this.selected > 0) {
+            this.selected--;
+            var child = this.$(this.itemViewContainer).children().eq(this.selected),
+                container = jQuery('.navigator-results'),
+                top = child.position().top;
+            if (top < 0) {
+              container.scrollTop(container.scrollTop() + top);
+            }
+            this.selectIssue(child, true);
+          }
+        },
+
+
+        onRender: function () {
+          var that = this,
+              $scrollEl = jQuery('.navigator-results'),
+              scrollEl = $scrollEl.get(0),
+              onScroll = function () {
+                if (scrollEl.offsetHeight + scrollEl.scrollTop >= scrollEl.scrollHeight) {
+                  that.options.app.fetchNextPage();
+                }
+              },
+              throttledScroll = _.throttle(onScroll, 300);
+          $scrollEl.off('scroll').on('scroll', throttledScroll);
+          this.bindShortcuts();
+        },
+
+
+        onAfterItemAdded: function () {
+          var showLimitNotes = this.collection.maxResultsReached != null && this.collection.maxResultsReached;
+          jQuery('.navigator').toggleClass('navigator-with-notes', showLimitNotes);
+          jQuery('.navigator-notes').toggle(showLimitNotes);
+        },
+
+
+        close: function () {
+          var scrollEl = jQuery('.navigator-results');
+          scrollEl.off('scroll');
+          Marionette.CollectionView.prototype.close.call(this);
+        },
+
+
+        bindShortcuts: function () {
+          var that = this;
+          key('up', 'list', function() {
+            that.selectPrev();
+          });
+          key('down', 'list', function() {
+            that.selectNext();
+          });
+        }
+
+      });
+
+
+      var IssuesActionsView = Marionette.ItemView.extend({
+        template: Templates['issues-actions'],
+
+
+        collectionEvents: {
+          'sync': 'render'
+        },
+
+
+        events: {
+          'click .navigator-actions-order': 'toggleOrderChoices',
+          'click .navigator-actions-order-choices': 'sort',
+          'click .navigator-actions-bulk': 'bulkChange'
+        },
+
+
+        ui: {
+          orderChoices: '.navigator-actions-order-choices'
+        },
+
+
+        onRender: function () {
+          if (!this.collection.sorting.sortText) {
+            this.collection.sorting.sortText = this.$('[data-sort=' + this.collection.sorting.sort + ']:first').text();
+            this.$('.navigator-actions-ordered-by').text(this.collection.sorting.sortText);
+          }
+        },
+
+
+        toggleOrderChoices: function (e) {
+          e.stopPropagation();
+          this.ui.orderChoices.toggleClass('open');
+          if (this.ui.orderChoices.is('.open')) {
+            var that = this;
+            jQuery('body').on('click.issues_actions', function () {
+              that.ui.orderChoices.removeClass('open');
+            });
+          }
+        },
+
+
+        sort: function (e) {
+          e.stopPropagation();
+          this.ui.orderChoices.removeClass('open');
+          jQuery('body').off('click.issues_actions');
+          var el = jQuery(e.target),
+              sort = el.data('sort'),
+              asc = el.data('asc');
+
+          if (sort != null && asc != null) {
+            this.collection.sorting = {
+              sort: sort,
+              sortText: el.text(),
+              asc: asc
+            };
+            this.options.app.fetchFirstPage();
+          }
+        },
+
+
+        bulkChange: function(e) {
+          e.preventDefault();
+          openModalWindow(jQuery(e.currentTarget).prop('href'), {});
+        },
+
+
+        serializeData: function () {
+          var data = Marionette.ItemView.prototype.serializeData.apply(this, arguments),
+              bulkChangeData = this.options.app.getQuery(true),
+              bulkChangeQuery = _.map(bulkChangeData,function (v, k) {
+                return [k, encodeURIComponent(v)].join('=');
+              }).join('&');
+          return _.extend(data || {}, {
+            paging: this.collection.paging,
+            sorting: this.collection.sorting,
+            maxResultsReached: this.collection.maxResultsReached,
+            appState: window.SS.appState.toJSON(),
+            bulkChangeUrl: baseUrl + '/issues/bulk_change_form',
+            query: bulkChangeQuery
+          });
+        }
+      });
+
+
+
+      var IssuesDetailsFavoriteFilterView = FavoriteFiltersModule.DetailsFavoriteFilterView.extend({
+        template: Templates['issues-details-favorite-filter'],
+
+
+        applyFavorite: function (e) {
+          var id = $j(e.target).data('id'),
+              filter = new FavoriteFilter({ id: id }),
+              app = this.options.filterView.options.app;
+
+          filter.fetch({
+            success: function () {
+              app.state.set('search', false);
+              app.favoriteFilter.clear({ silent: true });
+              app.favoriteFilter.set(filter.toJSON());
+            }
+          });
+
+          this.options.filterView.hideDetails();
+        },
+
+
+        serializeData: function () {
+          return _.extend({}, this.model.toJSON(), {
+            items: _.sortBy(this.model.get('choices'), function(item) {
+              return item.name.toLowerCase();
+            })
+          });
+        }
+      });
+
+
+
+      var IssuesFavoriteFilterView = FavoriteFiltersModule.FavoriteFilterView.extend({
+
+        initialize: function () {
+          BaseFilters.BaseFilterView.prototype.initialize.call(this, {
+            detailsView: IssuesDetailsFavoriteFilterView
+          });
+
+          this.listenTo(window.SS.appState, 'change:favorites', this.updateFavorites);
+        },
+
+
+        updateFavorites: function () {
+          this.model.set('choices', window.SS.appState.get('favorites'));
+          this.render();
+        }
+      });
+
+
+
+      var IssuesFilterBarView = FilterBarView.extend({
+        template: Templates['filter-bar'],
+
+        collectionEvents: {
+          'change:enabled': 'changeEnabled'
+        },
+
+
+        events: {
+          'click .navigator-filter-submit': 'search'
+        },
+
+
+        getQuery: function () {
+          var query = {};
+          this.collection.each(function (filter) {
+            _.extend(query, filter.view.formatValue());
+          });
+          return query;
+        },
+
+
+        onAfterItemAdded: function (itemView) {
+          if (itemView.model.get('type') === FavoriteFiltersModule.FavoriteFilterView ||
+              itemView.model.get('type') === IssuesFavoriteFilterView) {
+            jQuery('.navigator-header').addClass('navigator-header-favorite');
+          }
+        },
+
+
+        addMoreCriteriaFilter: function() {
+          var readOnlyFilters = this.collection.where({ type: ReadOnlyFilterView }),
+              disabledFilters = _.difference(this.collection.where({ enabled: false }), readOnlyFilters);
+          this.moreCriteriaFilter = new BaseFilters.Filter({
+            type: require('navigator/filters/more-criteria-filters').MoreCriteriaFilterView,
+            enabled: true,
+            optional: false,
+            filters: disabledFilters
+          });
+          this.collection.add(this.moreCriteriaFilter);
+        },
+
+
+        changeEnabled: function () {
+          var disabledFilters = _.reject(this.collection.where({ enabled: false }), function (filter) {
+                return filter.get('type') ===
+                       require('navigator/filters/more-criteria-filters').MoreCriteriaFilterView ||
+                       filter.get('type') === ReadOnlyFilterView;
+              });
+
+          if (disabledFilters.length === 0) {
+            this.moreCriteriaFilter.set({ enabled: false }, { silent: true });
+          } else {
+            this.moreCriteriaFilter.set({ enabled: true }, { silent: true });
+          }
+          this.moreCriteriaFilter.set({ filters: disabledFilters }, { silent: true });
+          this.moreCriteriaFilter.trigger('change:filters');
+        },
+
+
+        search: function () {
+          this.$('.navigator-filter-submit').blur();
+          this.options.app.state.set({
+            query: this.options.app.getQuery(),
+            search: true
+          });
+          this.options.app.fetchFirstPage();
+        },
+
+
+        fetchNextPage: function () {
+          this.options.app.fetchNextPage();
+        }
+
+      });
+
+
+      var IssuesHeaderView = Marionette.ItemView.extend({
+        template: Templates['issues-header'],
+
+
+        modelEvents: {
+          'change': 'render'
+        },
+
+
+        events: {
+          'click #issues-new-search': 'newSearch',
+          'click #issues-filter-save-as': 'saveAs',
+          'click #issues-filter-save': 'save',
+          'click #issues-filter-copy': 'copy',
+          'click #issues-filter-edit': 'edit'
+        },
+
+
+        initialize: function (options) {
+          Marionette.ItemView.prototype.initialize.apply(this, arguments);
+          this.listenTo(options.app.state, 'change', this.render);
+        },
+
+
+        newSearch: function () {
+          this.model.clear();
+          this.options.app.router.navigate('resolved=false', { trigger: true, replace: true });
+        },
+
+
+        saveAs: function () {
+          var url = baseUrl + '/issues/save_as_form?' + (Backbone.history.fragment || '').replace(/\|/g, '&');
+          openModalWindow(url, {});
+        },
+
+
+        save: function () {
+          var that = this;
+          url = baseUrl + '/issues/save/' + this.model.id + '?' + (Backbone.history.fragment || '').replace(/\|/g, '&');
+          jQuery.ajax({
+            type: 'POST',
+            url: url
+          }).done(function () {
+                that.options.app.state.set('search', false);
+              });
+        },
+
+
+        copy: function () {
+          var url = baseUrl + '/issues/copy_form/' + this.model.id;
+          openModalWindow(url, {});
+        },
+
+
+        edit: function () {
+          var url = baseUrl + '/issues/edit_form/' + this.model.id;
+          openModalWindow(url, {});
+        },
+
+
+        serializeData: function () {
+          return _.extend({
+            canSave: this.model.id && this.options.app.state.get('search'),
+            appState: window.SS.appState.toJSON(),
+            currentUser: window.SS.currentUser
+          }, this.model.toJSON());
+        }
+
+      });
+
+
+
+      var IssuesRouter = Backbone.Router.extend({
+
+        routes: {
+          '': 'emptyQuery',
+          ':query': 'index'
+        },
+
+
+        initialize: function (options) {
+          this.app = options.app;
+        },
+
+
+        parseQuery: function (query, separator) {
+          return (query || '').split(separator || '|').map(function (t) {
+            var tokens = t.split('=');
+            return {
+              key: tokens[0],
+              value: decodeURIComponent(tokens[1])
+            };
+          });
+        },
+
+
+        emptyQuery: function () {
+          this.navigate('resolved=false', { trigger: true, replace: true });
+        },
+
+
+        index: function (query) {
+          var params = this.parseQuery(query);
+
+          var idObj = _.findWhere(params, { key: 'id' });
+          if (idObj) {
+            var that = this,
+              f = this.app.favoriteFilter;
+            this.app.canSave = false;
+            f.set('id', idObj.value);
+            f.fetch({
+              success: function () {
+                var parsedFilter = that.parseQuery(f.get('query'));
+                params = _.extend({}, params);
+                params = _.extent(params, parsedFilter);
+                that.loadResults(params);
+              }
+            });
+          } else {
+            this.loadResults(params);
+          }
+        },
+
+
+        loadResults: function (params) {
+          this.app.filterBarView.restoreFromQuery(params);
+          this.app.restoreSorting(params);
+          this.app.fetchFirstPage();
+        }
+
+      });
+
+
+      /*
+       * Export public classes
+       */
+
+      return {
+        AppState: AppState,
+        Issue: Issue,
+        Issues: Issues,
+        FavoriteFilter: FavoriteFilter,
+        FavoriteFilters: FavoriteFilters,
+        IssueView: IssueView,
+        IssuesView: IssuesView,
+        IssuesActionsView: IssuesActionsView,
+        IssuesFilterBarView: IssuesFilterBarView,
+        IssuesHeaderView: IssuesHeaderView,
+        IssuesFavoriteFilterView: IssuesFavoriteFilterView,
+        IssuesRouter: IssuesRouter
+      };
+
+    });
diff --git a/server/sonar-web/src/main/js/issues/app.js b/server/sonar-web/src/main/js/issues/app.js
deleted file mode 100644 (file)
index 63b329e..0000000
+++ /dev/null
@@ -1,551 +0,0 @@
-requirejs.config({
-  baseUrl: baseUrl + '/js',
-
-  paths: {
-    'backbone': 'third-party/backbone',
-    'backbone.marionette': 'third-party/backbone.marionette',
-    'handlebars': 'third-party/handlebars'
-  },
-
-  shim: {
-    'backbone.marionette': {
-      deps: ['backbone'],
-      exports: 'Marionette'
-    },
-    'backbone': {
-      exports: 'Backbone'
-    },
-    'handlebars': {
-      exports: 'Handlebars'
-    }
-  }
-
-});
-
-requirejs(
-    [
-      'backbone', 'backbone.marionette', 'handlebars',
-      'issues/extra',
-      'navigator/filters/filter-bar',
-      'navigator/filters/base-filters',
-      'navigator/filters/checkbox-filters',
-      'navigator/filters/choice-filters',
-      'navigator/filters/ajax-select-filters',
-      'navigator/filters/favorite-filters',
-      'navigator/filters/range-filters',
-      'navigator/filters/context-filters',
-      'navigator/filters/read-only-filters',
-      'navigator/filters/action-plan-filters',
-      'navigator/filters/rule-filters',
-
-      'common/handlebars-extensions'
-    ],
-    function (Backbone, Marionette, Handlebars, Extra, FilterBar, BaseFilters, CheckboxFilterView,
-              ChoiceFilters, AjaxSelectFilters, FavoriteFilters, RangeFilters, ContextFilterView,
-              ReadOnlyFilterView, ActionPlanFilterView, RuleFilterView) {
-      Handlebars.registerPartial('detailInnerTemplate', jQuery('#issue-detail-inner-template').html());
-
-      var NavigatorApp = new Marionette.Application();
-
-
-      NavigatorApp.addRegions({
-        headerRegion: '.navigator-header',
-        filtersRegion: '.navigator-filters',
-        resultsRegion: '.navigator-results',
-        actionsRegion: '.navigator-actions',
-        detailsRegion: '.navigator-details'
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        jQuery('html').addClass('navigator-page issues-page');
-
-        this.appState = new Extra.AppState();
-        window.SS.appState = this.appState;
-
-        this.state = new Backbone.Model({
-          query: ''
-        });
-
-        this.issues = new Extra.Issues();
-        this.issues.sorting = {
-          sort: 'UPDATE_DATE',
-          asc: false
-        };
-        this.issuesPage = 1;
-
-        this.filters = new BaseFilters.Filters();
-
-        this.favoriteFilter = new Extra.FavoriteFilter();
-        this.issuesHeaderView = new Extra.IssuesHeaderView({
-          app: this,
-          model: this.favoriteFilter
-        });
-        this.headerRegion.show(this.issuesHeaderView);
-
-        this.issuesView = new Extra.IssuesView({
-          app: this,
-          collection: this.issues
-        });
-        this.resultsRegion.show(this.issuesView);
-
-        this.issuesActionsView = new Extra.IssuesActionsView({
-          app: this,
-          collection: this.issues
-        });
-        this.actionsRegion.show(this.issuesActionsView);
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        this.projectFilter = new BaseFilters.Filter({
-          name: window.SS.phrases.project,
-          property: 'componentRoots',
-          type: AjaxSelectFilters.ProjectFilterView,
-          enabled: true,
-          optional: false
-        });
-        this.filters.add(this.projectFilter);
-
-        this.assigneeChoices = {
-          '!assigned': window.SS.phrases.unassigned
-        };
-        this.reporterChoices = {};
-        if (window.SS.currentUser) {
-          this.assigneeChoices[window.SS.currentUser] = window.SS.currentUserName + ' (' + window.SS.currentUser + ')';
-          this.reporterChoices[window.SS.currentUser] = window.SS.currentUserName + ' (' + window.SS.currentUser + ')';
-        }
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        this.filters.add([
-          new BaseFilters.Filter({
-            name: window.SS.phrases.severity,
-            property: 'severities',
-            type: ChoiceFilters.ChoiceFilterView,
-            enabled: true,
-            optional: false,
-            choices: {
-              'BLOCKER': window.SS.phrases.severities.BLOCKER,
-              'CRITICAL': window.SS.phrases.severities.CRITICAL,
-              'MAJOR': window.SS.phrases.severities.MAJOR,
-              'MINOR': window.SS.phrases.severities.MINOR,
-              'INFO': window.SS.phrases.severities.INFO
-            },
-            choiceIcons: {
-              'BLOCKER': 'severity-blocker',
-              'CRITICAL': 'severity-critical',
-              'MAJOR': 'severity-major',
-              'MINOR': 'severity-minor',
-              'INFO': 'severity-info'
-            }
-          })
-        ]);
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        this.filters.add([
-          new BaseFilters.Filter({
-            name: window.SS.phrases.status,
-            property: 'statuses',
-            type: ChoiceFilters.ChoiceFilterView,
-            enabled: true,
-            optional: false,
-            choices: {
-              'OPEN': window.SS.phrases.statuses.OPEN,
-              'CONFIRMED': window.SS.phrases.statuses.CONFIRMED,
-              'REOPENED': window.SS.phrases.statuses.REOPENED,
-              'RESOLVED': window.SS.phrases.statuses.RESOLVED,
-              'CLOSED': window.SS.phrases.statuses.CLOSED
-            },
-            choiceIcons: {
-              'OPEN': 'status-open',
-              'CONFIRMED': 'status-confirmed',
-              'REOPENED': 'status-reopened',
-              'RESOLVED': 'status-resolved',
-              'CLOSED': 'status-closed'
-            }
-          })
-        ]);
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        this.filters.add([
-          new BaseFilters.Filter({
-            name: window.SS.phrases.assignee,
-            property: 'assignees',
-            type: AjaxSelectFilters.AssigneeFilterView,
-            enabled: true,
-            optional: false,
-            choices: this.assigneeChoices
-          })
-        ]);
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        this.filters.add([
-          new BaseFilters.Filter({
-            name: window.SS.phrases.resolution,
-            property: 'resolutions',
-            type: ChoiceFilters.ChoiceFilterView,
-            enabled: true,
-            optional: false,
-            choices: {
-              '!resolved=true': window.SS.phrases.resolutions.RESOLVED,
-              '!resolved=false': window.SS.phrases.resolutions.UNRESOLVED,
-              'FALSE-POSITIVE': window.SS.phrases.resolutions['FALSE-POSITIVE'],
-              'FIXED': window.SS.phrases.resolutions.FIXED,
-              'REMOVED': window.SS.phrases.resolutions.REMOVED
-            }
-          })
-        ]);
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        this.filters.add([
-          new BaseFilters.Filter({
-            name: window.SS.phrases.actionPlan,
-            property: 'actionPlans',
-            type: ActionPlanFilterView,
-            enabled: false,
-            optional: true,
-            projectFilter: this.projectFilter,
-            choices: {
-              '!planned': window.SS.phrases.unplanned
-            }
-          })
-        ]);
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        this.filters.add([
-          new BaseFilters.Filter({
-            name: window.SS.phrases.created,
-            propertyFrom: 'createdAfter',
-            propertyTo: 'createdBefore',
-            type: RangeFilters.DateRangeFilterView,
-            enabled: false,
-            optional: true
-          })
-        ]);
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        this.filters.add([
-          new BaseFilters.Filter({
-            name: window.SS.phrases.createdAt,
-            property: 'createdAt',
-            type: ReadOnlyFilterView,
-            enabled: false,
-            optional: true,
-            format: function(value) { return moment(value).format('YYYY-MM-DD HH:mm'); }
-          })
-        ]);
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        this.filters.add([
-          new BaseFilters.Filter({
-            name: window.SS.phrases.language,
-            property: 'languages',
-            type: ChoiceFilters.ChoiceFilterView,
-            enabled: false,
-            optional: true,
-            choices: window.SS.languages
-          })
-        ]);
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        this.filters.add([
-          new BaseFilters.Filter({
-            name: window.SS.phrases.reporter,
-            property: 'reporters',
-            type: AjaxSelectFilters.ReporterFilterView,
-            enabled: false,
-            optional: true,
-            choices: this.reporterChoices
-          })
-        ]);
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        this.filters.add([
-          new BaseFilters.Filter({
-            name: window.SS.phrases.rule,
-            property: 'rules',
-            type: RuleFilterView,
-            enabled: false,
-            optional: true
-          })
-        ]);
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        this.filterBarView = new Extra.IssuesFilterBarView({
-          app: this,
-          collection: this.filters,
-          extra: {
-            sort: '',
-            asc: false
-          }
-        });
-
-        this.filtersRegion.show(this.filterBarView);
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        var app = this;
-
-        jQuery.when(this.appState.fetch()).done(function () {
-
-          if (app.appState.get('favorites')) {
-            app.filters.unshift(
-                new BaseFilters.Filter({
-                  type: Extra.IssuesFavoriteFilterView,
-                  enabled: true,
-                  optional: false,
-                  choices: app.appState.get('favorites'),
-                  manageUrl: '/issues/manage'
-                })
-            );
-          }
-
-          app.router = new Extra.IssuesRouter({
-            app: app
-          });
-          Backbone.history.start();
-
-          app.favoriteFilter.on('change:query', function (model, query) {
-            app.router.navigate(query, { trigger: true, replace: true });
-          });
-        });
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        var app = this;
-
-        window.onBulkIssues = function () {
-          app.fetchFirstPage();
-          jQuery('#modal').dialog('close');
-        };
-
-        window.onSaveAs = window.onCopy = window.onEdit = function (id) {
-          jQuery('#modal').dialog('close');
-          app.appState.fetch();
-
-          var filter = new Extra.FavoriteFilter({ id: id });
-          filter.fetch({
-            success: function () {
-              app.state.set('search', false);
-              app.favoriteFilter.set(filter.toJSON());
-              app.fetchFirstPage();
-            }
-          });
-        };
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        this.onResize = function() {
-          var footerEl = jQuery('#footer'),
-              footerHeight = footerEl.outerHeight(true);
-
-          var resultsEl = jQuery('.navigator-results'),
-              resultsHeight = jQuery(window).height() - resultsEl.offset().top -
-                  parseInt(resultsEl.css('margin-bottom'), 10) - footerHeight;
-          resultsEl.height(resultsHeight);
-
-          var detailsEl = jQuery('.navigator-details'),
-              detailsWidth = jQuery(window).width() - detailsEl.offset().left -
-                  parseInt(detailsEl.css('margin-right'), 10) - 20,
-              detailsHeight = jQuery(window).height() - detailsEl.offset().top -
-                  parseInt(detailsEl.css('margin-bottom'), 10) - footerHeight;
-          detailsEl.width(detailsWidth).height(detailsHeight);
-
-          var resultsLoadingEl = jQuery('.navigator-results-loader');
-          resultsLoadingEl
-              .css('top', resultsEl.offset().top)
-              .css('left', resultsEl.offset().left)
-              .width(resultsEl.width() + 10)
-              .height(resultsEl.height() + 10);
-        };
-        jQuery(window).on('resize', this.onResize);
-        this.onResize();
-      });
-
-
-      NavigatorApp.addInitializer(function () {
-        var that = this;
-        jQuery('body')
-            .on('mousemove', function (e) {
-              that.processResize(e);
-            })
-            .on('mouseup', function () {
-              that.stopResize();
-            });
-        jQuery('.navigator-resizer').on('mousedown', function (e) {
-          that.startResize(e);
-        });
-
-        var resultsWidth = localStorage.getItem('issuesResultsWidth');
-        if (resultsWidth) {
-          jQuery('.navigator-results').width(+resultsWidth);
-          jQuery('.navigator-side').width(+resultsWidth + 20);
-          this.onResize();
-        }
-      });
-
-
-      NavigatorApp.startResize = function (e) {
-        this.isResize = true;
-        this.originalWidth = jQuery('.navigator-results').width();
-        this.x = e.clientX;
-        jQuery('html').attr('unselectable', 'on').css('user-select', 'none').on('selectstart', false);
-      };
-
-
-      NavigatorApp.processResize = function (e) {
-        if (this.isResize) {
-          var delta = e.clientX - this.x;
-          jQuery('.navigator-results').width(this.originalWidth + delta);
-          jQuery('.navigator-side').width(this.originalWidth + 20 + delta);
-          localStorage.setItem('issuesResultsWidth', jQuery('.navigator-results').width());
-          this.onResize();
-        }
-      };
-
-
-      NavigatorApp.stopResize = function() {
-        if (this.isResize) {
-          jQuery('html').attr('unselectable', 'off').css('user-select', 'text').off('selectstart');
-        }
-        this.isResize = false;
-      };
-
-
-      NavigatorApp.getQuery = function (withoutId) {
-        if (this.filterBarView) {
-          var query = this.filterBarView.getQuery();
-          if (!withoutId && this.favoriteFilter.id) {
-            query['id'] = this.favoriteFilter.id;
-          }
-          return query;
-        } else {
-          return {};
-        }
-      };
-
-
-      NavigatorApp.storeQuery = function (query, sorting) {
-        if (sorting) {
-          _.extend(query, {
-            sort: sorting.sort,
-            asc: '' + sorting.asc
-          });
-        }
-
-        var queryString = _.map(query,function (v, k) {
-          return [k, encodeURIComponent(v)].join('=');
-        }).join('|');
-        this.router.navigate(queryString, { replace: true });
-      };
-
-
-      NavigatorApp.restoreSorting = function (query) {
-        var sort = _.findWhere(query, { key: 'sort' }),
-            asc = _.findWhere(query, { key: 'asc' });
-
-        if (sort && asc) {
-          this.issues.sorting = {
-            sort: sort.value,
-            sortText: jQuery('[data-sort=' + sort.value + ']:first').text(),
-            asc: asc.value === 'true'
-          };
-        }
-      };
-
-
-      NavigatorApp.fetchIssues = function (firstPage) {
-        var query = this.getQuery(),
-            fetchQuery = _.extend({
-              pageIndex: this.issuesPage
-            }, query);
-
-        // SONAR-5086
-        if (fetchQuery['actionPlans'] && fetchQuery['componentRoots']) {
-          delete fetchQuery['componentRoots'];
-        }
-
-        if (this.issues.sorting) {
-          _.extend(fetchQuery, {
-            sort: this.issues.sorting.sort,
-            asc: this.issues.sorting.asc
-          });
-        }
-
-        _.extend(fetchQuery, {
-          hideRules: true
-        });
-
-        if (this.favoriteFilter.id) {
-          query['id'] = this.favoriteFilter.id;
-          fetchQuery['id'] = this.favoriteFilter.id;
-        }
-
-        this.storeQuery(query, this.issues.sorting);
-
-        var that = this;
-        jQuery('.navigator-results').addClass('fetching');
-        if (firstPage) {
-          this.issues.fetch({
-            data: fetchQuery,
-            success: function () {
-              jQuery('.navigator-results').removeClass('fetching');
-              that.issuesView.selectFirst();
-            }
-          });
-          this.detailsRegion.reset();
-        } else {
-          this.issues.fetch({
-            data: fetchQuery,
-            remove: false,
-            success: function () {
-              jQuery('.navigator-results').removeClass('fetching');
-            }
-          });
-        }
-      };
-
-
-      NavigatorApp.fetchFirstPage = function () {
-        this.issuesPage = 1;
-        this.fetchIssues(true);
-      };
-
-
-      NavigatorApp.fetchNextPage = function () {
-        if (this.issuesPage < this.issues.paging.pages) {
-          this.issuesPage++;
-          this.fetchIssues(false);
-        }
-      };
-
-      window.requestMessages().done(function () {
-        NavigatorApp.start();
-      });
-
-    });
diff --git a/server/sonar-web/src/main/js/issues/extra.js b/server/sonar-web/src/main/js/issues/extra.js
deleted file mode 100644 (file)
index 5aa0662..0000000
+++ /dev/null
@@ -1,673 +0,0 @@
-define(
-    [
-      'backbone',
-      'backbone.marionette',
-      'navigator/filters/filter-bar',
-      'navigator/filters/base-filters',
-      'navigator/filters/favorite-filters',
-      'navigator/filters/read-only-filters',
-      'component-viewer/main',
-      'templates/issues'
-    ],
-    function (Backbone, Marionette, FilterBarView, BaseFilters, FavoriteFiltersModule, ReadOnlyFilterView,
-              ComponentViewer, Templates) {
-
-      var AppState = Backbone.Model.extend({
-
-        defaults: {
-          canManageFilters: false,
-          canBulkChange: false
-        },
-
-
-        url: function () {
-          return baseUrl + '/api/issue_filters/app';
-        }
-
-      });
-
-
-      var Issue = Backbone.Model.extend({
-
-        url: function () {
-          return baseUrl + '/api/issues/show?key=' + this.get('key');
-        },
-
-
-        parse: function (r) {
-          return r.issue ? r.issue : r;
-        }
-
-      });
-
-
-      var Issues = Backbone.Collection.extend({
-        model: Issue,
-
-
-        url: function () {
-          return baseUrl + '/api/issues/search';
-        },
-
-
-        parse: function (r) {
-
-          function find(source, key, keyField) {
-            var searchDict = {};
-            searchDict[keyField || 'key'] = key;
-            return _.findWhere(source, searchDict) || key;
-          }
-
-          this.paging = r.paging;
-          this.maxResultsReached = r.maxResultsReached;
-
-          return r.issues.map(function (issue) {
-            var component = find(r.components, issue.component),
-                project = find(r.projects, issue.project),
-                rule = find(r.rules, issue.rule);
-
-            if (component) {
-              _.extend(issue, {
-                componentLongName: component.longName,
-                componentQualifier: component.qualifier
-              });
-            }
-
-            if (project) {
-              _.extend(issue, {
-                projectLongName: project.longName
-              });
-            }
-
-            if (rule) {
-              _.extend(issue, {
-                ruleName: rule.name
-              });
-            }
-
-            return issue;
-          });
-
-        }
-      });
-
-
-      var FavoriteFilter = Backbone.Model.extend({
-
-        url: function () {
-          return baseUrl + '/api/issue_filters/show/' + this.get('id');
-        },
-
-
-        parse: function (r) {
-          return r.filter ? r.filter : r;
-        }
-      });
-
-
-      var FavoriteFilters = Backbone.Collection.extend({
-        model: FavoriteFilter,
-
-
-        url: function () {
-          return baseUrl + '/api/issue_filters/favorites';
-        },
-
-
-        parse: function (r) {
-          return r.favoriteFilters;
-        }
-      });
-
-
-      var IssueView = Marionette.ItemView.extend({
-        template: Templates['issue-detail'],
-        tagName: 'li',
-
-
-        ui: {
-          component: '.component'
-        },
-
-
-        events: {
-          'click': 'showDetails'
-        },
-
-
-        modelEvents: {
-          'change': 'render'
-        },
-
-
-        showDetails: function () {
-          key.setScope('list');
-          this.options.issuesView.selected = this.$el.parent().children().index(this.$el);
-
-          this.options.issuesView.selectIssue(this.$el, false);
-
-          var that = this,
-              app = this.options.app,
-              settings = localStorage.getItem('componentViewerSettings'),
-              navigatorDetails = jQuery('.navigator-details'),
-              componentViewer = new ComponentViewer({
-                settings: settings,
-                shouldStoreSettings: true,
-                elementToFit: navigatorDetails,
-                component: {
-                  project: this.model.get('project'),
-                  projectLongName: this.model.get('projectLongName')
-                }
-              }),
-              showCallback = function () {
-                navigatorDetails.removeClass('navigator-fetching');
-                app.detailsRegion.show(componentViewer);
-                componentViewer.settings.set('issues', false);
-                componentViewer.open(that.model.get('component'));
-                componentViewer.on('loaded', function() {
-                  componentViewer.off('loaded');
-                  componentViewer.showIssues(false, that.model.toJSON());
-                });
-              };
-
-          navigatorDetails.empty().addClass('navigator-fetching');
-          var issueKey = this.model.get('key');
-          this.model.clear({ silent: true });
-          this.model.set({ key: issueKey }, { silent: true });
-          jQuery.when(this.model.fetch()).done(showCallback);
-        },
-
-
-        serializeData: function () {
-          var projectFilter = this.options.app.filters.findWhere({ property: 'componentRoots' }),
-              singleProject = _.isArray(projectFilter.get('value')) && projectFilter.get('value').length === 1;
-
-          return _.extend({
-            singleProject: singleProject
-          }, this.model.toJSON());
-        }
-      });
-
-
-      var NoIssuesView = Marionette.ItemView.extend({
-        tagName: 'li',
-        className: 'navigator-results-no-results',
-        template: Templates['no-issues']
-      });
-
-
-      var IssuesView = Marionette.CompositeView.extend({
-        template: Templates['issues'],
-        itemViewContainer: '.navigator-results-list',
-        itemView: IssueView,
-        emptyView: NoIssuesView,
-
-
-        initialize: function() {
-          var openIssue = function(el) {
-            el.click();
-          };
-          this.openIssue = _.debounce(openIssue, 300);
-        },
-
-
-        itemViewOptions: function () {
-          return {
-            issuesView: this,
-            app: this.options.app
-          };
-        },
-
-
-        selectIssue: function(el, open) {
-          this.$('.active').removeClass('active');
-          el.addClass('active');
-          if (open) {
-            this.openIssue(el);
-          }
-        },
-
-
-        selectFirst: function() {
-          this.selected = -1;
-          this.selectNext();
-        },
-
-
-        selectNext: function() {
-          if (this.selected < this.collection.length - 1) {
-            this.selected++;
-            var child = this.$(this.itemViewContainer).children().eq(this.selected),
-                container = jQuery('.navigator-results'),
-                containerHeight = container.height(),
-                bottom = child.position().top + child.outerHeight();
-            if (bottom > containerHeight) {
-              container.scrollTop(container.scrollTop() - containerHeight + bottom);
-            }
-            this.selectIssue(child, true);
-          }
-        },
-
-
-        selectPrev: function() {
-          if (this.selected > 0) {
-            this.selected--;
-            var child = this.$(this.itemViewContainer).children().eq(this.selected),
-                container = jQuery('.navigator-results'),
-                top = child.position().top;
-            if (top < 0) {
-              container.scrollTop(container.scrollTop() + top);
-            }
-            this.selectIssue(child, true);
-          }
-        },
-
-
-        onRender: function () {
-          var that = this,
-              $scrollEl = jQuery('.navigator-results'),
-              scrollEl = $scrollEl.get(0),
-              onScroll = function () {
-                if (scrollEl.offsetHeight + scrollEl.scrollTop >= scrollEl.scrollHeight) {
-                  that.options.app.fetchNextPage();
-                }
-              },
-              throttledScroll = _.throttle(onScroll, 300);
-          $scrollEl.off('scroll').on('scroll', throttledScroll);
-          this.bindShortcuts();
-        },
-
-
-        onAfterItemAdded: function () {
-          var showLimitNotes = this.collection.maxResultsReached != null && this.collection.maxResultsReached;
-          jQuery('.navigator').toggleClass('navigator-with-notes', showLimitNotes);
-          jQuery('.navigator-notes').toggle(showLimitNotes);
-        },
-
-
-        close: function () {
-          var scrollEl = jQuery('.navigator-results');
-          scrollEl.off('scroll');
-          Marionette.CollectionView.prototype.close.call(this);
-        },
-
-
-        bindShortcuts: function () {
-          var that = this;
-          key('up', 'list', function() {
-            that.selectPrev();
-          });
-          key('down', 'list', function() {
-            that.selectNext();
-          });
-        }
-
-      });
-
-
-      var IssuesActionsView = Marionette.ItemView.extend({
-        template: Templates['issues-actions'],
-
-
-        collectionEvents: {
-          'sync': 'render'
-        },
-
-
-        events: {
-          'click .navigator-actions-order': 'toggleOrderChoices',
-          'click .navigator-actions-order-choices': 'sort',
-          'click .navigator-actions-bulk': 'bulkChange'
-        },
-
-
-        ui: {
-          orderChoices: '.navigator-actions-order-choices'
-        },
-
-
-        onRender: function () {
-          if (!this.collection.sorting.sortText) {
-            this.collection.sorting.sortText = this.$('[data-sort=' + this.collection.sorting.sort + ']:first').text();
-            this.$('.navigator-actions-ordered-by').text(this.collection.sorting.sortText);
-          }
-        },
-
-
-        toggleOrderChoices: function (e) {
-          e.stopPropagation();
-          this.ui.orderChoices.toggleClass('open');
-          if (this.ui.orderChoices.is('.open')) {
-            var that = this;
-            jQuery('body').on('click.issues_actions', function () {
-              that.ui.orderChoices.removeClass('open');
-            });
-          }
-        },
-
-
-        sort: function (e) {
-          e.stopPropagation();
-          this.ui.orderChoices.removeClass('open');
-          jQuery('body').off('click.issues_actions');
-          var el = jQuery(e.target),
-              sort = el.data('sort'),
-              asc = el.data('asc');
-
-          if (sort != null && asc != null) {
-            this.collection.sorting = {
-              sort: sort,
-              sortText: el.text(),
-              asc: asc
-            };
-            this.options.app.fetchFirstPage();
-          }
-        },
-
-
-        bulkChange: function(e) {
-          e.preventDefault();
-          openModalWindow(jQuery(e.currentTarget).prop('href'), {});
-        },
-
-
-        serializeData: function () {
-          var data = Marionette.ItemView.prototype.serializeData.apply(this, arguments),
-              bulkChangeData = this.options.app.getQuery(true),
-              bulkChangeQuery = _.map(bulkChangeData,function (v, k) {
-                return [k, encodeURIComponent(v)].join('=');
-              }).join('&');
-          return _.extend(data || {}, {
-            paging: this.collection.paging,
-            sorting: this.collection.sorting,
-            maxResultsReached: this.collection.maxResultsReached,
-            appState: window.SS.appState.toJSON(),
-            bulkChangeUrl: baseUrl + '/issues/bulk_change_form',
-            query: bulkChangeQuery
-          });
-        }
-      });
-
-
-
-      var IssuesDetailsFavoriteFilterView = FavoriteFiltersModule.DetailsFavoriteFilterView.extend({
-        template: Templates['issues-details-favorite-filter'],
-
-
-        applyFavorite: function (e) {
-          var id = $j(e.target).data('id'),
-              filter = new FavoriteFilter({ id: id }),
-              app = this.options.filterView.options.app;
-
-          filter.fetch({
-            success: function () {
-              app.state.set('search', false);
-              app.favoriteFilter.clear({ silent: true });
-              app.favoriteFilter.set(filter.toJSON());
-            }
-          });
-
-          this.options.filterView.hideDetails();
-        },
-
-
-        serializeData: function () {
-          return _.extend({}, this.model.toJSON(), {
-            items: _.sortBy(this.model.get('choices'), function(item) {
-              return item.name.toLowerCase();
-            })
-          });
-        }
-      });
-
-
-
-      var IssuesFavoriteFilterView = FavoriteFiltersModule.FavoriteFilterView.extend({
-
-        initialize: function () {
-          BaseFilters.BaseFilterView.prototype.initialize.call(this, {
-            detailsView: IssuesDetailsFavoriteFilterView
-          });
-
-          this.listenTo(window.SS.appState, 'change:favorites', this.updateFavorites);
-        },
-
-
-        updateFavorites: function () {
-          this.model.set('choices', window.SS.appState.get('favorites'));
-          this.render();
-        }
-      });
-
-
-
-      var IssuesFilterBarView = FilterBarView.extend({
-        template: Templates['filter-bar'],
-
-        collectionEvents: {
-          'change:enabled': 'changeEnabled'
-        },
-
-
-        events: {
-          'click .navigator-filter-submit': 'search'
-        },
-
-
-        getQuery: function () {
-          var query = {};
-          this.collection.each(function (filter) {
-            _.extend(query, filter.view.formatValue());
-          });
-          return query;
-        },
-
-
-        onAfterItemAdded: function (itemView) {
-          if (itemView.model.get('type') === FavoriteFiltersModule.FavoriteFilterView ||
-              itemView.model.get('type') === IssuesFavoriteFilterView) {
-            jQuery('.navigator-header').addClass('navigator-header-favorite');
-          }
-        },
-
-
-        addMoreCriteriaFilter: function() {
-          var readOnlyFilters = this.collection.where({ type: ReadOnlyFilterView }),
-              disabledFilters = _.difference(this.collection.where({ enabled: false }), readOnlyFilters);
-          this.moreCriteriaFilter = new BaseFilters.Filter({
-            type: require('navigator/filters/more-criteria-filters').MoreCriteriaFilterView,
-            enabled: true,
-            optional: false,
-            filters: disabledFilters
-          });
-          this.collection.add(this.moreCriteriaFilter);
-        },
-
-
-        changeEnabled: function () {
-          var disabledFilters = _.reject(this.collection.where({ enabled: false }), function (filter) {
-                return filter.get('type') ===
-                       require('navigator/filters/more-criteria-filters').MoreCriteriaFilterView ||
-                       filter.get('type') === ReadOnlyFilterView;
-              });
-
-          if (disabledFilters.length === 0) {
-            this.moreCriteriaFilter.set({ enabled: false }, { silent: true });
-          } else {
-            this.moreCriteriaFilter.set({ enabled: true }, { silent: true });
-          }
-          this.moreCriteriaFilter.set({ filters: disabledFilters }, { silent: true });
-          this.moreCriteriaFilter.trigger('change:filters');
-        },
-
-
-        search: function () {
-          this.$('.navigator-filter-submit').blur();
-          this.options.app.state.set({
-            query: this.options.app.getQuery(),
-            search: true
-          });
-          this.options.app.fetchFirstPage();
-        },
-
-
-        fetchNextPage: function () {
-          this.options.app.fetchNextPage();
-        }
-
-      });
-
-
-      var IssuesHeaderView = Marionette.ItemView.extend({
-        template: Templates['issues-header'],
-
-
-        modelEvents: {
-          'change': 'render'
-        },
-
-
-        events: {
-          'click #issues-new-search': 'newSearch',
-          'click #issues-filter-save-as': 'saveAs',
-          'click #issues-filter-save': 'save',
-          'click #issues-filter-copy': 'copy',
-          'click #issues-filter-edit': 'edit'
-        },
-
-
-        initialize: function (options) {
-          Marionette.ItemView.prototype.initialize.apply(this, arguments);
-          this.listenTo(options.app.state, 'change', this.render);
-        },
-
-
-        newSearch: function () {
-          this.model.clear();
-          this.options.app.router.navigate('resolved=false', { trigger: true, replace: true });
-        },
-
-
-        saveAs: function () {
-          var url = baseUrl + '/issues/save_as_form?' + (Backbone.history.fragment || '').replace(/\|/g, '&');
-          openModalWindow(url, {});
-        },
-
-
-        save: function () {
-          var that = this;
-          url = baseUrl + '/issues/save/' + this.model.id + '?' + (Backbone.history.fragment || '').replace(/\|/g, '&');
-          jQuery.ajax({
-            type: 'POST',
-            url: url
-          }).done(function () {
-                that.options.app.state.set('search', false);
-              });
-        },
-
-
-        copy: function () {
-          var url = baseUrl + '/issues/copy_form/' + this.model.id;
-          openModalWindow(url, {});
-        },
-
-
-        edit: function () {
-          var url = baseUrl + '/issues/edit_form/' + this.model.id;
-          openModalWindow(url, {});
-        },
-
-
-        serializeData: function () {
-          return _.extend({
-            canSave: this.model.id && this.options.app.state.get('search'),
-            appState: window.SS.appState.toJSON(),
-            currentUser: window.SS.currentUser
-          }, this.model.toJSON());
-        }
-
-      });
-
-
-
-      var IssuesRouter = Backbone.Router.extend({
-
-        routes: {
-          '': 'emptyQuery',
-          ':query': 'index'
-        },
-
-
-        initialize: function (options) {
-          this.app = options.app;
-        },
-
-
-        parseQuery: function (query, separator) {
-          return (query || '').split(separator || '|').map(function (t) {
-            var tokens = t.split('=');
-            return {
-              key: tokens[0],
-              value: decodeURIComponent(tokens[1])
-            };
-          });
-        },
-
-
-        emptyQuery: function () {
-          this.navigate('resolved=false', { trigger: true, replace: true });
-        },
-
-
-        index: function (query) {
-          var params = this.parseQuery(query);
-
-          var idObj = _.findWhere(params, { key: 'id' });
-          if (idObj) {
-            var that = this,
-              f = this.app.favoriteFilter;
-            this.app.canSave = false;
-            f.set('id', idObj.value);
-            f.fetch({
-              success: function () {
-                var parsedFilter = that.parseQuery(f.get('query'));
-                params = _.extend({}, params);
-                params = _.extent(params, parsedFilter);
-                that.loadResults(params);
-              }
-            });
-          } else {
-            this.loadResults(params);
-          }
-        },
-
-
-        loadResults: function (params) {
-          this.app.filterBarView.restoreFromQuery(params);
-          this.app.restoreSorting(params);
-          this.app.fetchFirstPage();
-        }
-
-      });
-
-
-      /*
-       * Export public classes
-       */
-
-      return {
-        AppState: AppState,
-        Issue: Issue,
-        Issues: Issues,
-        FavoriteFilter: FavoriteFilter,
-        FavoriteFilters: FavoriteFilters,
-        IssueView: IssueView,
-        IssuesView: IssuesView,
-        IssuesActionsView: IssuesActionsView,
-        IssuesFilterBarView: IssuesFilterBarView,
-        IssuesHeaderView: IssuesHeaderView,
-        IssuesFavoriteFilterView: IssuesFavoriteFilterView,
-        IssuesRouter: IssuesRouter
-      };
-
-    });
index e11e0e1c402770017d43518149a8dcbe2746dc26..c53557a89c5b7439f19953788849e4988ccdd8f8 100644 (file)
@@ -1,49 +1,36 @@
 @import (reference) 'variables';
 @import (reference) 'mixins';
 
-.component-viewer-source {
-
-  .code pre {
-    padding: 0;
-    font-family: 'Source Code Pro', monospace;
-    font-size: 12px;
-    line-height: 16px;
-  }
-
-  /* constants */
-  .code .c {
-    font-style: normal;
-  }
-
-  /* javadoc */
-  .code .j {
-    font-style: normal;
-  }
-
-  /* keyword */
-  .code .k {
-    color: saturate(@darkBlue, 50%);
-    font-weight: 600;
-  }
+.code pre {
+  padding: 0;
+  font-family: 'Source Code Pro', monospace;
+  font-size: 12px;
+  line-height: 16px;
+}
 
-  /* string */
-  .code .s {
-    color: saturate(@red, 0%);
-    font-weight: normal;
-  }
+/* constants */
+.code .c {
+  font-style: normal;
+}
 
-  /* preprocessing directive */
-  .code .p {
-    font-weight: normal;
-  }
+/* javadoc */
+.code .j {
+  font-style: normal;
+}
 
-  .sym {
-    @symColor: darken(saturate(@green, 10%), 15%);
-    border-bottom: 1px dotted @symColor;
-    .trans(color);
+/* keyword */
+.code .k {
+  color: saturate(@darkBlue, 50%);
+  font-weight: 600;
+}
 
-    &:hover,
-    &.highlighted { color: @symColor; }
-  }
+/* string */
+.code .s {
+  color: saturate(@red, 0%);
+  font-weight: normal;
+}
 
+/* preprocessing directive */
+.code .p {
+  font-weight: normal;
 }
index a24fcd828ab32651ae3e81caee1e48fb675ef5e7..2533fc458716f2a6fdf2db2d4ce696faa4983447 100644 (file)
   &:hover { background-color: @barBorderColor; }
 }
 
-
 .component-viewer-source {
   position: relative;
   float: left;
     overflow: auto;
   }
 
-  .code {
-    width: 100%;
-    border: 1px solid @barBorderColor;
-  }
-
   .code th {
-    height: 30px;
-    .box-sizing(border-box);
-    background-color: @barBackgroundColor;
-
-    &.stat {
-      padding-top: 4px;
-      padding-bottom: 4px;
-      border-left: none;
-      border-right: none;
-      border-bottom: 1px solid @barBorderColor;
-    }
-
-    &.lid {
-      border-right: 1px solid @barBorderColor;
-    }
 
     &.settings {
       line-height: 22px;
     }
   }
 
-  .code .row:hover {
-    td.stat { background-color: @barBorderColor; }
-    td.line { background-color: @barBackgroundColor; }
-  }
-
-  .code .row-highlighted,
-  .code .row-highlighted:hover {
-    td.stat { background-color: #fdf190; }
-    td.line, .code-issues, .code-issue { background-color: #fff8c2; }
-  }
-
-  .code td.line {
-    width: 100%;
-    padding: 1px 5px;
-  }
-
-  .code .stat {
-    vertical-align: top;
-    min-width: 12px;
-    padding: 1px 5px;
-    background-color: @barBackgroundColor;
-    color: #888;
-    font-size: 11px;
-    line-height: 16px;
-    text-align: right;
-    cursor: default;
-    white-space: nowrap;
-  }
-
-  .code .lid {
-    min-width: 18px;
-    padding-left: 10px;
-    padding-right: 10px;
-    cursor: pointer;
-  }
-
   .code .coverage-tests {
     cursor: pointer;
   }
     background-color: @barBackgroundColor;
   }
 
-  .code .issue > pre {
-    display: inline-block;
-    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAGCAYAAAAPDoR2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1M0M2Rjk4M0M3QUYxMUUzODkzRUREMUM5OTNDMjY4QSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1M0M2Rjk4NEM3QUYxMUUzODkzRUREMUM5OTNDMjY4QSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjUzQzZGOTgxQzdBRjExRTM4OTNFREQxQzk5M0MyNjhBIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjUzQzZGOTgyQzdBRjExRTM4OTNFREQxQzk5M0MyNjhBIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+bcqJtQAAAEhJREFUeNpi+G+swwDGDAwgbAWlwZiJAQFCgfgwEIfDRaC67ID4NRDnQ2kQnwFZwgFqnANMAQOUYY9sF0wBiCGH5CBkrAgQYACuWi4sSGW8yAAAAABJRU5ErkJggg==);
-    background-repeat: repeat-x;
-    background-size: 4px;
-    background-position: bottom;
-  }
-
-  .code .scm {
-    line-height: 16px;
-    padding-top: 0;
-    padding-bottom: 0;
-    text-align: left;
-
-    .scm-date {
-      display: inline-block;
-      vertical-align: middle;
-      padding: 2px 4px;
-      line-height: 1;
-      background-color: @barBorderColor;
-    }
-
-    .scm-author {
-      display: inline-block;
-      vertical-align: middle;
-      max-width: 120px;
-      padding: 2px 0;
-      line-height: 1;
-      white-space: nowrap;
-      overflow: hidden;
-      text-overflow: ellipsis;
-    }
-
-  }
-
   .code .stat {
 
     &.coverage-green {
diff --git a/server/sonar-web/src/main/less/components.less b/server/sonar-web/src/main/less/components.less
new file mode 100644 (file)
index 0000000..68d0e96
--- /dev/null
@@ -0,0 +1,3 @@
+@import "components/component-issues";
+@import "components/code-source";
+@import "components/facets";
diff --git a/server/sonar-web/src/main/less/components/code-source.less b/server/sonar-web/src/main/less/components/code-source.less
new file mode 100644 (file)
index 0000000..1e4fd50
--- /dev/null
@@ -0,0 +1,92 @@
+@import (reference) "../mixins";
+@import (reference) "../variables";
+
+.code-source {
+
+  .code {
+    width: 100%;
+    border: 1px solid @barBorderColor;
+  }
+
+  .code th {
+    height: 30px;
+    .box-sizing(border-box);
+    background-color: @barBackgroundColor;
+
+    &.lid {
+      border-right: 1px solid @barBorderColor;
+    }
+
+    &.stat {
+      padding-top: 4px;
+      padding-bottom: 4px;
+      border-left: none;
+      border-right: none;
+      border-bottom: 1px solid @barBorderColor;
+    }
+  }
+
+  .code .stat {
+    vertical-align: top;
+    min-width: 12px;
+    padding: 1px 5px;
+    background-color: @barBackgroundColor;
+    color: #888;
+    font-size: 11px;
+    line-height: 16px;
+    text-align: right;
+    cursor: default;
+    white-space: nowrap;
+  }
+
+  .code .lid {
+    min-width: 18px;
+    padding-left: 10px;
+    padding-right: 10px;
+    cursor: pointer;
+  }
+
+  .code .scm {
+    line-height: 16px;
+    padding-top: 0;
+    padding-bottom: 0;
+    text-align: left;
+
+    .scm-date {
+      display: inline-block;
+      vertical-align: middle;
+      padding: 2px 4px;
+      line-height: 1;
+      background-color: @barBorderColor;
+    }
+
+    .scm-author {
+      display: inline-block;
+      vertical-align: middle;
+      max-width: 120px;
+      padding: 2px 0;
+      line-height: 1;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+
+  }
+
+  .code .row:hover {
+    td.stat { background-color: @barBorderColor; }
+    td.line { background-color: @barBackgroundColor; }
+  }
+
+  .code .row-highlighted,
+  .code .row-highlighted:hover {
+    td.stat { background-color: #fdf190; }
+    td.line, .code-issues, .code-issue { background-color: #fff8c2; }
+  }
+
+  .code td.line {
+    width: 100%;
+    padding: 1px 5px;
+  }
+
+}
diff --git a/server/sonar-web/src/main/less/components/component-issues.less b/server/sonar-web/src/main/less/components/component-issues.less
new file mode 100644 (file)
index 0000000..9b07304
--- /dev/null
@@ -0,0 +1,24 @@
+@import (reference) "../variables";
+@import (reference) "../mixins";
+
+.issues-distribution-bar {
+  display: inline-block;
+  vertical-align: middle;
+  width: 45px;
+}
+
+.issues-distribution-bar-item {
+  display: block;
+  min-width: 1px;
+  height: 3px;
+
+  &.s-blocker { background-color: @severityBlockerColor; }
+  &.s-critical { background-color: @severityCriticalColor; }
+  &.s-major { background-color: @severityMajorColor; }
+  &.s-minor { background-color: @severityMinorColor; }
+  &.s-info { background-color: @severityInfoColor; }
+}
+
+.issues-distribution-bar-item + .issues-distribution-bar-item {
+  margin-top: 1px;
+}
diff --git a/server/sonar-web/src/main/less/components/facets.less b/server/sonar-web/src/main/less/components/facets.less
new file mode 100644 (file)
index 0000000..ec0a114
--- /dev/null
@@ -0,0 +1,52 @@
+@import (reference) "../mixins";
+@import (reference) "../variables";
+
+.facet {
+  display: inline-block;
+  vertical-align: middle;
+  line-height: 1;
+  margin: 0 6px 6px 6px;
+  padding: 4px 5px;
+  border: 1px solid @barBorderColor;
+  border-radius: 2px;
+  background-color: lighten(@barBackgroundColor, 2%);
+  font-size: 0;
+  font-weight: 300;
+  cursor: pointer;
+  white-space: nowrap;
+  .trans(none);
+
+  &:hover {
+    border: 1px solid @blue;
+    text-decoration: none;
+  }
+
+  &.active {
+    border: 1px solid @blue;
+    background-color: @lightBlue;
+    text-decoration: none;
+
+    .facet-name {
+      background-color: @lightBlue;
+    }
+
+    .facet-stat {
+      border-color: @blue;
+      background-color: @lightBlue;
+    }
+  }
+}
+
+.facet-name {
+  background-color: lighten(@barBackgroundColor, 2%);
+  font-size: @smallFontSize;
+}
+
+.facet-stat {
+  margin-left: 5px;
+  padding-left: 5px;
+  border-left: 1px solid @barBorderColor;
+  background-color: lighten(@barBackgroundColor, 2%);
+  color: @secondFontColor;
+  font-size: @smallFontSize;
+}
index fa174f8b19bac7383d4af13f7b3827fb8e81acd6..6b60e2ea4a256db639acc07fc0096ff14527fb8c 100644 (file)
@@ -298,7 +298,7 @@ a[class^="icon-"], a[class*=" icon-"] {
 }
 .icon-bulk-change:before {
   content: "\f085";
-  font-size: @iconFontSize;
+  font-size: @iconSmallFontSize;
 }
 .icon-arrow-down:before {
   content: "\f0d7";
@@ -412,6 +412,10 @@ a[class^="icon-"], a[class*=" icon-"] {
   color: @red;
   font-size: @iconFontSize;
 }
+.icon-close:before {
+  content: "\f00d";
+  font-size: @iconFontSize;
+}
 .icon-compare:before {
   content: "\f0c5";
   font-size: @iconSmallFontSize;
@@ -521,6 +525,18 @@ a[class^="icon-"], a[class*=" icon-"] {
   color: #999;
   font-size: @iconSmallFontSize;
 }
+.icon-filters:before {
+  content: "\f039";
+  font-size: @iconSmallFontSize;
+}
+.icon-next:before {
+  content: "\f0d7";
+  font-size: @iconFontSize;
+}
+.icon-prev:before {
+  content: "\f0d8";
+  font-size: @iconFontSize;
+}
 
 
 /*
diff --git a/server/sonar-web/src/main/less/issues.less b/server/sonar-web/src/main/less/issues.less
new file mode 100644 (file)
index 0000000..4a35c24
--- /dev/null
@@ -0,0 +1,302 @@
+@import (reference) "variables";
+@import (reference) "mixins";
+@import (reference) "ui";
+
+@sideWidth: 300px;
+
+
+.issues {
+
+  &.sticky {
+
+    .issues-workspace-header {
+      position: fixed;
+      z-index: 4;
+      top: 0;
+      left: @sideWidth;
+      right: 0px;
+    }
+
+    .issues-workspace-list,
+    .issues-workspace-component-viewer {
+      padding-top: 37px;
+    }
+
+    .issues-side {
+      position: fixed;
+      z-index: 4;
+      top: 0;
+      bottom: 0;
+      overflow-y: scroll;
+    }
+
+  }
+}
+
+.issues-side {
+  position: fixed;
+  width: @sideWidth;
+  top: 30px; left: 0; bottom: 0;
+  .box-sizing(border-box);
+  border-right: 1px solid @barBorderColor;
+  background-color: @barBorderColor;
+  color: #fff;
+  overflow-x: hidden;
+}
+
+.issues-facet-box {
+  border-top: 1px solid @barBorderColor;
+  background-color: @barBackgroundColor;
+  font-size: @baseFontSize;
+}
+
+.issues-facet-box-collapsed {
+  background-color: transparent;
+
+  .issues-facet-list,
+  .issues-facet-container {
+    display: none;
+  }
+
+  .issues-facet-header {
+    color: @secondFontColor;
+    font-weight: 300;
+  }
+}
+
+.issues-facet {
+  position: relative;
+  width: @sideWidth - 22px;
+  margin: 0 22px 1px 0;
+  border-color: transparent;
+  .box-sizing(border-box);
+  background-color: @barBackgroundColor;
+  white-space: normal;
+  overflow-x: hidden;
+
+  .facet-name {
+    white-space: nowrap;
+    background-color: @barBackgroundColor;
+    color: @secondFontColor;
+  }
+
+  .facet-stat {
+    position: absolute;
+    top: 0; right: 0;
+    padding: 4px 5px;
+    border: none;
+    background-color: @barBackgroundColor;
+
+    &:before {
+      content: " ";
+      position: absolute;
+      top: 0; bottom: 0; right: 100%;
+      width: 10px;
+      background-image: linear-gradient(to right, rgba(255, 255, 255, 0), @barBackgroundColor 75%);
+    }
+  }
+
+  &.active .facet-stat:before {
+    background-image: linear-gradient(to right, rgba(255, 255, 255, 0), @lightBlue 75%);
+  }
+}
+
+.issues-facet-half {
+  width: @sideWidth / 2 - 22px;
+}
+
+.issues-facet-header {
+  display: block;
+  padding: 6px 10px;
+  border-bottom: none;
+  color: @baseFontColor;
+  font-weight: 400;
+}
+
+.issues-facet-list {
+  margin: 0 -22px 0 0;
+  padding: 0 10px 10px;
+  font-size: 0;
+}
+
+.issues-facet-container {
+  margin-top: 6px;
+  padding: 0 10px 16px;
+}
+
+.issues-facet-input {
+  width: @sideWidth * 0.4;
+//  .box-sizing(border-box);
+}
+
+.issues-filters {
+  .clearfix;
+  padding: 8px 10px;
+  background-color: @barBackgroundColor;
+  color: @secondFontColor;
+  font-size: @smallFontSize;
+  font-weight: 300;
+}
+
+.issues-filters-list {
+  display: none;
+  margin-bottom: 8px;
+  line-height: 1.5;
+}
+
+.issues-filters-filter {
+
+}
+
+.issues-filters-header {
+  font-size: @smallFontSize;
+  font-weight: 300;
+}
+
+.issues-filters-header-filters {
+  display: inline-block;
+  max-width: 50%;
+  .text-ellipsis;
+}
+
+.issues-filters-header-actions {
+  float: right;
+}
+
+.issues-filters-button {
+  border-bottom-color: @secondFontColor;
+  color: @secondFontColor;
+  font-size: @smallFontSize;
+  font-weight: 300;
+  .trans(color);
+
+  &:hover, &:active {
+    color: #fff;
+  }
+
+  &:focus {}
+}
+
+.issues-workspace {
+  padding-left: @sideWidth;
+}
+
+.issues-header {
+  .clearfix;
+  margin-bottom: 10px;
+  padding: 5px 0;
+  border: 1px solid @barBorderColor;
+  border-left: none;
+  background: @barBackgroundColor;
+  font-size: @smallFontSize;
+}
+
+.issues-header-item {
+  display: inline-block;
+  margin: 0 10px;
+}
+
+.issues-header-nav {
+  float: right;
+}
+
+.issues-workspace-list {
+  padding: 0 10px;
+}
+
+.issues-workspace-list-more {
+  margin-top: 10px;
+  padding: 5px 10px;
+  border: 1px solid @barBorderColor;
+  background: @barBackgroundColor;
+  text-align: center;
+}
+
+.issues-workspace-component-viewer {
+  padding: 0 10px;
+  overflow-x: auto;
+}
+
+.issues-workspace-component-viewer-header {
+  position: relative;
+}
+
+.issues-workspace-component-viewer-close {
+  position: absolute;
+  top: 50%;
+  right: 10px;
+  margin-top: -8px;
+}
+
+.issues-extended-view {
+
+  .issues-workspace-list {
+    display: none;
+  }
+}
+
+.issue-box {
+  .clearfix;
+  border: 1px solid @barBorderColor;
+  border-left: none;
+  overflow: hidden;
+
+  &.selected {
+    background-color: #e4ecf3;
+  }
+}
+
+.issue-box + .issue-box {
+  margin-top: 12px;
+}
+
+.issue-box-component,
+.issue-box-snippet {
+  border-left: 2px solid @red;
+}
+
+.issue-box-details {
+
+}
+
+.code-issue-modern {
+  border-left: 2px solid @red;
+
+  .code-issue-name,
+  .code-issue-actions,
+  .code-issue-details,
+  .code-issue-comment {
+    border: none;
+    background: transparent;
+  }
+
+  &.highlighted {
+    background: #E4ECF3;
+  }
+}
+
+.code-source .code-issue-modern {
+  margin: 5px 0;
+}
+
+.issue-box-snippet {
+  padding: 3px 0;
+  overflow: hidden;
+  cursor: pointer;
+
+  .code {
+    border: none;
+  }
+}
+
+.issue-box-component {
+  padding: 5px;
+}
+
+.code .issue > pre {
+  display: inline-block;
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAGCAYAAAAPDoR2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1M0M2Rjk4M0M3QUYxMUUzODkzRUREMUM5OTNDMjY4QSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1M0M2Rjk4NEM3QUYxMUUzODkzRUREMUM5OTNDMjY4QSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjUzQzZGOTgxQzdBRjExRTM4OTNFREQxQzk5M0MyNjhBIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjUzQzZGOTgyQzdBRjExRTM4OTNFREQxQzk5M0MyNjhBIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+bcqJtQAAAEhJREFUeNpi+G+swwDGDAwgbAWlwZiJAQFCgfgwEIfDRaC67ID4NRDnQ2kQnwFZwgFqnANMAQOUYY9sF0wBiCGH5CBkrAgQYACuWi4sSGW8yAAAAABJRU5ErkJggg==);
+  background-repeat: repeat-x;
+  background-size: 4px;
+  background-position: bottom;
+}
index 1613a496c5ecf65075a732beb9ccd39cb87b6223..200ab94be5a079d70b0d338c0c18aaca9a303419 100644 (file)
   overflow: hidden;
 }
 
-.navigator-facets-list-item-option {
-  display: inline-block;
-  vertical-align: middle;
-  line-height: 1;
-  margin: 0 6px 6px 6px;
-  padding: 4px 5px;
-  border: 1px solid @barBorderColor;
-  border-radius: 2px;
-  background-color: lighten(@barBackgroundColor, 2%);
-  font-size: 0;
-  font-weight: 300;
-  cursor: pointer;
-  .trans;
-
-  &:hover {
-    border: 1px solid @blue;
-    text-decoration: none;
-  }
-
-  &.active {
-    border: 1px solid @blue;
-    background-color: @lightBlue;
-    text-decoration: none;
-
-    .navigator-facets-list-item-option-stat {
-      border-color: @blue;
-    }
-  }
-}
-
-.navigator-facets-list-item-option-name {
-  font-size: @smallFontSize;
-}
-
-.navigator-facets-list-item-option-stat {
-  margin-left: 5px;
-  padding-left: 5px;
-  border-left: 1px solid @barBorderColor;
-  color: @navigatorFacetStatColor;
-  font-size: @smallFontSize;
-  .trans;
-}
-
 
 
 // Results
index a0c6ec7f7bad8ca63c64c68821546486daaa27d9..62af5c47ceadf08f61098740cba9a6c9af73af0b 100644 (file)
@@ -708,7 +708,7 @@ th.operations, td.operations {
 
 .rulename, .rulename a {
   color: #444;
-  font-weight: bold;
+  font-weight: 500;
 }
 
 .rulename a:hover {
@@ -806,7 +806,6 @@ th.operations, td.operations {
 .code-issue {
   margin: 0;
   font-size: 12px;
-  padding: 0 0 10px 0;
   .trans(border-color);
 
   .error {
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issues2_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issues2_controller.rb
new file mode 100644 (file)
index 0000000..9cc713e
--- /dev/null
@@ -0,0 +1,75 @@
+#
+# 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.
+#
+require 'set'
+
+class Issues2Controller < ApplicationController
+
+  SECTION=Navigation::SECTION_ISSUES2
+
+  before_filter :init_options
+  before_filter :load_fav_filters, :only => [:index, :search, :search2, :filter, :manage, :favourites, :toggle_fav]
+
+  PAGE_SIZE = 100
+
+  # GET /issues/index
+  def index
+    redirect_to :action => 'search'
+  end
+
+  # GET /issues/search
+  def search
+
+  end
+
+  private
+
+  def init_options
+    @options_for_statuses = Internal.issues.listStatus().map { |s| [message('issue.status.' + s), s] }
+    @options_for_resolutions = Internal.issues.listResolutions().map { |s| [message('issue.resolution.' + s), s] }
+  end
+
+  def load_fav_filters
+    @favourite_filters = Internal.issues.findFavouriteIssueFiltersForCurrentUser() if logged_in?
+  end
+
+  def find_filter(id)
+    Internal.issues.findIssueFilter(id)
+  end
+
+  def criteria_params
+    new_params = params.clone
+    new_params.delete('controller')
+    new_params.delete('action')
+    new_params
+  end
+
+  def init_params
+    params['pageSize'] = PAGE_SIZE unless request.xhr?
+  end
+
+  def issues_query_params_sanitized
+    Internal.issues.sanitizeFilterQuery(params).to_hash
+  end
+
+  def issues_query_params_from_filter(filter)
+    Internal.issues.deserializeFilterQuery(filter).to_hash
+  end
+
+end
index 4b690d90178c3aa238c1d26f7de9c1fd13f85c1f..7c70925232938445b3b5dd8adab1644ea3adb269 100644 (file)
@@ -32,6 +32,7 @@ class Navigation
   SECTION_RESOURCE_CONFIGURATION = Navigation.new('resource_configuration', true)
   SECTION_SESSION = Navigation.new('session', true)
   SECTION_ISSUES = Navigation.new('issues', false)
+  SECTION_ISSUES2 = Navigation.new('issues2', false)
   SECTION_MEASURES = Navigation.new('measures', false)
   SECTION_QUALITY_PROFILES = Navigation.new('quality_profiles', false)
   SECTION_QUALITY_GATES = Navigation.new('quality_gates', false)
index fac32ece75d1db492071f67c424485fff120aa6d..70dcece4ae5744e879a4a9f2016c0a4f7694c603 100644 (file)
@@ -2,256 +2,6 @@
   <script data-main="<%= ApplicationController.root_context -%>/js/issues/app" src="<%= ApplicationController.root_context -%>/js/require.js"></script>
 <% end %>
 
-<div class="navigator">
-  <div class="navigator-header"></div>
-  <div class="navigator-filters"></div>
-
-  <div class="navigator-content">
-    <div class="navigator-side">
-      <div style="position:relative; overflow: visible; height: 100%;">
-        <div class="navigator-actions"></div>
-        <div class="navigator-results"></div>
-        <a class="navigator-resizer"><i class="icon-resizer"></i></a>
-      </div>
-    </div>
-    <div class="navigator-main">
-      <div class="navigator-notes"><%= message('issue_filter.max_results_reached', :params => 10000) -%></div>
-      <div class="navigator-details"></div>
-    </div>
-  </div>
+<div class="issues">
+  <i class="spinner"></i>
 </div>
-
-
-<script>
-  window.SS = {};
-
-  _.extend(window.SS, {
-    currentUser: '<%= current_user.login if current_user -%>',
-    currentUserName: '<%= current_user.name if current_user -%>',
-    severities: <%= RulesConfigurationController::RULE_PRIORITIES.to_json.html_safe -%>,
-    statuses: <%= @options_for_statuses.to_json.html_safe -%>,
-    resolutions: <%= @options_for_resolutions.to_json.html_safe -%>,
-    favorites: <%= render :partial => 'issues/filter_favourites' -%>,
-    languages: {<% controller.java_facade.getLanguages().to_a.sort {|a,b| a.getName() <=> b.getName()}.each do |language| -%>
-      '<%= language.getKey().html_safe -%>': '<%= language.getName().html_safe -%>',
-    <% end %>},
-
-    phrases: {
-      'actionPlan':    '<%= escape_javascript message('issue_filter.criteria.actionPlan') -%>',
-      'actionPlanNotAvailable': '<%= escape_javascript message('issue_filter.criteria.actionPlanNotAvailable') -%>',
-      'any':           '<%= escape_javascript message('any') -%>',
-      'anytime':       '<%= escape_javascript message('anytime') -%>',
-      'all':           '<%= escape_javascript message('all') -%>',
-      'edit':          '<%= escape_javascript message('edit') -%>',
-      'delete':        '<%= escape_javascript message('delete') -%>',
-      'to':            '<%= escape_javascript message('to.downcase') -%>',
-      'project':       '<%= escape_javascript message('issue_filter.criteria.project') -%>',
-      'language':       '<%= escape_javascript message('language') -%>',
-      'severity':      '<%= escape_javascript message('issue_filter.criteria.severity') -%>',
-      'severities': {
-        BLOCKER:       '<%= escape_javascript message('severity.BLOCKER') -%>',
-        CRITICAL:      '<%= escape_javascript message('severity.CRITICAL') -%>',
-        MAJOR:         '<%= escape_javascript message('severity.MAJOR') -%>',
-        MINOR:         '<%= escape_javascript message('severity.MINOR') -%>',
-        INFO:          '<%= escape_javascript message('severity.INFO') -%>'
-      },
-      'status':        '<%= escape_javascript message('issue_filter.criteria.status') -%>',
-      'statuses': {
-        OPEN:          '<%= escape_javascript message('issue.status.OPEN') -%>',
-        CONFIRMED:     '<%= escape_javascript message('issue.status.CONFIRMED') -%>',
-        REOPENED:      '<%= escape_javascript message('issue.status.REOPENED') -%>',
-        RESOLVED:      '<%= escape_javascript message('issue.status.RESOLVED') -%>',
-        CLOSED:        '<%= escape_javascript message('issue.status.CLOSED') -%>'
-      },
-      'actions': {
-        comment:        '<%= escape_javascript message('issue.comment.formlink') -%>',
-        assign:         '<%= escape_javascript message('issue.assign.formlink') -%>',
-        assign_to_me:   '<%= escape_javascript message('issue.assign.to_me') -%>',
-        assigned_to:    '<%= escape_javascript message('assigned_to') -%>',
-        plan:           '<%= escape_javascript message('issue.do_plan') -%>',
-        planned_for:    '<%= escape_javascript message('issue.planned_for') -%>',
-        set_severity:   '<%= escape_javascript message('issue.set_severity') -%>',
-        <%  Internal.issues.listPluginActions().each do |action| -%>
-          '<%= escape_javascript action -%>': '<%= escape_javascript message("issue.action.#{action}.formlink") -%>',
-        <% end -%>
-      },
-      'transitions': {
-        confirm:        '<%= escape_javascript message('issue.transition.confirm') -%>',
-        unconfirm:      '<%= escape_javascript message('issue.transition.unconfirm') -%>',
-        resolve:        '<%= escape_javascript message('issue.transition.resolve') -%>',
-        falsepositive:  '<%= escape_javascript message('issue.transition.falsepositive') -%>',
-        reopen:         '<%= escape_javascript message('issue.transition.reopen') -%>',
-        close:          '<%= escape_javascript message('issue.transition.close') -%>'
-      },
-      assignee:        '<%= escape_javascript message('issue_filter.criteria.assignee') -%>',
-      resolution:      '<%= escape_javascript message('issue_filter.criteria.resolution') -%>',
-      resolutions: {
-        'RESOLVED':  '<%= escape_javascript message('issue.status.RESOLVED') -%>',
-        'UNRESOLVED':  '<%= escape_javascript message('unresolved') -%>',
-        'FALSE-POSITIVE': '<%= escape_javascript message('issue.resolution.FALSE-POSITIVE') -%>',
-        'FIXED':       '<%= escape_javascript message('issue.resolution.FIXED') -%>',
-        'REMOVED':     '<%= escape_javascript message('issue.resolution.REMOVED') -%>'
-      },
-      reporter:        '<%= escape_javascript message('issue_filter.criteria.reporter') -%>',
-      rule:            '<%= escape_javascript message('issue_filter.criteria.rule') -%>',
-      created:         '<%= escape_javascript message('issue_filter.criteria.created') -%>',
-      createdAt:       '<%= escape_javascript message('issue_filter.criteria.created_at') -%>',
-
-      moreCriteria:    '<%= escape_javascript message('issue_filter.more_criteria') -%>',
-      unassigned:      '<%= escape_javascript message('unassigned') -%>',
-      unplanned:       '<%= escape_javascript message('issue.unplanned') -%>',
-      assignedToMe:    '<%= escape_javascript message('assigned_to_me') -%>',
-      filtersList:     '<%= escape_javascript message('issue_filter.filter_list') -%>',
-      commentConfirmDelete:  '<%= escape_javascript message('issue.comment.delete_confirm_message') -%>',
-      requirementRemoved:    '<%= escape_javascript message('issue.technical_debt_deleted') -%>',
-
-      select2: {
-        noMatches: '<%= escape_javascript message('select2.noMatches') -%>',
-        searching: '<%= escape_javascript message('select2.searching') -%>',
-        tooShort: '<%= escape_javascript message('select2.tooShort', :params => [2]) -%>'
-      },
-      'Done': '<%= escape_javascript message("Done") -%>',
-      'Prev': '<%= escape_javascript message("Prev") -%>',
-      'Next': '<%= escape_javascript message("Next") -%>',
-      'Today': '<%= escape_javascript message("Today") -%>',
-      'January': '<%= escape_javascript message("January") -%>',
-      'February': '<%= escape_javascript message("February") -%>',
-      'March': '<%= escape_javascript message("March") -%>',
-      'April': '<%= escape_javascript message("April") -%>',
-      'May': '<%= escape_javascript message("May") -%>',
-      'June': '<%= escape_javascript message("June") -%>',
-      'July': '<%= escape_javascript message("July") -%>',
-      'August': '<%= escape_javascript message("August") -%>',
-      'September': '<%= escape_javascript message("September") -%>',
-      'October': '<%= escape_javascript message("October") -%>',
-      'November': '<%= escape_javascript message("November") -%>',
-      'December': '<%= escape_javascript message("December") -%>',
-      'Jan': '<%= escape_javascript message("Jan") -%>',
-      'Feb': '<%= escape_javascript message("Feb") -%>',
-      'Mar': '<%= escape_javascript message("Mar") -%>',
-      'Apr': '<%= escape_javascript message("Apr") -%>',
-      'Jun': '<%= escape_javascript message("Jun") -%>',
-      'Jul': '<%= escape_javascript message("Jul") -%>',
-      'Aug': '<%= escape_javascript message("Aug") -%>',
-      'Sep': '<%= escape_javascript message("Sep") -%>',
-      'Oct': '<%= escape_javascript message("Oct") -%>',
-      'Nov': '<%= escape_javascript message("Nov") -%>',
-      'Dec': '<%= escape_javascript message("Dec") -%>',
-      'Sunday': '<%= escape_javascript message("Sunday") -%>',
-      'Monday': '<%= escape_javascript message("Monday") -%>',
-      'Tuesday': '<%= escape_javascript message("Tuesday") -%>',
-      'Wednesday': '<%= escape_javascript message("Wednesday") -%>',
-      'Thursday': '<%= escape_javascript message("Thursday") -%>',
-      'Friday': '<%= escape_javascript message("Friday") -%>',
-      'Saturday': '<%= escape_javascript message("Saturday") -%>',
-      'Sun': '<%= escape_javascript message("Sun") -%>',
-      'Mon': '<%= escape_javascript message("Mon") -%>',
-      'Tue': '<%= escape_javascript message("Tue") -%>',
-      'Wed': '<%= escape_javascript message("Wed") -%>',
-      'Thu': '<%= escape_javascript message("Thu") -%>',
-      'Fri': '<%= escape_javascript message("Fri") -%>',
-      'Sat': '<%= escape_javascript message("Sat") -%>',
-      'Su': '<%= escape_javascript message("Su") -%>',
-      'Mo': '<%= escape_javascript message("Mo") -%>',
-      'Tu': '<%= escape_javascript message("Tu") -%>',
-      'We': '<%= escape_javascript message("We") -%>',
-      'Th': '<%= escape_javascript message("Th") -%>',
-      'Fr': '<%= escape_javascript message("Fr") -%>',
-      'Sa': '<%= escape_javascript message("Sa") -%>'
-    }
-  });
-
-  window.messages = {
-    'all': '<%= escape_javascript message('all') -%>',
-    'assigned_to': '<%= escape_javascript message('assigned_to') -%>',
-    'bulk_change': '<%= escape_javascript message('bulk_change') -%>',
-    'issue.planned_for': '<%= escape_javascript message('issue.planned_for') -%>',
-    'issue.do_plan': '<%= escape_javascript message('issue.do_plan') -%>',
-    'issue.planned_for': '<%= escape_javascript message('issue.planned_for') -%>',
-    'more_actions': '<%= escape_javascript message('more_actions') -%>',
-    'reporter': '<%= escape_javascript message('reporter') -%>',
-    'author': '<%= escape_javascript message('author') -%>',
-    'cancel': '<%= escape_javascript message('cancel') -%>',
-    'save': '<%= escape_javascript message('save') -%>',
-    'issue.technical_debt_short': '<%= escape_javascript message('issue.technical_debt_short') -%>',
-    'issue.technical_debt_deleted': '<%= escape_javascript message('issue.technical_debt_deleted') -%>',
-    'rule': '<%= escape_javascript message('rule') -%>',
-    'changelog': '<%= escape_javascript message('changelog') -%>',
-    'edit': '<%= escape_javascript message('edit') -%>',
-    'delete': '<%= escape_javascript message('delete') -%>',
-    'moreCriteria': '<%= escape_javascript message('issue_filter.more_criteria') -%>',
-    'issue.plan.submit': '<%= escape_javascript message('issue.plan.submit') -%>',
-    'issue.unplan.submit': '<%= escape_javascript message('issue.unplan.submit') -%>',
-    'issue.assign.submit': '<%= escape_javascript message('issue.assign.submit') -%>',
-    'issue.set_severity.submit': '<%= escape_javascript message('issue.set_severity.submit') -%>',
-    'issue.comment.submit': '<%= escape_javascript message('issue.comment.submit') -%>',
-    'issue.plan.error.plan_must_be_created_first_for_admin': '<%= escape_javascript message('issue.plan.error.plan_must_be_created_first_for_admin') -%>',
-    'issue.plan.error.plan_must_be_created_first_for_other': '<%= escape_javascript message('issue.plan.error.plan_must_be_created_first_for_other') -%>',
-    'markdown.helplink': '<%= escape_javascript message('markdown.helplink') -%>',
-    'bold': '<%= escape_javascript message('bold') -%>',
-    'code': '<%= escape_javascript message('code') -%>',
-    'bulleted_point': '<%= escape_javascript message('bulleted_point') -%>',
-    'severity.BLOCKER': '<%= escape_javascript message('severity.BLOCKER') -%>',
-    'severity.CRITICAL': '<%= escape_javascript message('severity.CRITICAL') -%>',
-    'severity.MAJOR': '<%= escape_javascript message('severity.MAJOR') -%>',
-    'severity.MINOR': '<%= escape_javascript message('severity.MINOR') -%>',
-    'severity.INFO': '<%= escape_javascript message('severity.INFO') -%>',
-
-    'component_viewer.measure_section.size': '<%= escape_javascript message('component_viewer.measure_section.size') -%>',
-    'component_viewer.measure_section.complexity': '<%= escape_javascript message('component_viewer.measure_section.complexity') -%>',
-    'component_viewer.measure_section.structure': '<%= escape_javascript message('component_viewer.measure_section.structure') -%>',
-    'component_viewer.measure_section.documentation': '<%= escape_javascript message('component_viewer.measure_section.documentation') -%>',
-    'component_viewer.measure_section.severities': '<%= escape_javascript message('component_viewer.measure_section.severities') -%>',
-    'component_viewer.measure_section.rules': '<%= escape_javascript message('component_viewer.measure_section.rules') -%>',
-    'component_viewer.measure_section.issues': '<%= escape_javascript message('component_viewer.measure_section.issues') -%>',
-    'component_viewer.measure_section.sqale': '<%= escape_javascript message('component_viewer.measure_section.sqale') -%>',
-    'component_viewer.measure_section.unit_tests': '<%= escape_javascript message('component_viewer.measure_section.unit_tests') -%>',
-    'component_viewer.measure_section.integration_tests': '<%= escape_javascript message('component_viewer.measure_section.integration_tests') -%>',
-
-    'component_viewer.issues.current_issue': '<%= escape_javascript message('component_viewer.issues.current_issue') -%>',
-    'component_viewer.issues.unresolved_issues': '<%= escape_javascript message('component_viewer.issues.unresolved_issues') -%>',
-    'component_viewer.issues.false_positive_issues': '<%= escape_javascript message('component_viewer.issues.false_positive_issues') -%>',
-
-    'component_viewer.header.debt': '<%= escape_javascript message('component_viewer.header.debt') -%>',
-    'component_viewer.header.toggle_issues': '<%= escape_javascript message('component_viewer.header.toggle_issues') -%>',
-    'component_viewer.header.toggle_coverage': '<%= escape_javascript message('component_viewer.header.toggle_coverage') -%>',
-    'component_viewer.header.toggle_duplications': '<%= escape_javascript message('component_viewer.header.toggle_duplications') -%>',
-
-    'metric.lines.name': '<%= escape_javascript message('metric.lines.name') -%>',
-    'metric.ncloc.name': '<%= escape_javascript message('metric.ncloc.name') -%>',
-    'metric.complexity.name': '<%= escape_javascript message('metric.complexity.name') -%>',
-    'metric.function_complexity.name': '<%= escape_javascript message('metric.function_complexity.name') -%>',
-    'metric.classes.name': '<%= escape_javascript message('metric.classes.name') -%>',
-    'metric.functions.name': '<%= escape_javascript message('metric.functions.name') -%>',
-    'metric.accessors.name': '<%= escape_javascript message('metric.accessors.name') -%>',
-    'metric.statements.name': '<%= escape_javascript message('metric.statements.name') -%>',
-    'metric.comment_lines.name': '<%= escape_javascript message('metric.comment_lines.name') -%>',
-    'metric.comment_lines_density.name': '<%= escape_javascript message('metric.comment_lines_density.name') -%>',
-    'metric.public_api.name': '<%= escape_javascript message('metric.public_api.name') -%>',
-    'metric.public_undocumented_api.name': '<%= escape_javascript message('metric.public_undocumented_api.name') -%>',
-    'metric.public_documented_api_density.name': '<%= escape_javascript message('metric.public_documented_api_density.name') -%>',
-
-    'metric.coverage.name': '<%= escape_javascript message('metric.coverage.name') -%>',
-    'metric.line_coverage.name': '<%= escape_javascript message('metric.line_coverage.name') -%>',
-    'metric.lines_to_cover.name': '<%= escape_javascript message('metric.lines_to_cover.name') -%>',
-    'metric.covered_lines.name': '<%= escape_javascript message('metric.covered_lines.name') -%>',
-    'metric.uncovered_lines.name': '<%= escape_javascript message('metric.uncovered_lines.name') -%>',
-    'metric.branch_coverage.name': '<%= escape_javascript message('metric.branch_coverage.name') -%>',
-    'metric.conditions_to_cover.name': '<%= escape_javascript message('metric.conditions_to_cover.name') -%>',
-    'metric.covered_conditions.name': '<%= escape_javascript message('metric.covered_conditions.name') -%>',
-    'metric.uncovered_conditions.name': '<%= escape_javascript message('metric.uncovered_conditions.name') -%>',
-
-    'metric.duplicated_blocks.name': '<%= escape_javascript message('metric.duplicated_blocks.name') -%>',
-    'metric.duplicated_files.name': '<%= escape_javascript message('metric.duplicated_files.name') -%>',
-    'metric.duplicated_lines.name': '<%= escape_javascript message('metric.duplicated_lines.name') -%>',
-    'metric.duplicated_lines_density.name': '<%= escape_javascript message('metric.duplicated_lines_density.name') -%>',
-
-    'metric.violations.name': '<%= escape_javascript message('metric.violations.name') -%>',
-    'metric.sqale_index.name': '<%= escape_javascript message('metric.sqale_index.name') -%>',
-    'metric.blocker_violations.name': '<%= escape_javascript message('metric.blocker_violations.name') -%>',
-    'metric.critical_violations.name': '<%= escape_javascript message('metric.critical_violations.name') -%>',
-    'metric.major_violations.name': '<%= escape_javascript message('metric.major_violations.name') -%>',
-    'metric.minor_violations.name': '<%= escape_javascript message('metric.minor_violations.name') -%>',
-    'metric.info_violations.name': '<%= escape_javascript message('metric.info_violations.name') -%>'
-  };
-</script>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_bulk_change_form.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_bulk_change_form.html.erb
new file mode 100644 (file)
index 0000000..3802a05
--- /dev/null
@@ -0,0 +1,161 @@
+<%
+   project_uuid = @projectUuids.to_a.first if !@projectUuids.empty? && @projectUuids.to_a.size == 1
+   project_key = Internal.component_api.findByUuid(project_uuid).key() if project_uuid
+   max_page_size_reached = @issues.size >= Internal.issues.maxPageSize()
+
+   transitions_by_issues = {}
+   unresolved_issues = 0
+   unresolved_issues_user_can_admin = 0
+   at_least_one_issue_is_planned = false
+   at_least_one_issue_is_assigned = false
+   all_issues_are_assigned_to_current_user = true
+   @issues.each do |issue|
+     transitions = Internal.issues.listTransitions(issue)
+     transitions.each do |transition|
+       issues_for_transition = transitions_by_issues[transition.key] || 0
+       issues_for_transition += 1
+       transitions_by_issues[transition.key] = issues_for_transition
+     end
+     unresolved_issues += 1 unless issue.resolution()
+     if Java::OrgSonarServerUser::UserSession.get().hasProjectPermissionByUuid('issueadmin', issue.projectUuid)
+       unresolved_issues_user_can_admin += 1 unless issue.resolution()
+     end
+     at_least_one_issue_is_planned ||= issue.actionPlanKey()
+     at_least_one_issue_is_assigned ||= issue.assignee()
+     all_issues_are_assigned_to_current_user &&= issue.assignee() == current_user.login
+   end
+%>
+<form id="bulk-change-form" method="post" action="<%= ApplicationController.root_context -%>/issues/bulk_change">
+  <input type="hidden" name="issues" value="<%= @issues.map { |issue| issue.key() }.join(',') -%>">
+  <input type="hidden" name="actions[]" id="bulk-change-transition-action">
+  <fieldset>
+    <div class="modal-head">
+      <h2><%= message('issue_bulk_change.form.title', {:params => @issues.size.to_s}) -%></h2>
+    </div>
+    <div class="modal-body">
+      <div>
+      <% if max_page_size_reached %>
+        <p class="notes"><%= message('issue_bulk_change.max_issues_reached', :params => @issues.size) -%></p>
+      <% end %>
+      </div>
+      <div class="modal-error"/>
+
+      <% if unresolved_issues > 0 %>
+      <div class="modal-field">
+        <label for="assignee">
+          <%= message('issue.assign.formlink') -%>
+        </label>
+        <input id="assign-action" name="actions[]" type="checkbox" value="assign"/>
+        <%= assign_added_choices = {}
+            # SONAR-4728 Display 'Not assigned' option only if at least one issue is assigned
+            assign_added_choices[''] = escape_javascript(message('unassigned')) if at_least_one_issue_is_assigned
+            # SONAR-4728 Display 'Assign to me' option only if not not all issues are asigned to current user
+            assign_added_choices[current_user.login] = escape_javascript(message('assigned_to_me')) unless all_issues_are_assigned_to_current_user
+            user_select_tag('assign.assignee', :html_id => 'assignee', :open => false, :selected_user => current_user,
+                            :include_choices => assign_added_choices)
+        -%>
+        <span style="float:right" class="note">(<%= message('issue_bulk_change.x_issues', :params => unresolved_issues.to_s) -%>)</span>
+      </div>
+      <%
+         if project_key
+           plans = Internal.issues.findOpenActionPlans(project_key)
+           # Display action plan combo box if there are some action plans defined on the project
+           unless plans.empty?
+             first_plan = plans[0]
+             options = plans.map { |plan|
+               label = plan.deadLine ? "#{h plan.name} (#{format_date(plan.deadLine)})" : h(plan.name)
+               [label, plan.key]
+             }
+             # SONAR-4728 Display 'Unplan' option only if at least one issue is planned
+             options.unshift([escape_javascript(message('issue.unplan.submit')), '']) if at_least_one_issue_is_planned
+             plan_options = options_for_select(options, first_plan.key)
+      %>
+        <div class="modal-field">
+          <label for="plan">
+            <%= message('issue.do_plan') -%>
+          </label>
+          <input id="plan-action" name="actions[]" type="checkbox" value="plan"/>
+          <%= dropdown_tag('plan.plan', plan_options, {:show_search_box => false}, {:id => 'plan'}) -%>
+          <span style="float:right" class="note">(<%= message('issue_bulk_change.x_issues', :params => unresolved_issues.to_s) -%>)</span>
+        </div>
+        <% end %>
+      <% end %>
+      <div class="modal-field">
+        <label for="severity">
+          <%= message('issue.set_severity') -%>
+        </label>
+        <input id="set-severity-action" name="actions[]" type="checkbox" value="set_severity"/>
+        <%= severity_dropdown_tag('set_severity.severity', severitiy_select_option_tags, {:show_search_box => false},
+                         {:id => 'severity'}) -%>
+        <span style="float:right" class="note">(<%= message('issue_bulk_change.x_issues', :params => unresolved_issues_user_can_admin.to_s) -%>)</span>
+      </div>
+      <% end %>
+
+      <% if transitions_by_issues.size > 0 %>
+      <div class="modal-field">
+        <label>
+          <%= message('issue.transition') -%>
+        </label>
+        <% transitions_by_issues.keys.sort.each do |transition| %>
+          <input type="radio" id="transition-<%= transition -%>" name="do_transition.transition" value="<%= transition -%>"
+                 onClick="addTransitionAction();">&nbsp;<label for="transition-<%= transition -%>" style="float: none; display: inline; left: 0; cursor: pointer;"><%= message("issue.transition.#{transition}") -%></label>
+          <span style="float:right" class="note">(<%= message('issue_bulk_change.x_issues', :params => transitions_by_issues[transition].to_s) %>)</span><br/>
+      <% end %>
+      </div>
+      <% end %>
+
+      <div class="modal-field">
+        <label>
+         <%= message('issue.comment.formlink') -%>
+          <span style="cursor: help;"><%= image_tag 'help.png', :title => h(message('issue_bulk_change.comment.help')) -%></span>
+        </label>
+        <div style="padding: 0 10px 10px 0;">
+          <div>
+            <textarea rows="4" name="comment" id="comment" style="width: 100%"></textarea>
+          </div>
+          <div style="float:right">
+            <%= render :partial => 'markdown/tips' -%>
+          </div>
+        </div>
+      </div>
+
+      <div class="modal-field">
+        <label for="send-notifications">
+          <%= message('issue.send_notifications') -%>
+        </label>
+        <input id="send-notifications" name="sendNotifications" type="checkbox" value="true" />
+      </div>
+    </div>
+    <div class="modal-foot">
+      <span id="bulk-change-loading-image" class="loading-image hidden"><%= image_tag 'loading.gif' %></span>
+      <input type="submit" value="<%= message('apply') -%>" id="bulk-change-submit" class="bulk-change" onclick="return displayLoadingImage()" />
+      <a href="#" onclick="return closeModalWindow()" id="bulk-change-cancel"><%= message('cancel') -%></a>
+    </div>
+  </fieldset>
+</form>
+<script>
+  $j("#bulk-change-form").modalForm({
+    success: function () {
+      onBulkIssues(<%= json_escape(params.to_json) -%>);
+    }
+  });
+
+  function addTransitionAction() {
+    $j('#bulk-change-transition-action').val("do_transition");
+  }
+
+  function displayLoadingImage() {
+    $j('#bulk-change-loading-image').removeClass("hidden");
+  }
+
+  function check(id){
+    $j('#'+ id).prop('checked', true);
+  }
+
+  /**
+   * Attach some events on select boxes to automatically select associate check box when selecting an option
+   */
+  $j('#assignee').click(function() { check('assign-action'); });
+  $j('#plan').click(function() { check('plan-action'); });
+  $j('#severity').click(function() { check('set-severity-action'); });
+</script>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_copy_form.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_copy_form.html.erb
new file mode 100644 (file)
index 0000000..ad34992
--- /dev/null
@@ -0,0 +1,20 @@
+<form id="copy-filter-form" method="post" action="<%= ApplicationController.root_context -%>/issues/copy">
+  <input type="hidden" name="id" value="<%= @filter.id -%>">
+  <fieldset>
+    <div class="modal-head">
+      <h2><%= message('issue_filter.copy_filter') -%></h2>
+    </div>
+    <%= render :partial => 'filter_shared_form' %>
+    <div class="modal-foot">
+      <input type="submit" value="<%= message('copy') -%>" id="copy-submit" class="issue-filter"/>
+      <a href="#" onclick="return closeModalWindow()" id="copy-cancel"><%= h message('cancel') -%></a>
+    </div>
+  </fieldset>
+</form>
+<script>
+  $j("#copy-filter-form").modalForm({
+    success: function (data) {
+      return onCopy(data);
+    }
+  });
+</script>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_edit_form.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_edit_form.html.erb
new file mode 100644 (file)
index 0000000..650e7e1
--- /dev/null
@@ -0,0 +1,20 @@
+<form id="edit-filter-form" method="post" action="<%= ApplicationController.root_context -%>/issues/edit">
+  <input type="hidden" name="id" value="<%= @filter.id -%>">
+  <fieldset>
+    <div class="modal-head">
+      <h2><%= message('issue_filter.edit_filter') -%></h2>
+    </div>
+    <%= render :partial => 'filter_shared_form', :locals => {:display_owner => true} %>
+    <div class="modal-foot">
+      <input type="submit" value="<%= message('save') -%>" id="save-submit" class="issue-filter"/>
+      <a href="#" onclick="return closeModalWindow()" id="save-cancel"><%= message('cancel') -%></a>
+    </div>
+  </fieldset>
+</form>
+<script>
+  $j("#edit-filter-form").modalForm({
+    success: function (data) {
+      return onEdit(data);
+    }
+  });
+</script>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_favourites.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_favourites.html.erb
new file mode 100644 (file)
index 0000000..4c1f601
--- /dev/null
@@ -0,0 +1,9 @@
+<% if logged_in? %>
+  {
+  <% @favourite_filters.each do |filter| %>
+    "<%= h filter.id -%>": "<%= escape_javascript filter.name -%>",
+  <% end %>
+  }
+<% else %>
+  null
+<% end %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_save_as_form.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_save_as_form.html.erb
new file mode 100644 (file)
index 0000000..a771f24
--- /dev/null
@@ -0,0 +1,20 @@
+<form id="save-as-filter-form" method="post" action="<%= ApplicationController.root_context -%>/issues/save_as">
+  <input type="hidden" name="data" value="<%= u(@filter_query_serialized) -%>">
+  <fieldset>
+    <div class="modal-head">
+      <h2><%= message('issue_filter.save_filter') -%></h2>
+    </div>
+    <%= render :partial => 'filter_shared_form' %>
+    <div class="modal-foot">
+      <input type="submit" value="<%= message('save') -%>" id="save-as-submit" class="issue-filter"/>
+      <a href="#" onclick="return closeModalWindow()" id="save-as-cancel"><%= message('cancel') -%></a>
+    </div>
+  </fieldset>
+</form>
+<script>
+  $j("#save-as-filter-form").modalForm({
+    success: function (data) {
+      return onSaveAs(data);
+    }
+  });
+</script>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_shared_form.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_shared_form.html.erb
new file mode 100644 (file)
index 0000000..24c3891
--- /dev/null
@@ -0,0 +1,33 @@
+<% if !local_assigns.has_key? :display_owner
+     display_owner = false
+   end %>
+<div class="modal-body">
+  <div class="modal-error"/>
+  <div class="modal-field">
+    <label for="name"><%= message('issue_filter.form.name') -%> <em class="mandatory">*</em></label>
+    <input id="name" name="name" type="text" size="50" maxlength="100" value="<%= h(@filter.name) if @filter -%>" autofocus="autofocus"/>
+  </div>
+  <div class="modal-field">
+    <label for="description"><%= message('issue_filter.form.description') -%></label>
+    <input id="description" name="description" type="text" size="50" maxlength="4000" value="<%= h(@filter.description) if @filter  -%>"/>
+  </div>
+  <% if display_owner && can_be_reassigned_by(current_user, @filter) %>
+    <% filter_owner = Api.users.findByLogin(@filter.user) %>
+    <div class="modal-field">
+      <label for="user"><%= h message('issue_filter.form.owner') -%></label>
+      <%= user_select_tag('user', :html_id => 'select-filter-owner', :selected_user => filter_owner) -%>
+    </div>
+  <% else %>
+    <input id="user" name="user" type="hidden" value="<%= h(@filter.user) if @filter -%>"/>
+  <% end %>
+  <% if Internal.issues.canUserShareIssueFilter() %>
+    <div class="modal-field">
+      <% if !@filter || @filter.user.nil? || @filter.user == current_user.login %>
+        <label for="shared"><%= message('issue_filter.form.share') -%></label>
+        <input id="shared" name="shared" type="checkbox" value="true" <%= 'checked' if (@filter && @filter.shared) -%>/>
+      <% else %>
+        <input id="shared" name="shared" type="hidden" value="<%= @filter.shared if @filter -%>"/>
+      <% end %>
+    </div>
+  <% end %>
+</div>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_list.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_list.html.erb
new file mode 100644 (file)
index 0000000..7e07f5f
--- /dev/null
@@ -0,0 +1,126 @@
+<%
+   if @issues_result.issues && !@issues_result.issues.empty?
+    colspan = 6
+%>
+  <div id="issues-list">
+    <table class="data width100">
+      <thead>
+        <tr>
+          <th width="1%" nowrap class="column-severity">
+            <%= column_html(@issues_query, @issues_result, message('severity_abbreviated'), message('severity'), 'SEVERITY') %>
+          </th>
+          <th width="1%" nowrap class="column-status">
+            <%= column_html(@issues_query, @issues_result, message('status'), message('status'), 'STATUS') %>
+          </th>
+          <th>
+            <%= message('description') -%>
+          </th>
+          <th nowrap>
+            <%= message('component') -%>
+          </th>
+          <th class="column-assignee">
+            <%= column_html(@issues_query, @issues_result, message('issue_filter.header.assignee'), message('issue_filter.header.assignee'), 'ASSIGNEE') %>
+          </th>
+          <th width="1%" nowrap>
+            <%= message('issue_filter.header.action_plan') -%>
+          </th>
+          <th width="1%" nowrap class="column-update-date">
+            <%= column_html(@issues_query, @issues_result, message('issue_filter.header.update_date'), message('issue_filter.header.update_date'), 'UPDATE_DATE') %>
+          </th>
+        </tr>
+      </thead>
+      <tbody>
+      <%
+         @issues_result.issues.each do |issue|
+      %>
+        <tr class="<%= cycle('even', 'odd') -%>">
+          <td width="1%" nowrap>
+            <i class="icon-severity-<%= issue.severity.downcase -%>"></i>
+          </td>
+          <td>
+            <%= message("issue.status.#{issue.status}") -%>
+            <% if issue.resolution %>
+              <span class="note" style="white-space: nowrap">[<%= message("issue.resolution.#{issue.resolution}") -%>]</span>
+            <% end %>
+          </td>
+          <td>
+            <a class='open-modal rule-modal issue-detail-link' modal-width='900' href='<%= url_for :controller => 'issue', :action => 'show', :id => issue.key, :modal => true -%>'>
+              <%= h truncate(issue.message, :length => 100) -%></a>
+          </td>
+          <td>
+            <% project = @issues_result.project(issue)
+               component = @issues_result.component(issue) -%>
+            <div class="subtitle"><%= h (truncate(project.name, :length => 100)) -%></div>
+            <% if component %>
+              <!-- Do not display component name when issue is on module -->
+              <% if component.key != project.key %>
+                <%= h component.longName() -%>
+              <% end %>
+            <% else %>
+              <del><%= h issue.componentKey() %></del>
+            <% end %>
+          </td>
+          <td>
+            <%= h @issues_result.user(issue.assignee).name if issue.assignee -%>
+          </td>
+          <td>
+            <%= h @issues_result.actionPlan(issue).name if issue.actionPlanKey() -%>
+          </td>
+          <td width="1%" nowrap>
+            <%= human_short_date(Api::Utils.java_to_ruby_datetime(issue.updateDate())) -%>
+          </td>
+        </tr>
+      <%
+         end
+      %>
+      </tbody>
+      <%= if @ajax_mode
+           paginate_java(@issues_result.paging, :colspan => colspan, :id => 'issue-filter-foot', :include_loading_icon => true,
+                :url_results => url_for({:controller => 'issues', :action => 'search'}.merge(params))) { |label, page_id|
+             link_to_function label, "refreshList('#{@issues_query.sort}', #{@issues_query.asc}, #{page_id})"
+           }
+         else
+           paginate_java(@issues_result.paging, :colspan => colspan, :id => 'issue-filter-foot', :include_loading_icon => true) { |label, page_id|
+               link_to(label, params.merge({:pageIndex => page_id}))
+           }
+        end
+      %>
+    </table>
+  </div>
+<%
+   end
+%>
+
+<script type="text/javascript">
+  var filterCriteria = <%= json_escape(@issues_query_params.to_json) -%>;
+
+  function refreshList(sort, asc, page) {
+    $j('#issue-filter-foot_pages').hide();
+    $j('#issue-filter-foot_loading').show();
+
+    filterCriteria['sort']=sort;
+    filterCriteria['asc']=asc;
+    filterCriteria['pageIndex']=page;
+    var url = baseUrl + '/issues/search?' + $j.param(filterCriteria);
+
+    <% if @ajax_mode %>
+      $j('.issues-content').load(url, function () {
+        // As issues will be loaded after open-modal has been processed by jQuery, we have to process manually modal classes
+        processModal();
+      });
+    <% else %>
+      window.location = url;
+    <% end %>
+    return false;
+  }
+
+  function processModal(){
+    $j('.issues-content .open-modal').modal();
+  }
+
+  <% if @ajax_mode %>
+    $j(document).ready(function() {
+      processModal();
+    });
+  <% end %>
+</script>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations.html.erb
new file mode 100644 (file)
index 0000000..389856f
--- /dev/null
@@ -0,0 +1,53 @@
+<% if @issues_result && @issues_result.maxResultsReached() %>
+  <p class="notes"><%= message('issue_filter.max_results_reached', :params => @issues_result.paging.total()) -%></p>
+<% end %>
+
+<% unless @issues_result.issues && !@issues_result.issues.empty? %>
+  <p class="notes" style="padding: 5px;"><%= message('issue_filter.no_result') -%></p>
+<% end %>
+
+<% if logged_in? && !@first_search %>
+  <div id="issue-filters-operations" class="line-block marginbottom10">
+    <ul class="operations">
+      <% if @filter && @filter.id %>
+        <li><a id="copy" href="<%= url_for :action => 'copy_form', :id => @filter.id -%>" class="link-action open-modal"><%= message('copy') -%></a></li>
+      <% end %>
+      <% if !@unchanged && @filter && @filter.id && @filter.user == current_user.login %>
+        <li>
+          <%= link_to message('save'), params.merge({:action => 'save', :id => @filter.id}), :class => 'link-action', :id => 'save', :method => :post -%>
+        </li>
+      <% end %>
+      <% unless @filter %>
+        <li>
+          <a id="save-as" href="<%= url_for params.merge({:action => 'save_as_form'}) -%>" class="link-action open-modal"><%= message('save_as') -%></a>
+        </li>
+      <% end %>
+      <% if @issues_result.issues && !@issues_result.issues.empty? %>
+        <li class="last">
+          <a id="bulk-change" href="<%= url_for params.merge({:action => 'bulk_change_form'}) -%>"
+             class="link-action open-modal"><%= message('bulk_change') -%></a>
+        </li>
+      <% end %>
+    </ul>
+
+    <% if @filter && @filter.id && @filter.name.present? %>
+    <div class="page_title" id="filter-title">
+      <p>
+        <span class="h3"><%= h @filter.name -%></span>
+          <span class="note">
+            <% if !@filter.shared %>
+              [<%= message 'issue_filter.private' -%>]
+            <% elsif @filter.user==current_user.login %>
+              [<%= message 'issue_filter.shared_with_all_users' -%>]
+            <% elsif @filter.user %>
+              [<%= message 'shared_by' -%> <%= Api.users.findByLogin(@filter.user).name -%>]
+            <% end %>
+          </span>
+        <% if @filter.user == current_user.login %>
+          &nbsp;<a href="<%= url_for :action => 'edit_form', :id => @filter.id -%>" class="open-modal" id="edit-filter"><%= image_tag 'pencil-small.png', :alt => message('edit') -%></a>
+        <% end %>
+        </p>
+    </div>
+    <% end %>
+  </div>
+<% end %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations_ajax.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations_ajax.html.erb
new file mode 100644 (file)
index 0000000..36e6c6d
--- /dev/null
@@ -0,0 +1,12 @@
+<% if logged_in? && @issues_result.issues && !@issues_result.issues.empty? %>
+  <div class="line-block marginbottom10">
+    <div id="issue-filters-operations" style="padding-right: 5px; padding-top: 5px;">
+      <ul class="operations">
+        <li class="last">
+          <a id="bulk-change" href="<%= url_for params.merge({:controller => 'issues', :action => 'bulk_change_form'}) -%>"
+             class="link-action open-modal"><%= message('bulk_change') -%></a>
+        </li>
+      </ul>
+    </div>
+  </div>
+<% end %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search.html.erb
new file mode 100644 (file)
index 0000000..f9d6b02
--- /dev/null
@@ -0,0 +1,4 @@
+<div class="issues-content">
+  <%= render :partial => 'operations' -%>
+  <%= render :partial => 'list' -%>
+</div>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search_ajax.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search_ajax.html.erb
new file mode 100644 (file)
index 0000000..eb1c6bf
--- /dev/null
@@ -0,0 +1,10 @@
+<div class="issues-content">
+  <%
+     if @issues_result.issues && !@issues_result.issues.empty?
+  %>
+    <%= render :partial => 'operations_ajax' -%>
+    <%= render :partial => 'list' -%>
+  <% else %>
+    <%= render :partial => 'shared/no_issues' -%>
+  <% end %>
+</div>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/manage.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/manage.html.erb
new file mode 100644 (file)
index 0000000..619b9f7
--- /dev/null
@@ -0,0 +1,149 @@
+<% content_for :script do %>
+  <script>
+    $j(document).ready(function () {
+      $j(".issue-filter-star").click(function () {
+        var filterId = $j(this).attr('filter-id');
+        var star = $j(this);
+        $j.ajax({
+          type: 'POST',
+          url: baseUrl + "/issues/toggle_fav",
+          data: {id: filterId},
+          success: function (data) {
+            if (data == 'true') {
+              star.removeClass('icon-not-favorite').addClass('icon-favorite');
+              star.attr('title', '<%= escape_javascript message('click_to_remove_from_favorites') -%>');
+            } else {
+              star.removeClass('icon-favorite').addClass('icon-not-favorite');
+              star.attr('title', '<%= escape_javascript message('click_to_add_to_favorites') -%>');
+            }
+            $j('#sidebar-favourites').load(baseUrl + '/issues/favourites');
+          }
+        });
+      });
+    });
+
+    function onSaveAs(data) {
+      window.location = baseUrl + '/issues/search#id=' + data;
+    }
+
+    function onCopy(data) {
+      window.location = baseUrl + '/issues/search#id=' + data;
+    }
+
+    function onEdit(data) {
+      window.location = baseUrl + '/issues/search#id=' + data;
+    }
+  </script>
+<% end %>
+<div>
+  <div class="page">
+    <h1><%= message 'issue_filter.manage.my_filters' -%></h1>
+    <table class="data marginbottom10" id="my-issue-filters">
+      <thead>
+      <tr>
+        <th class="thin"></th>
+        <th><%= message('issue_filter.form.name') -%></th>
+        <th><%= message('issue_filter.sharing') -%></th>
+        <th class="right"><%= message('operations') -%></th>
+      </tr>
+      </thead>
+      <tbody>
+      <% if @filters.empty? %>
+        <tr class="even">
+          <td colspan="4"><%= message('issue_filter.no_filters') -%></td>
+        </tr>
+      <% else %>
+        <% @filters.each do |filter| %>
+          <tr id="my-issue-filter-<%= filter.name.parameterize -%>" class="<%= cycle('even', 'odd', :name => 'my-filters') -%>">
+            <td>
+              <%= issue_filter_star(filter, @favourite_filter_ids.include?(filter.id)) -%>
+            </td>
+            <td>
+              <a href="<%= ApplicationController.root_context -%>/issues/search#id=<%= h filter.id -%>|<%= h filter.data -%>"><%= h filter.name -%></a>
+              <% if filter.description %>
+                <div class="note"><%= h filter.description -%></div>
+              <% end %>
+            </td>
+            <td>
+              <% if filter.shared %>
+                <%= message 'issue_filter.shared_with_all_users' -%>
+              <% else %>
+                <%= message 'issue_filter.private' -%>
+              <% end %>
+            </td>
+            <td class="thin nowrap right">
+              <a id="copy-<%= filter.name.parameterize -%>" href="<%= ApplicationController.root_context -%>/issues/copy_form/<%= filter.id -%>"
+                 class="link-action open-modal"><%= message('copy') -%></a>
+              &nbsp;
+              <a id="edit_<%= filter.name.parameterize -%>" href="<%= ApplicationController.root_context -%>/issues/edit_form/<%= filter.id -%>"
+                 class="link-action open-modal"><%= message('edit') -%></a>
+              &nbsp;
+              <%= link_to_action message('delete'), "#{ApplicationController.root_context}/issues/delete/#{filter.id}",
+                                 :class => 'link-action link-red',
+                                 :id => "delete_#{filter.name.parameterize}",
+                                 :confirm_button => message('delete'),
+                                 :confirm_title => 'issue_filter.delete_confirm_title',
+                                 :confirm_msg => 'issue_filter.are_you_sure_want_delete_filter_x',
+                                 :confirm_msg_params => [filter.name] -%>
+            </td>
+          </tr>
+        <% end %>
+      <% end %>
+      </tbody>
+    </table>
+
+    <br/>
+
+    <h1><%= message 'issue_filter.manage.shared_filters' -%></h1>
+    <table class="data" id="shared-filters">
+      <thead>
+      <tr>
+        <th class="thin"></th>
+        <th><%= message('issue_filter.form.name') -%></th>
+        <th><%= message('shared_by') -%></th>
+        <th class="right"><%= message('operations') -%></th>
+      </tr>
+      </thead>
+      <tbody>
+      <% if @shared_filters.empty? %>
+        <tr class="even">
+          <td colspan="4"><%= message('issue_filter.no_filters') -%></td>
+        </tr>
+      <% else %>
+        <% @shared_filters.each do |filter| %>
+          <tr id="shared-<%= filter.name.parameterize -%>" class="<%= cycle('even', 'odd', :name => 'shared-filters') -%>">
+            <td>
+              <%= issue_filter_star(filter, @favourite_filter_ids.include?(filter.id)) -%>
+            </td>
+            <td>
+              <a href="<%= ApplicationController.root_context -%>/issues/search#id=<%= h filter.id -%>|<%= h filter.data -%>"><%= h filter.name -%></a>
+              <% if filter.description %>
+                <div class="note"><%= h filter.description -%></div>
+              <% end %>
+            </td>
+            <td>
+              <%= h Api.users.findByLogin(filter.user).name -%>
+            </td>
+            <td class="thin nowrap right">
+              <a id="copy-<%= filter.name.parameterize -%>" href="<%= ApplicationController.root_context -%>/issues/copy_form/<%= filter.id -%>" class="link-action open-modal"><%= message('copy') -%></a>
+              <% if has_role?(:admin) %>
+                &nbsp;
+                <a id="edit_shared_<%= filter.name.parameterize -%>" href="<%= ApplicationController.root_context -%>/issues/edit_form/<%= filter.id -%>" class="link-action open-modal"><%= message('edit') -%></a>
+                &nbsp;
+                <%= link_to_action message('delete'), "#{ApplicationController.root_context}/issues/delete/#{filter.id}",
+                                   :class => 'link-action link-red',
+                                   :id => "delete_system_#{filter.name.parameterize}",
+                                   :confirm_button => message('delete'),
+                                   :confirm_title => 'issue_filter.delete_confirm_title',
+                                   :confirm_msg => 'issue_filter.are_you_sure_want_delete_filter_x',
+                                   :confirm_msg_params => [filter.name] -%>
+              <% end %>
+            </td>
+          </tr>
+        <% end %>
+      <% end %>
+      </tbody>
+    </table>
+
+  </div>
+</div>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/search.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/search.html.erb
new file mode 100644 (file)
index 0000000..9cd9535
--- /dev/null
@@ -0,0 +1,257 @@
+<% content_for :script do %>
+  <script data-main="<%= ApplicationController.root_context -%>/js/issues-old/app" src="<%= ApplicationController.root_context -%>/js/require.js"></script>
+<% end %>
+
+<div class="navigator">
+    <div class="navigator-header"></div>
+    <div class="navigator-filters"></div>
+
+    <div class="navigator-content">
+        <div class="navigator-side">
+            <div style="position:relative; overflow: visible; height: 100%;">
+                <div class="navigator-actions"></div>
+                <div class="navigator-results"></div>
+                <a class="navigator-resizer"><i class="icon-resizer"></i></a>
+            </div>
+        </div>
+        <div class="navigator-main">
+            <div class="navigator-notes"><%= message('issue_filter.max_results_reached', :params => 10000) -%></div>
+            <div class="navigator-details"></div>
+        </div>
+    </div>
+</div>
+
+
+<script>
+  window.SS = {};
+
+  _.extend(window.SS, {
+    currentUser: '<%= current_user.login if current_user -%>',
+    currentUserName: '<%= current_user.name if current_user -%>',
+    severities: <%= RulesConfigurationController::RULE_PRIORITIES.to_json.html_safe -%>,
+    statuses: <%= @options_for_statuses.to_json.html_safe -%>,
+    resolutions: <%= @options_for_resolutions.to_json.html_safe -%>,
+    favorites: <%= render :partial => 'issues/filter_favourites' -%>,
+    languages: {<% controller.java_facade.getLanguages().to_a.sort {|a,b| a.getName() <=> b.getName()}.each do |language| -%>
+      '<%= language.getKey().html_safe -%>': '<%= language.getName().html_safe -%>',
+      <% end %>},
+
+    phrases: {
+      'actionPlan':    '<%= escape_javascript message('issue_filter.criteria.actionPlan') -%>',
+      'actionPlanNotAvailable': '<%= escape_javascript message('issue_filter.criteria.actionPlanNotAvailable') -%>',
+      'any':           '<%= escape_javascript message('any') -%>',
+      'anytime':       '<%= escape_javascript message('anytime') -%>',
+      'all':           '<%= escape_javascript message('all') -%>',
+      'edit':          '<%= escape_javascript message('edit') -%>',
+      'delete':        '<%= escape_javascript message('delete') -%>',
+      'to':            '<%= escape_javascript message('to.downcase') -%>',
+      'project':       '<%= escape_javascript message('issue_filter.criteria.project') -%>',
+      'language':       '<%= escape_javascript message('language') -%>',
+      'severity':      '<%= escape_javascript message('issue_filter.criteria.severity') -%>',
+      'severities': {
+        BLOCKER:       '<%= escape_javascript message('severity.BLOCKER') -%>',
+        CRITICAL:      '<%= escape_javascript message('severity.CRITICAL') -%>',
+        MAJOR:         '<%= escape_javascript message('severity.MAJOR') -%>',
+        MINOR:         '<%= escape_javascript message('severity.MINOR') -%>',
+        INFO:          '<%= escape_javascript message('severity.INFO') -%>'
+      },
+      'status':        '<%= escape_javascript message('issue_filter.criteria.status') -%>',
+      'statuses': {
+        OPEN:          '<%= escape_javascript message('issue.status.OPEN') -%>',
+        CONFIRMED:     '<%= escape_javascript message('issue.status.CONFIRMED') -%>',
+        REOPENED:      '<%= escape_javascript message('issue.status.REOPENED') -%>',
+        RESOLVED:      '<%= escape_javascript message('issue.status.RESOLVED') -%>',
+        CLOSED:        '<%= escape_javascript message('issue.status.CLOSED') -%>'
+      },
+      'actions': {
+        comment:        '<%= escape_javascript message('issue.comment.formlink') -%>',
+        assign:         '<%= escape_javascript message('issue.assign.formlink') -%>',
+        assign_to_me:   '<%= escape_javascript message('issue.assign.to_me') -%>',
+        assigned_to:    '<%= escape_javascript message('assigned_to') -%>',
+        plan:           '<%= escape_javascript message('issue.do_plan') -%>',
+        planned_for:    '<%= escape_javascript message('issue.planned_for') -%>',
+        set_severity:   '<%= escape_javascript message('issue.set_severity') -%>',
+        <%  Internal.issues.listPluginActions().each do |action| -%>
+        '<%= escape_javascript action -%>': '<%= escape_javascript message("issue.action.#{action}.formlink") -%>',
+        <% end -%>
+      },
+      'transitions': {
+        confirm:        '<%= escape_javascript message('issue.transition.confirm') -%>',
+        unconfirm:      '<%= escape_javascript message('issue.transition.unconfirm') -%>',
+        resolve:        '<%= escape_javascript message('issue.transition.resolve') -%>',
+        falsepositive:  '<%= escape_javascript message('issue.transition.falsepositive') -%>',
+        reopen:         '<%= escape_javascript message('issue.transition.reopen') -%>',
+        close:          '<%= escape_javascript message('issue.transition.close') -%>'
+      },
+      assignee:        '<%= escape_javascript message('issue_filter.criteria.assignee') -%>',
+      resolution:      '<%= escape_javascript message('issue_filter.criteria.resolution') -%>',
+      resolutions: {
+        'RESOLVED':  '<%= escape_javascript message('issue.status.RESOLVED') -%>',
+        'UNRESOLVED':  '<%= escape_javascript message('unresolved') -%>',
+        'FALSE-POSITIVE': '<%= escape_javascript message('issue.resolution.FALSE-POSITIVE') -%>',
+        'FIXED':       '<%= escape_javascript message('issue.resolution.FIXED') -%>',
+        'REMOVED':     '<%= escape_javascript message('issue.resolution.REMOVED') -%>'
+      },
+      reporter:        '<%= escape_javascript message('issue_filter.criteria.reporter') -%>',
+      rule:            '<%= escape_javascript message('issue_filter.criteria.rule') -%>',
+      created:         '<%= escape_javascript message('issue_filter.criteria.created') -%>',
+      createdAt:       '<%= escape_javascript message('issue_filter.criteria.created_at') -%>',
+
+      moreCriteria:    '<%= escape_javascript message('issue_filter.more_criteria') -%>',
+      unassigned:      '<%= escape_javascript message('unassigned') -%>',
+      unplanned:       '<%= escape_javascript message('issue.unplanned') -%>',
+      assignedToMe:    '<%= escape_javascript message('assigned_to_me') -%>',
+      filtersList:     '<%= escape_javascript message('issue_filter.filter_list') -%>',
+      commentConfirmDelete:  '<%= escape_javascript message('issue.comment.delete_confirm_message') -%>',
+      requirementRemoved:    '<%= escape_javascript message('issue.technical_debt_deleted') -%>',
+
+      select2: {
+        noMatches: '<%= escape_javascript message('select2.noMatches') -%>',
+        searching: '<%= escape_javascript message('select2.searching') -%>',
+        tooShort: '<%= escape_javascript message('select2.tooShort', :params => [2]) -%>'
+      },
+      'Done': '<%= escape_javascript message("Done") -%>',
+      'Prev': '<%= escape_javascript message("Prev") -%>',
+      'Next': '<%= escape_javascript message("Next") -%>',
+      'Today': '<%= escape_javascript message("Today") -%>',
+      'January': '<%= escape_javascript message("January") -%>',
+      'February': '<%= escape_javascript message("February") -%>',
+      'March': '<%= escape_javascript message("March") -%>',
+      'April': '<%= escape_javascript message("April") -%>',
+      'May': '<%= escape_javascript message("May") -%>',
+      'June': '<%= escape_javascript message("June") -%>',
+      'July': '<%= escape_javascript message("July") -%>',
+      'August': '<%= escape_javascript message("August") -%>',
+      'September': '<%= escape_javascript message("September") -%>',
+      'October': '<%= escape_javascript message("October") -%>',
+      'November': '<%= escape_javascript message("November") -%>',
+      'December': '<%= escape_javascript message("December") -%>',
+      'Jan': '<%= escape_javascript message("Jan") -%>',
+      'Feb': '<%= escape_javascript message("Feb") -%>',
+      'Mar': '<%= escape_javascript message("Mar") -%>',
+      'Apr': '<%= escape_javascript message("Apr") -%>',
+      'Jun': '<%= escape_javascript message("Jun") -%>',
+      'Jul': '<%= escape_javascript message("Jul") -%>',
+      'Aug': '<%= escape_javascript message("Aug") -%>',
+      'Sep': '<%= escape_javascript message("Sep") -%>',
+      'Oct': '<%= escape_javascript message("Oct") -%>',
+      'Nov': '<%= escape_javascript message("Nov") -%>',
+      'Dec': '<%= escape_javascript message("Dec") -%>',
+      'Sunday': '<%= escape_javascript message("Sunday") -%>',
+      'Monday': '<%= escape_javascript message("Monday") -%>',
+      'Tuesday': '<%= escape_javascript message("Tuesday") -%>',
+      'Wednesday': '<%= escape_javascript message("Wednesday") -%>',
+      'Thursday': '<%= escape_javascript message("Thursday") -%>',
+      'Friday': '<%= escape_javascript message("Friday") -%>',
+      'Saturday': '<%= escape_javascript message("Saturday") -%>',
+      'Sun': '<%= escape_javascript message("Sun") -%>',
+      'Mon': '<%= escape_javascript message("Mon") -%>',
+      'Tue': '<%= escape_javascript message("Tue") -%>',
+      'Wed': '<%= escape_javascript message("Wed") -%>',
+      'Thu': '<%= escape_javascript message("Thu") -%>',
+      'Fri': '<%= escape_javascript message("Fri") -%>',
+      'Sat': '<%= escape_javascript message("Sat") -%>',
+      'Su': '<%= escape_javascript message("Su") -%>',
+      'Mo': '<%= escape_javascript message("Mo") -%>',
+      'Tu': '<%= escape_javascript message("Tu") -%>',
+      'We': '<%= escape_javascript message("We") -%>',
+      'Th': '<%= escape_javascript message("Th") -%>',
+      'Fr': '<%= escape_javascript message("Fr") -%>',
+      'Sa': '<%= escape_javascript message("Sa") -%>'
+    }
+  });
+
+  window.messages = {
+    'all': '<%= escape_javascript message('all') -%>',
+    'assigned_to': '<%= escape_javascript message('assigned_to') -%>',
+    'bulk_change': '<%= escape_javascript message('bulk_change') -%>',
+    'issue.planned_for': '<%= escape_javascript message('issue.planned_for') -%>',
+    'issue.do_plan': '<%= escape_javascript message('issue.do_plan') -%>',
+    'issue.planned_for': '<%= escape_javascript message('issue.planned_for') -%>',
+    'more_actions': '<%= escape_javascript message('more_actions') -%>',
+    'reporter': '<%= escape_javascript message('reporter') -%>',
+    'author': '<%= escape_javascript message('author') -%>',
+    'cancel': '<%= escape_javascript message('cancel') -%>',
+    'save': '<%= escape_javascript message('save') -%>',
+    'issue.technical_debt_short': '<%= escape_javascript message('issue.technical_debt_short') -%>',
+    'issue.technical_debt_deleted': '<%= escape_javascript message('issue.technical_debt_deleted') -%>',
+    'rule': '<%= escape_javascript message('rule') -%>',
+    'changelog': '<%= escape_javascript message('changelog') -%>',
+    'edit': '<%= escape_javascript message('edit') -%>',
+    'delete': '<%= escape_javascript message('delete') -%>',
+    'moreCriteria': '<%= escape_javascript message('issue_filter.more_criteria') -%>',
+    'issue.plan.submit': '<%= escape_javascript message('issue.plan.submit') -%>',
+    'issue.unplan.submit': '<%= escape_javascript message('issue.unplan.submit') -%>',
+    'issue.assign.submit': '<%= escape_javascript message('issue.assign.submit') -%>',
+    'issue.set_severity.submit': '<%= escape_javascript message('issue.set_severity.submit') -%>',
+    'issue.comment.submit': '<%= escape_javascript message('issue.comment.submit') -%>',
+    'issue.plan.error.plan_must_be_created_first_for_admin': '<%= escape_javascript message('issue.plan.error.plan_must_be_created_first_for_admin') -%>',
+    'issue.plan.error.plan_must_be_created_first_for_other': '<%= escape_javascript message('issue.plan.error.plan_must_be_created_first_for_other') -%>',
+    'markdown.helplink': '<%= escape_javascript message('markdown.helplink') -%>',
+    'bold': '<%= escape_javascript message('bold') -%>',
+    'code': '<%= escape_javascript message('code') -%>',
+    'bulleted_point': '<%= escape_javascript message('bulleted_point') -%>',
+    'severity.BLOCKER': '<%= escape_javascript message('severity.BLOCKER') -%>',
+    'severity.CRITICAL': '<%= escape_javascript message('severity.CRITICAL') -%>',
+    'severity.MAJOR': '<%= escape_javascript message('severity.MAJOR') -%>',
+    'severity.MINOR': '<%= escape_javascript message('severity.MINOR') -%>',
+    'severity.INFO': '<%= escape_javascript message('severity.INFO') -%>',
+
+    'component_viewer.measure_section.size': '<%= escape_javascript message('component_viewer.measure_section.size') -%>',
+    'component_viewer.measure_section.complexity': '<%= escape_javascript message('component_viewer.measure_section.complexity') -%>',
+    'component_viewer.measure_section.structure': '<%= escape_javascript message('component_viewer.measure_section.structure') -%>',
+    'component_viewer.measure_section.documentation': '<%= escape_javascript message('component_viewer.measure_section.documentation') -%>',
+    'component_viewer.measure_section.severities': '<%= escape_javascript message('component_viewer.measure_section.severities') -%>',
+    'component_viewer.measure_section.rules': '<%= escape_javascript message('component_viewer.measure_section.rules') -%>',
+    'component_viewer.measure_section.issues': '<%= escape_javascript message('component_viewer.measure_section.issues') -%>',
+    'component_viewer.measure_section.sqale': '<%= escape_javascript message('component_viewer.measure_section.sqale') -%>',
+    'component_viewer.measure_section.unit_tests': '<%= escape_javascript message('component_viewer.measure_section.unit_tests') -%>',
+    'component_viewer.measure_section.integration_tests': '<%= escape_javascript message('component_viewer.measure_section.integration_tests') -%>',
+
+    'component_viewer.issues.current_issue': '<%= escape_javascript message('component_viewer.issues.current_issue') -%>',
+    'component_viewer.issues.unresolved_issues': '<%= escape_javascript message('component_viewer.issues.unresolved_issues') -%>',
+    'component_viewer.issues.false_positive_issues': '<%= escape_javascript message('component_viewer.issues.false_positive_issues') -%>',
+
+    'component_viewer.header.debt': '<%= escape_javascript message('component_viewer.header.debt') -%>',
+    'component_viewer.header.toggle_issues': '<%= escape_javascript message('component_viewer.header.toggle_issues') -%>',
+    'component_viewer.header.toggle_coverage': '<%= escape_javascript message('component_viewer.header.toggle_coverage') -%>',
+    'component_viewer.header.toggle_duplications': '<%= escape_javascript message('component_viewer.header.toggle_duplications') -%>',
+
+    'metric.lines.name': '<%= escape_javascript message('metric.lines.name') -%>',
+    'metric.ncloc.name': '<%= escape_javascript message('metric.ncloc.name') -%>',
+    'metric.complexity.name': '<%= escape_javascript message('metric.complexity.name') -%>',
+    'metric.function_complexity.name': '<%= escape_javascript message('metric.function_complexity.name') -%>',
+    'metric.classes.name': '<%= escape_javascript message('metric.classes.name') -%>',
+    'metric.functions.name': '<%= escape_javascript message('metric.functions.name') -%>',
+    'metric.accessors.name': '<%= escape_javascript message('metric.accessors.name') -%>',
+    'metric.statements.name': '<%= escape_javascript message('metric.statements.name') -%>',
+    'metric.comment_lines.name': '<%= escape_javascript message('metric.comment_lines.name') -%>',
+    'metric.comment_lines_density.name': '<%= escape_javascript message('metric.comment_lines_density.name') -%>',
+    'metric.public_api.name': '<%= escape_javascript message('metric.public_api.name') -%>',
+    'metric.public_undocumented_api.name': '<%= escape_javascript message('metric.public_undocumented_api.name') -%>',
+    'metric.public_documented_api_density.name': '<%= escape_javascript message('metric.public_documented_api_density.name') -%>',
+
+    'metric.coverage.name': '<%= escape_javascript message('metric.coverage.name') -%>',
+    'metric.line_coverage.name': '<%= escape_javascript message('metric.line_coverage.name') -%>',
+    'metric.lines_to_cover.name': '<%= escape_javascript message('metric.lines_to_cover.name') -%>',
+    'metric.covered_lines.name': '<%= escape_javascript message('metric.covered_lines.name') -%>',
+    'metric.uncovered_lines.name': '<%= escape_javascript message('metric.uncovered_lines.name') -%>',
+    'metric.branch_coverage.name': '<%= escape_javascript message('metric.branch_coverage.name') -%>',
+    'metric.conditions_to_cover.name': '<%= escape_javascript message('metric.conditions_to_cover.name') -%>',
+    'metric.covered_conditions.name': '<%= escape_javascript message('metric.covered_conditions.name') -%>',
+    'metric.uncovered_conditions.name': '<%= escape_javascript message('metric.uncovered_conditions.name') -%>',
+
+    'metric.duplicated_blocks.name': '<%= escape_javascript message('metric.duplicated_blocks.name') -%>',
+    'metric.duplicated_files.name': '<%= escape_javascript message('metric.duplicated_files.name') -%>',
+    'metric.duplicated_lines.name': '<%= escape_javascript message('metric.duplicated_lines.name') -%>',
+    'metric.duplicated_lines_density.name': '<%= escape_javascript message('metric.duplicated_lines_density.name') -%>',
+
+    'metric.violations.name': '<%= escape_javascript message('metric.violations.name') -%>',
+    'metric.sqale_index.name': '<%= escape_javascript message('metric.sqale_index.name') -%>',
+    'metric.blocker_violations.name': '<%= escape_javascript message('metric.blocker_violations.name') -%>',
+    'metric.critical_violations.name': '<%= escape_javascript message('metric.critical_violations.name') -%>',
+    'metric.major_violations.name': '<%= escape_javascript message('metric.major_violations.name') -%>',
+    'metric.minor_violations.name': '<%= escape_javascript message('metric.minor_violations.name') -%>',
+    'metric.info_violations.name': '<%= escape_javascript message('metric.info_violations.name') -%>'
+  };
+</script>
index fd4844050c6c6090670b1e8f95398ad4352ecd4c..ecf608e904f03af0c6ce710b71d1a190d9236308 100644 (file)
@@ -21,6 +21,9 @@
         <li>
           <a href="<%= ApplicationController.root_context -%>/issues/index" class="<%= 'selected' if selected_section==Navigation::SECTION_ISSUES -%>"><%= message('issues.page') -%></a>
         </li>
+        <li>
+            <a href="<%= ApplicationController.root_context -%>/issues2/index" class="<%= 'selected' if selected_section==Navigation::SECTION_ISSUES2 -%>">Old Issues</a>
+        </li>
         <li>
           <a href="<%= ApplicationController.root_context -%>/coding_rules" class="<%= 'selected' if selected_section==Navigation::SECTION_CODING_RULES -%>"><%= message('coding_rules.page') -%></a>
         </li>
index 1bb662b5f33eb63945d43f9fd6f069fa74de47a0..934006976bef041b6ed670a76da182676051821b 100644 (file)
@@ -755,6 +755,22 @@ issues.ordered_by=Ordered by
 issues.found=Found
 
 
+#------------------------------------------------------------------------------
+#
+# ISSUES FACETS
+#
+#------------------------------------------------------------------------------
+issues.facet.severities=Severity
+issues.facet.componentRootUuids=Project
+issues.facet.statuses=Status
+issues.facet.actionPlans=Action Plan
+issues.facet.assignees=Assignee
+issues.facet.components=Component
+issues.facet.rules=Rule
+issues.facet.resolutions=Resolution
+issues.facet.languages=Language
+
+
 #------------------------------------------------------------------------------
 #
 # ISSUE BULK CHANGE