aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2014-10-29 17:56:05 +0100
committerStas Vilchik <vilchiks@gmail.com>2014-10-29 18:05:31 +0100
commitb716af32093789a36c31996c2cf0e3eabf407c71 (patch)
treee5274f642ccdf04e753f5b339b9182e1ddd8b269 /server/sonar-web
parent253c3b75e65786c61505ab241c981a38c917ba18 (diff)
downloadsonarqube-b716af32093789a36c31996c2cf0e3eabf407c71.tar.gz
sonarqube-b716af32093789a36c31996c2cf0e3eabf407c71.zip
SONAR-5718 Add a new issues page
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/Gruntfile.coffee3
-rw-r--r--server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-facets-view.coffee4
-rw-r--r--server/sonar-web/src/main/coffee/issue/collections/issues.coffee46
-rw-r--r--server/sonar-web/src/main/coffee/issue/issue-view.coffee1
-rw-r--r--server/sonar-web/src/main/coffee/issues/app.coffee117
-rw-r--r--server/sonar-web/src/main/coffee/issues/component-viewer/main.coffee191
-rw-r--r--server/sonar-web/src/main/coffee/issues/component-viewer/state.coffee12
-rw-r--r--server/sonar-web/src/main/coffee/issues/controller.coffee163
-rw-r--r--server/sonar-web/src/main/coffee/issues/facets-view.coffee43
-rw-r--r--server/sonar-web/src/main/coffee/issues/facets/assignee-facet.coffee52
-rw-r--r--server/sonar-web/src/main/coffee/issues/facets/base-facet.coffee52
-rw-r--r--server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee42
-rw-r--r--server/sonar-web/src/main/coffee/issues/facets/project-facet.coffee25
-rw-r--r--server/sonar-web/src/main/coffee/issues/facets/resolution-facet.coffee43
-rw-r--r--server/sonar-web/src/main/coffee/issues/facets/rule-facet.coffee25
-rw-r--r--server/sonar-web/src/main/coffee/issues/facets/severity-facet.coffee22
-rw-r--r--server/sonar-web/src/main/coffee/issues/facets/status-facet.coffee21
-rw-r--r--server/sonar-web/src/main/coffee/issues/filters-view.coffee77
-rw-r--r--server/sonar-web/src/main/coffee/issues/issue-view.coffee11
-rw-r--r--server/sonar-web/src/main/coffee/issues/layout.coffee63
-rw-r--r--server/sonar-web/src/main/coffee/issues/models/facet.coffee22
-rw-r--r--server/sonar-web/src/main/coffee/issues/models/facets.coffee10
-rw-r--r--server/sonar-web/src/main/coffee/issues/models/filter.coffee14
-rw-r--r--server/sonar-web/src/main/coffee/issues/models/filters.coffee10
-rw-r--r--server/sonar-web/src/main/coffee/issues/models/issues.coffee61
-rw-r--r--server/sonar-web/src/main/coffee/issues/models/state.coffee42
-rw-r--r--server/sonar-web/src/main/coffee/issues/router.coffee32
-rw-r--r--server/sonar-web/src/main/coffee/issues/workspace-header-view.coffee70
-rw-r--r--server/sonar-web/src/main/coffee/issues/workspace-list-item-view.coffee42
-rw-r--r--server/sonar-web/src/main/coffee/issues/workspace-list-view.coffee93
-rw-r--r--server/sonar-web/src/main/hbs/coding-rules/coding-rules-facets.hbs6
-rw-r--r--server/sonar-web/src/main/hbs/component-viewer/cw-layout.hbs2
-rw-r--r--server/sonar-web/src/main/hbs/issues-old/filter-bar.hbs (renamed from server/sonar-web/src/main/hbs/issues/filter-bar.hbs)0
-rw-r--r--server/sonar-web/src/main/hbs/issues-old/issue-detail.hbs (renamed from server/sonar-web/src/main/hbs/issues/issue-detail.hbs)0
-rw-r--r--server/sonar-web/src/main/hbs/issues-old/issues-actions.hbs (renamed from server/sonar-web/src/main/hbs/issues/issues-actions.hbs)0
-rw-r--r--server/sonar-web/src/main/hbs/issues-old/issues-details-favorite-filter.hbs (renamed from server/sonar-web/src/main/hbs/issues/issues-details-favorite-filter.hbs)0
-rw-r--r--server/sonar-web/src/main/hbs/issues-old/issues-header.hbs (renamed from server/sonar-web/src/main/hbs/issues/issues-header.hbs)0
-rw-r--r--server/sonar-web/src/main/hbs/issues-old/issues.hbs (renamed from server/sonar-web/src/main/hbs/issues/issues.hbs)0
-rw-r--r--server/sonar-web/src/main/hbs/issues-old/no-issues.hbs (renamed from server/sonar-web/src/main/hbs/issues/no-issues.hbs)0
-rw-r--r--server/sonar-web/src/main/hbs/issues/facets/_issues-facet-header.hbs4
-rw-r--r--server/sonar-web/src/main/hbs/issues/facets/issues-assignee-facet.hbs18
-rw-r--r--server/sonar-web/src/main/hbs/issues/facets/issues-base-facet.hbs10
-rw-r--r--server/sonar-web/src/main/hbs/issues/facets/issues-creation-date-facet.hbs9
-rw-r--r--server/sonar-web/src/main/hbs/issues/facets/issues-resolution-facet.hbs18
-rw-r--r--server/sonar-web/src/main/hbs/issues/facets/issues-severity-facet.hbs10
-rw-r--r--server/sonar-web/src/main/hbs/issues/facets/issues-status-facet.hbs10
-rw-r--r--server/sonar-web/src/main/hbs/issues/issues-component-viewer.hbs18
-rw-r--r--server/sonar-web/src/main/hbs/issues/issues-filters.hbs34
-rw-r--r--server/sonar-web/src/main/hbs/issues/issues-issue.hbs142
-rw-r--r--server/sonar-web/src/main/hbs/issues/issues-layout.hbs10
-rw-r--r--server/sonar-web/src/main/hbs/issues/issues-workspace-header.hbs21
-rw-r--r--server/sonar-web/src/main/hbs/issues/issues-workspace-list-item.hbs24
-rw-r--r--server/sonar-web/src/main/hbs/issues/issues-workspace-list.hbs5
-rw-r--r--server/sonar-web/src/main/js/common/handlebars-extensions.js12
-rw-r--r--server/sonar-web/src/main/js/issues-old/app.js (renamed from server/sonar-web/src/main/js/issues/app.js)2
-rw-r--r--server/sonar-web/src/main/js/issues-old/extra.js (renamed from server/sonar-web/src/main/js/issues/extra.js)2
-rw-r--r--server/sonar-web/src/main/less/component-viewer-source-colorizer.less67
-rw-r--r--server/sonar-web/src/main/less/component-viewer.less92
-rw-r--r--server/sonar-web/src/main/less/components.less3
-rw-r--r--server/sonar-web/src/main/less/components/code-source.less92
-rw-r--r--server/sonar-web/src/main/less/components/component-issues.less24
-rw-r--r--server/sonar-web/src/main/less/components/facets.less52
-rw-r--r--server/sonar-web/src/main/less/icons.less18
-rw-r--r--server/sonar-web/src/main/less/issues.less302
-rw-r--r--server/sonar-web/src/main/less/navigator/base.less43
-rw-r--r--server/sonar-web/src/main/less/style.less3
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issues2_controller.rb75
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/models/navigation.rb1
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/search.html.erb254
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_bulk_change_form.html.erb161
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_copy_form.html.erb20
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_edit_form.html.erb20
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_favourites.html.erb9
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_save_as_form.html.erb20
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_shared_form.html.erb33
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_list.html.erb126
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations.html.erb53
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations_ajax.html.erb12
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search.html.erb4
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search_ajax.html.erb10
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/manage.html.erb149
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/search.html.erb257
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb3
83 files changed, 3228 insertions, 441 deletions
diff --git a/server/sonar-web/Gruntfile.coffee b/server/sonar-web/Gruntfile.coffee
index 69b39a66782..5892d28aa23 100644
--- a/server/sonar-web/Gruntfile.coffee
+++ b/server/sonar-web/Gruntfile.coffee
@@ -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'
]
diff --git a/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-facets-view.coffee b/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-facets-view.coffee
index 55b3f0b5720..a83094f7084 100644
--- a/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-facets-view.coffee
+++ b/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-facets-view.coffee
@@ -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
index 00000000000..7dc4da76734
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issue/collections/issues.coffee
@@ -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
diff --git a/server/sonar-web/src/main/coffee/issue/issue-view.coffee b/server/sonar-web/src/main/coffee/issue/issue-view.coffee
index 2be7d3120e2..1618fe8d26a 100644
--- a/server/sonar-web/src/main/coffee/issue/issue-view.coffee
+++ b/server/sonar-web/src/main/coffee/issue/issue-view.coffee
@@ -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
index 00000000000..29bcf6fee9a
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/app.coffee
@@ -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
index 00000000000..a51aaf116c4
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/component-viewer/main.coffee
@@ -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
index 00000000000..45e46c2b32a
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/component-viewer/state.coffee
@@ -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
index 00000000000..a1a05844c57
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/controller.coffee
@@ -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
index 00000000000..e1e6e121a78
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/facets-view.coffee
@@ -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
index 00000000000..0c922e9ba5e
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/facets/assignee-facet.coffee
@@ -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
index 00000000000..190028b9326
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/facets/base-facet.coffee
@@ -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
index 00000000000..5979ba1f977
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee
@@ -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
index 00000000000..4b4495000ed
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/facets/project-facet.coffee
@@ -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
index 00000000000..a51aa4ab796
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/facets/resolution-facet.coffee
@@ -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
index 00000000000..df5b544bb9a
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/facets/rule-facet.coffee
@@ -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
index 00000000000..2f7dac47911
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/facets/severity-facet.coffee
@@ -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
index 00000000000..ced0a9dedfb
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/facets/status-facet.coffee
@@ -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
index 00000000000..1d9f9e9eaed
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/filters-view.coffee
@@ -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
index 00000000000..624327d2672
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/issue-view.coffee
@@ -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
index 00000000000..c0649f25b4c
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/layout.coffee
@@ -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
index 00000000000..fa7cb817441
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/models/facet.coffee
@@ -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
index 00000000000..b0be4ad3c3c
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/models/facets.coffee
@@ -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
index 00000000000..bbb881f924e
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/models/filter.coffee
@@ -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
index 00000000000..b6c8f9dc59f
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/models/filters.coffee
@@ -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
index 00000000000..7174163359e
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/models/issues.coffee
@@ -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
index 00000000000..6ef3f104983
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/models/state.coffee
@@ -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
index 00000000000..b89b9e5ace3
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/router.coffee
@@ -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
index 00000000000..fd2e92e439a
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/workspace-header-view.coffee
@@ -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
index 00000000000..1d5f67c4df1
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/workspace-list-item-view.coffee
@@ -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
index 00000000000..18413b1dff2
--- /dev/null
+++ b/server/sonar-web/src/main/coffee/issues/workspace-list-view.coffee
@@ -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
+
+
diff --git a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-facets.hbs b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-facets.hbs
index d2ea9d4fd56..c274ffff513 100644
--- a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-facets.hbs
+++ b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-facets.hbs
@@ -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}}
diff --git a/server/sonar-web/src/main/hbs/component-viewer/cw-layout.hbs b/server/sonar-web/src/main/hbs/component-viewer/cw-layout.hbs
index a010e4c4803..088eadcf56b 100644
--- a/server/sonar-web/src/main/hbs/component-viewer/cw-layout.hbs
+++ b/server/sonar-web/src/main/hbs/component-viewer/cw-layout.hbs
@@ -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/filter-bar.hbs b/server/sonar-web/src/main/hbs/issues-old/filter-bar.hbs
index 4f1907964c7..4f1907964c7 100644
--- a/server/sonar-web/src/main/hbs/issues/filter-bar.hbs
+++ b/server/sonar-web/src/main/hbs/issues-old/filter-bar.hbs
diff --git a/server/sonar-web/src/main/hbs/issues/issue-detail.hbs b/server/sonar-web/src/main/hbs/issues-old/issue-detail.hbs
index c746f72148c..c746f72148c 100644
--- a/server/sonar-web/src/main/hbs/issues/issue-detail.hbs
+++ b/server/sonar-web/src/main/hbs/issues-old/issue-detail.hbs
diff --git a/server/sonar-web/src/main/hbs/issues/issues-actions.hbs b/server/sonar-web/src/main/hbs/issues-old/issues-actions.hbs
index 351d6d4d25c..351d6d4d25c 100644
--- a/server/sonar-web/src/main/hbs/issues/issues-actions.hbs
+++ b/server/sonar-web/src/main/hbs/issues-old/issues-actions.hbs
diff --git a/server/sonar-web/src/main/hbs/issues/issues-details-favorite-filter.hbs b/server/sonar-web/src/main/hbs/issues-old/issues-details-favorite-filter.hbs
index 84c265857f0..84c265857f0 100644
--- a/server/sonar-web/src/main/hbs/issues/issues-details-favorite-filter.hbs
+++ b/server/sonar-web/src/main/hbs/issues-old/issues-details-favorite-filter.hbs
diff --git a/server/sonar-web/src/main/hbs/issues/issues-header.hbs b/server/sonar-web/src/main/hbs/issues-old/issues-header.hbs
index bb44852c45e..bb44852c45e 100644
--- a/server/sonar-web/src/main/hbs/issues/issues-header.hbs
+++ b/server/sonar-web/src/main/hbs/issues-old/issues-header.hbs
diff --git a/server/sonar-web/src/main/hbs/issues/issues.hbs b/server/sonar-web/src/main/hbs/issues-old/issues.hbs
index 50dba468b50..50dba468b50 100644
--- a/server/sonar-web/src/main/hbs/issues/issues.hbs
+++ b/server/sonar-web/src/main/hbs/issues-old/issues.hbs
diff --git a/server/sonar-web/src/main/hbs/issues/no-issues.hbs b/server/sonar-web/src/main/hbs/issues-old/no-issues.hbs
index 33ec1914d28..33ec1914d28 100644
--- a/server/sonar-web/src/main/hbs/issues/no-issues.hbs
+++ b/server/sonar-web/src/main/hbs/issues-old/no-issues.hbs
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
index 00000000000..9b59a846155
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/issues/facets/_issues-facet-header.hbs
@@ -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
index 00000000000..835b6a5b4a4
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/issues/facets/issues-assignee-facet.hbs
@@ -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
index 00000000000..0c2bf496a7a
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/issues/facets/issues-base-facet.hbs
@@ -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
index 00000000000..1d90cb2d139
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/issues/facets/issues-creation-date-facet.hbs
@@ -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
index 00000000000..2268710a51a
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/issues/facets/issues-resolution-facet.hbs
@@ -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
index 00000000000..66deef1086e
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/issues/facets/issues-severity-facet.hbs
@@ -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
index 00000000000..800b7cdb679
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/issues/facets/issues-status-facet.hbs
@@ -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/issues-component-viewer.hbs b/server/sonar-web/src/main/hbs/issues/issues-component-viewer.hbs
new file mode 100644
index 00000000000..1fe6686a28d
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/issues/issues-component-viewer.hbs
@@ -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-filters.hbs b/server/sonar-web/src/main/hbs/issues/issues-filters.hbs
new file mode 100644
index 00000000000..7009e8b38cc
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/issues/issues-filters.hbs
@@ -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-issue.hbs b/server/sonar-web/src/main/hbs/issues/issues-issue.hbs
new file mode 100644
index 00000000000..7027e07ce79
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/issues/issues-issue.hbs
@@ -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
index 00000000000..dfa704a6fb8
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/issues/issues-layout.hbs
@@ -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
index 00000000000..e2c874348eb
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/issues/issues-workspace-header.hbs
@@ -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
index 00000000000..35fe12446aa
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/issues/issues-workspace-list-item.hbs
@@ -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
index 00000000000..74f2f7acff8
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/issues/issues-workspace-list.hbs
@@ -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/js/common/handlebars-extensions.js b/server/sonar-web/src/main/js/common/handlebars-extensions.js
index 65a211bfc74..e927143b7c1 100644
--- a/server/sonar-web/src/main/js/common/handlebars-extensions.js
+++ b/server/sonar-web/src/main/js/common/handlebars-extensions.js
@@ -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/app.js b/server/sonar-web/src/main/js/issues-old/app.js
index 63b329ead76..2d45ffdce75 100644
--- a/server/sonar-web/src/main/js/issues/app.js
+++ b/server/sonar-web/src/main/js/issues-old/app.js
@@ -25,7 +25,7 @@ requirejs.config({
requirejs(
[
'backbone', 'backbone.marionette', 'handlebars',
- 'issues/extra',
+ 'issues-old/extra',
'navigator/filters/filter-bar',
'navigator/filters/base-filters',
'navigator/filters/checkbox-filters',
diff --git a/server/sonar-web/src/main/js/issues/extra.js b/server/sonar-web/src/main/js/issues-old/extra.js
index 5aa0662a43e..ba1c35a7642 100644
--- a/server/sonar-web/src/main/js/issues/extra.js
+++ b/server/sonar-web/src/main/js/issues-old/extra.js
@@ -7,7 +7,7 @@ define(
'navigator/filters/favorite-filters',
'navigator/filters/read-only-filters',
'component-viewer/main',
- 'templates/issues'
+ 'templates/issues-old'
],
function (Backbone, Marionette, FilterBarView, BaseFilters, FavoriteFiltersModule, ReadOnlyFilterView,
ComponentViewer, Templates) {
diff --git a/server/sonar-web/src/main/less/component-viewer-source-colorizer.less b/server/sonar-web/src/main/less/component-viewer-source-colorizer.less
index e11e0e1c402..c53557a89c5 100644
--- a/server/sonar-web/src/main/less/component-viewer-source-colorizer.less
+++ b/server/sonar-web/src/main/less/component-viewer-source-colorizer.less
@@ -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;
}
diff --git a/server/sonar-web/src/main/less/component-viewer.less b/server/sonar-web/src/main/less/component-viewer.less
index a24fcd828ab..2533fc45871 100644
--- a/server/sonar-web/src/main/less/component-viewer.less
+++ b/server/sonar-web/src/main/less/component-viewer.less
@@ -107,7 +107,6 @@
&:hover { background-color: @barBorderColor; }
}
-
.component-viewer-source {
position: relative;
float: left;
@@ -117,27 +116,7 @@
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;
@@ -160,42 +139,6 @@
}
}
- .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;
}
@@ -231,41 +174,6 @@
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
index 00000000000..68d0e96a244
--- /dev/null
+++ b/server/sonar-web/src/main/less/components.less
@@ -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
index 00000000000..1e4fd508693
--- /dev/null
+++ b/server/sonar-web/src/main/less/components/code-source.less
@@ -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
index 00000000000..9b07304a6e5
--- /dev/null
+++ b/server/sonar-web/src/main/less/components/component-issues.less
@@ -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
index 00000000000..ec0a11412a0
--- /dev/null
+++ b/server/sonar-web/src/main/less/components/facets.less
@@ -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;
+}
diff --git a/server/sonar-web/src/main/less/icons.less b/server/sonar-web/src/main/less/icons.less
index fa174f8b19b..6b60e2ea4a2 100644
--- a/server/sonar-web/src/main/less/icons.less
+++ b/server/sonar-web/src/main/less/icons.less
@@ -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
index 00000000000..4a35c249d81
--- /dev/null
+++ b/server/sonar-web/src/main/less/issues.less
@@ -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;
+}
diff --git a/server/sonar-web/src/main/less/navigator/base.less b/server/sonar-web/src/main/less/navigator/base.less
index 1613a496c5e..200ab94be5a 100644
--- a/server/sonar-web/src/main/less/navigator/base.less
+++ b/server/sonar-web/src/main/less/navigator/base.less
@@ -255,49 +255,6 @@
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
diff --git a/server/sonar-web/src/main/less/style.less b/server/sonar-web/src/main/less/style.less
index a0c6ec7f7ba..62af5c47cea 100644
--- a/server/sonar-web/src/main/less/style.less
+++ b/server/sonar-web/src/main/less/style.less
@@ -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
index 00000000000..9cc713e267c
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issues2_controller.rb
@@ -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
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/models/navigation.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/models/navigation.rb
index 4b690d90178..7c709252329 100644
--- a/server/sonar-web/src/main/webapp/WEB-INF/app/models/navigation.rb
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/models/navigation.rb
@@ -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)
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/search.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/search.html.erb
index fac32ece75d..70dcece4ae5 100644
--- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/search.html.erb
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/search.html.erb
@@ -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
index 00000000000..3802a05e99d
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_bulk_change_form.html.erb
@@ -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
index 00000000000..ad34992b288
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_copy_form.html.erb
@@ -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
index 00000000000..650e7e19993
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_edit_form.html.erb
@@ -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
index 00000000000..4c1f60180b8
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_favourites.html.erb
@@ -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
index 00000000000..a771f24bd01
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_save_as_form.html.erb
@@ -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
index 00000000000..24c3891792a
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_shared_form.html.erb
@@ -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
index 00000000000..7e07f5f74b6
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_list.html.erb
@@ -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
index 00000000000..389856f6de1
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations.html.erb
@@ -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
index 00000000000..36e6c6d4573
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations_ajax.html.erb
@@ -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
index 00000000000..f9d6b02a759
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search.html.erb
@@ -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
index 00000000000..eb1c6bf3e58
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search_ajax.html.erb
@@ -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
index 00000000000..619b9f78068
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/manage.html.erb
@@ -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
index 00000000000..9cd9535b53e
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/search.html.erb
@@ -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>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb
index fd4844050c6..ecf608e904f 100644
--- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb
@@ -22,6 +22,9 @@
<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>
<li>