aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2014-04-25 13:21:14 +0600
committerStas Vilchik <vilchiks@gmail.com>2014-04-25 13:21:25 +0600
commiteaee386f735b6fe74a424eaefaa0e834b14f16fb (patch)
treecce60846dc51b2e31d15bad2112525444f244e17
parente3426094b776b73079996dd33cefb7cb1542388c (diff)
downloadsonarqube-eaee386f735b6fe74a424eaefaa0e834b14f16fb.tar.gz
sonarqube-eaee386f735b6fe74a424eaefaa0e834b14f16fb.zip
Component Viewer: integrate into issue page
-rw-r--r--sonar-server/Gruntfile.coffee8
-rw-r--r--sonar-server/src/main/coffee/component-viewer/app.coffee32
-rw-r--r--sonar-server/src/main/coffee/component-viewer/main.coffee35
-rw-r--r--sonar-server/src/main/coffee/component-viewer/source.coffee27
-rw-r--r--sonar-server/src/main/coffee/issues/collections/action-plans.coffee14
-rw-r--r--sonar-server/src/main/coffee/issues/issue-view.coffee229
-rw-r--r--sonar-server/src/main/coffee/issues/models/issue.coffee14
-rw-r--r--sonar-server/src/main/coffee/issues/models/rule.coffee14
-rw-r--r--sonar-server/src/main/coffee/issues/views/assign-form-view.coffee81
-rw-r--r--sonar-server/src/main/coffee/issues/views/comment-form-view.coffee60
-rw-r--r--sonar-server/src/main/coffee/issues/views/plan-form-view.coffee59
-rw-r--r--sonar-server/src/main/coffee/issues/views/rule-view.coffee20
-rw-r--r--sonar-server/src/main/coffee/issues/views/set-severity-form-view.coffee52
-rw-r--r--sonar-server/src/main/hbs/component-viewer/source.hbs18
-rw-r--r--sonar-server/src/main/hbs/issues/assign-form.hbs9
-rw-r--r--sonar-server/src/main/hbs/issues/comment-form.hbs17
-rw-r--r--sonar-server/src/main/hbs/issues/issue.hbs166
-rw-r--r--sonar-server/src/main/hbs/issues/plan-form.hbs20
-rw-r--r--sonar-server/src/main/hbs/issues/rule.hbs7
-rw-r--r--sonar-server/src/main/hbs/issues/set-severity-form.hbs16
-rw-r--r--sonar-server/src/main/js/issues/extra.js640
-rw-r--r--sonar-server/src/main/less/component-viewer.less37
-rw-r--r--sonar-server/src/main/less/icons.less1
-rw-r--r--sonar-server/src/main/less/navigator/base.less2
-rw-r--r--sonar-server/src/main/less/style.less2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/component_viewer_controller.rb27
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/issues/search.html.erb36
27 files changed, 933 insertions, 710 deletions
diff --git a/sonar-server/Gruntfile.coffee b/sonar-server/Gruntfile.coffee
index 5a5d14c69b5..498263d1a77 100644
--- a/sonar-server/Gruntfile.coffee
+++ b/sonar-server/Gruntfile.coffee
@@ -161,10 +161,6 @@ module.exports = (grunt) ->
name: 'common/select-list'
out: '<%= pkg.assets %>build/js/common/select-list.js'
- componentViewer: options:
- name: 'component-viewer/app'
- out: '<%= pkg.assets %>build/js/component-viewer/app.js'
-
handlebars:
options:
@@ -191,6 +187,10 @@ module.exports = (grunt) ->
'<%= pkg.assets %>js/templates/component-viewer.js': [
'<%= pkg.sources %>hbs/component-viewer/**/*.hbs'
]
+ '<%= pkg.assets %>js/templates/issues.js': [
+ '<%= pkg.sources %>hbs/common/**/*.hbs'
+ '<%= pkg.sources %>hbs/issues/**/*.hbs'
+ ]
clean:
diff --git a/sonar-server/src/main/coffee/component-viewer/app.coffee b/sonar-server/src/main/coffee/component-viewer/app.coffee
deleted file mode 100644
index 85ec0f9026d..00000000000
--- a/sonar-server/src/main/coffee/component-viewer/app.coffee
+++ /dev/null
@@ -1,32 +0,0 @@
-requirejs.config
- baseUrl: "#{baseUrl}/js"
-
- paths:
- 'backbone': 'third-party/backbone'
- 'backbone.marionette': 'third-party/backbone.marionette'
- 'handlebars': 'third-party/handlebars'
- 'jquery.mockjax': 'third-party/jquery.mockjax'
-
- shim:
- 'backbone.marionette':
- deps: ['backbone']
- exports: 'Marionette'
- 'backbone':
- exports: 'Backbone'
- 'handlebars':
- exports: 'Handlebars'
-
-
-requirejs [
- 'component-viewer/main',
-], (
- ComponentViewer
-) ->
-
- TEST_RESOURCE_KEY = 'org.codehaus.sonar:sonar-plugin-api:src/main/java/org/sonar/api/resources/ResourceTypeTree.java'
-
-
- @componentViewer = new ComponentViewer()
- @componentViewer.render().$el.appendTo '#body'
-
- @componentViewer.open TEST_RESOURCE_KEY \ No newline at end of file
diff --git a/sonar-server/src/main/coffee/component-viewer/main.coffee b/sonar-server/src/main/coffee/component-viewer/main.coffee
index c92b1c74c36..c231570efd1 100644
--- a/sonar-server/src/main/coffee/component-viewer/main.coffee
+++ b/sonar-server/src/main/coffee/component-viewer/main.coffee
@@ -40,11 +40,20 @@ define [
model: @source
main: @
- @settings = new Backbone.Model issues: false, coverage: true, duplications: false, scm: false
+ @settings = new Backbone.Model
+ issues: false
+ coverage: false
+ duplications: false
+ scm: false
+ workspace: false
onRender: ->
- @workspaceRegion.show @workspaceView
+ if @settings.get 'workspace'
+ @workspaceRegion.show @workspaceView
+ @$el.addClass 'component-viewer-workspace-enabled'
+ else
+ @$el.removeClass 'component-viewer-workspace-enabled'
@sourceRegion.show @sourceView
@@ -109,6 +118,28 @@ define [
@sourceView.render()
+ showWorkspace: ->
+ @settings.set 'workspace', true
+ @render()
+
+
+ hideWorkspace: ->
+ @settings.set 'workspace', false
+ @render()
+
+
+ showIssues: (issues, scrollToFirst) ->
+ @settings.set 'issues', true
+ if _.isArray(issues) && issues.length > 0
+ @source.set 'issues', issues
+ @sourceView.render()
+
+
+ hideIssues: ->
+ @settings.set 'issues', false
+ @sourceView.render()
+
+
addTransition: (key, transition, optionsForCurrent, options) ->
if optionsForCurrent?
last = @workspace.at(@workspace.length - 1)
diff --git a/sonar-server/src/main/coffee/component-viewer/source.coffee b/sonar-server/src/main/coffee/component-viewer/source.coffee
index 27f72f9da40..72f7c7e7b14 100644
--- a/sonar-server/src/main/coffee/component-viewer/source.coffee
+++ b/sonar-server/src/main/coffee/component-viewer/source.coffee
@@ -2,11 +2,15 @@ define [
'backbone.marionette'
'templates/component-viewer'
'component-viewer/coverage-popup'
+ 'issues/issue-view'
+ 'issues/models/issue'
'common/handlebars-extensions'
], (
Marionette
Templates
CoveragePopupView
+ IssueView
+ Issue
) ->
$ = jQuery
@@ -19,6 +23,7 @@ define [
events:
'click .settings-toggle button': 'toggleSettings'
'change #source-coverage': 'toggleCoverage'
+ 'change #source-workspace': 'toggleWorkspace'
'click .coverage-tests': 'showCoveragePopup'
@@ -26,12 +31,23 @@ define [
@delegateEvents()
@showSettings = false
+ @renderIssues() if @options.main.settings.get('issues') && @model.has('issues')
+
+
+ renderIssues: ->
+ issues = @model.get 'issues'
+ issues.forEach (issue) =>
+ line = issue.line || 0
+ container = @$("[data-line-number=#{line}]").children('.line')
+ container.addClass 'issue' if line > 0
+ issueView = new IssueView model: new Issue issue
+ issueView.render().$el.appendTo container
+
showSpinner: ->
@$el.html '<div style="padding: 10px;"><i class="spinner"></i></div>'
-
toggleSettings: ->
@$('.settings-toggle button').toggleClass 'open'
@$('.component-viewer-source-settings').toggleClass 'open'
@@ -43,6 +59,12 @@ define [
if active then @options.main.showCoverage() else @options.main.hideCoverage()
+ toggleWorkspace: (e) ->
+ active = $(e.currentTarget).is ':checked'
+ @showSettings = true
+ if active then @options.main.showWorkspace() else @options.main.hideWorkspace()
+
+
showCoveragePopup: (e) ->
e.stopPropagation()
$('body').click()
@@ -88,4 +110,5 @@ define [
serializeData: ->
source: @prepareSource()
settings: @options.main.settings.toJSON()
- showSettings: @showSettings \ No newline at end of file
+ showSettings: @showSettings
+ component: @options.main.component.toJSON() \ No newline at end of file
diff --git a/sonar-server/src/main/coffee/issues/collections/action-plans.coffee b/sonar-server/src/main/coffee/issues/collections/action-plans.coffee
new file mode 100644
index 00000000000..69e0d459de1
--- /dev/null
+++ b/sonar-server/src/main/coffee/issues/collections/action-plans.coffee
@@ -0,0 +1,14 @@
+define [
+ 'backbone'
+], (
+ Backbone
+) ->
+
+ class ActionPlans extends Backbone.Collection
+
+ url: ->
+ "#{baseUrl}/api/action_plans/search"
+
+
+ parse: (r) ->
+ r.actionPlans \ No newline at end of file
diff --git a/sonar-server/src/main/coffee/issues/issue-view.coffee b/sonar-server/src/main/coffee/issues/issue-view.coffee
new file mode 100644
index 00000000000..0c1dc02ceb8
--- /dev/null
+++ b/sonar-server/src/main/coffee/issues/issue-view.coffee
@@ -0,0 +1,229 @@
+define [
+ 'backbone.marionette'
+ 'templates/issues'
+
+ 'issues/models/rule'
+ 'issues/views/rule-view'
+
+ 'issues/collections/action-plans'
+
+ 'issues/views/assign-form-view'
+ 'issues/views/comment-form-view'
+ 'issues/views/plan-form-view'
+ 'issues/views/set-severity-form-view'
+
+], (
+ Marionette
+ Templates
+
+ Rule
+ RuleView
+
+ ActionPlans
+
+ AssignFormView
+ CommentFormView
+ PlanFormView
+ SetSeverityFormView
+
+) ->
+
+ $ = jQuery
+
+
+ class IssueView extends Marionette.Layout
+ className: 'code-issues'
+ template: Templates['issue']
+
+
+ regions:
+ formRegion: '.code-issue-form'
+ ruleRegion: '#tab-issue-rule'
+
+
+ modelEvents:
+ 'change': 'render'
+
+
+ events:
+ 'click': 'setDetailScope',
+
+ 'click .code-issue-toggle': 'toggleCollapsed',
+
+ 'click [href=#tab-issue-rule]': 'fetchRule',
+
+ 'click #issue-comment': 'comment',
+ 'click .issue-comment-edit': 'editComment',
+ 'click .issue-comment-delete': 'deleteComment',
+ 'click .issue-transition': 'transition',
+ 'click #issue-set-severity': 'setSeverity',
+ 'click #issue-assign': 'assign',
+ 'click #issue-assign-to-me': 'assignToMe',
+ 'click #issue-plan': 'plan',
+ 'click .issue-action': 'action'
+
+
+ onRender: ->
+ @$('.code-issue-details').tabs()
+ @$('.code-issue-form').hide()
+ @rule = new Rule key: this.model.get('rule')
+ @ruleRegion.show new RuleView model: @rule, issue: @model
+
+
+ setDetailScope: ->
+ key.setScope 'detail'
+
+
+ onClose: ->
+ @ruleRegion.reset() if @ruleRegion
+
+
+ resetIssue: (options) ->
+ key = @model.get 'key'
+ @model.clear silent: true
+ @model.set { key: key }, { silent: true }
+ @model.fetch options
+
+
+ toggleCollapsed: ->
+ @$('.code-issue').toggleClass 'code-issue-collapsed'
+ @fetchRule()
+
+
+ fetchRule: ->
+ unless @rule.has 'name'
+ @$('#tab-issue-rule').addClass 'navigator-fetching'
+ @rule.fetch
+ success: => @$('#tab-issue-rule').removeClass 'navigator-fetching'
+
+
+ showActionView: (view) ->
+ @$('.code-issue-actions').hide()
+ @$('.code-issue-form').show()
+ @formRegion.show view
+
+
+ showActionSpinner: ->
+ @$('.code-issue-actions').addClass 'navigator-fetching'
+
+
+ hideActionSpinner: ->
+ @$('.code-issue-actions').removeClass 'navigator-fetching'
+
+
+ updateAfterAction: (fetch) ->
+ @formRegion.reset()
+ @$('.code-issue-actions').show()
+ @$('.code-issue-form').hide()
+ @$('[data-comment-key]').show()
+
+ if fetch
+ $.when(@resetIssue()).done => @hideActionSpinner()
+
+
+ comment: ->
+ commentFormView = new CommentFormView
+ issue: @model
+ detailView: @
+ @showActionView commentFormView
+
+
+ editComment: (e) ->
+ commentEl = $(e.target).closest '[data-comment-key]'
+ commentKey = commentEl.data 'comment-key'
+ comment = _.findWhere this.model.get('comments'), { key: commentKey }
+
+ commentEl.hide();
+
+ commentFormView = new CommentFormView
+ model: new Backbone.Model comment
+ issue: @model
+ detailView: @
+ @showActionView commentFormView
+
+
+ deleteComment: (e) ->
+ commentKey = $(e.target).closest('[data-comment-key]').data 'comment-key'
+ confirmMsg = $(e.target).data 'confirm-msg'
+
+ if confirm(confirmMsg)
+ @showActionSpinner()
+ $.ajax
+ type: "POST"
+ url: baseUrl + "/issue/delete_comment?id=" + commentKey
+ .done => @updateAfterAction true
+ .fail (r) =>
+ alert _.pluck(r.responseJSON.errors, 'msg').join(' ')
+ @hideActionSpinner()
+
+
+ transition: (e) ->
+ @showActionSpinner();
+ $.ajax
+ type: 'POST',
+ url: baseUrl + '/api/issues/do_transition',
+ data:
+ issue: @model.get('key')
+ transition: $(e.target).data('transition')
+ .done => @resetIssue()
+ .fail (r) =>
+ alert _.pluck(r.responseJSON.errors, 'msg').join(' ')
+ @hideActionSpinner()
+
+
+ setSeverity: ->
+ setSeverityFormView = new SetSeverityFormView
+ issue: @model
+ detailView: @
+ @showActionView setSeverityFormView
+
+
+ assign: ->
+ assignFormView = new AssignFormView
+ issue: @model
+ detailView: this
+ @showActionView assignFormView
+
+
+ assignToMe: ->
+ @showActionSpinner()
+ $.ajax
+ type: 'POST'
+ url: baseUrl + '/api/issues/assign'
+ data:
+ issue: @model.get('key')
+ assignee: window.SS.currentUser
+ .done => @resetIssue()
+ .fail (r) =>
+ alert _.pluck(r.responseJSON.errors, 'msg').join(' ')
+ @hideActionSpinner()
+
+
+ plan: ->
+ actionPlans = new ActionPlans()
+ planFormView = new PlanFormView
+ collection: actionPlans
+ issue: @model
+ detailView: @
+ @showActionSpinner()
+ actionPlans.fetch
+ reset: true
+ data: project: @model.get('project')
+ success: =>
+ @hideActionSpinner()
+ @showActionView planFormView
+
+
+ action: (e) ->
+ actionKey = $(e.target).data 'action'
+ @showActionSpinner()
+ $.ajax
+ type: 'POST'
+ url: baseUrl + '/api/issues/do_action'
+ data:
+ issue: @model.get('key')
+ actionKey: actionKey
+ .done => @resetIssue()
+ .fail (r) =>
+ alert _.pluck(r.responseJSON.errors, 'msg').join(' ')
+ @hideActionSpinner() \ No newline at end of file
diff --git a/sonar-server/src/main/coffee/issues/models/issue.coffee b/sonar-server/src/main/coffee/issues/models/issue.coffee
new file mode 100644
index 00000000000..9011a28c596
--- /dev/null
+++ b/sonar-server/src/main/coffee/issues/models/issue.coffee
@@ -0,0 +1,14 @@
+define [
+ 'backbone'
+], (
+ Backbone
+) ->
+
+ class Issue extends Backbone.Model
+
+ url: ->
+ "#{baseUrl}/api/issues/show?key=#{@get('key')}"
+
+
+ parse: (r) ->
+ if r.issue then r.issue else r \ No newline at end of file
diff --git a/sonar-server/src/main/coffee/issues/models/rule.coffee b/sonar-server/src/main/coffee/issues/models/rule.coffee
new file mode 100644
index 00000000000..570feb88e94
--- /dev/null
+++ b/sonar-server/src/main/coffee/issues/models/rule.coffee
@@ -0,0 +1,14 @@
+define [
+ 'backbone'
+], (
+ Backbone
+) ->
+
+ class Rule extends Backbone.Model
+
+ url: ->
+ "#{baseUrl}/api/rules/show/?key=#{@get('key')}"
+
+
+ parse: (r) ->
+ if r.rule then r.rule else r \ No newline at end of file
diff --git a/sonar-server/src/main/coffee/issues/views/assign-form-view.coffee b/sonar-server/src/main/coffee/issues/views/assign-form-view.coffee
new file mode 100644
index 00000000000..433a1b68ed3
--- /dev/null
+++ b/sonar-server/src/main/coffee/issues/views/assign-form-view.coffee
@@ -0,0 +1,81 @@
+define [
+ 'backbone.marionette'
+ 'templates/issues'
+], (
+ Marionette
+ Templates
+) ->
+
+ $ = jQuery
+
+
+ class AssignFormView extends Marionette.ItemView
+ template: Templates['assign-form']
+
+
+ ui:
+ select: '#issue-assignee-select'
+
+
+ events:
+ 'click #issue-assign-cancel': 'cancel'
+ 'click #issue-assign-submit': 'submit'
+
+
+ onRender: ->
+ currentUser = window.SS.currentUser
+ assignee = @options.issue.get('assignee')
+ additionalChoices = []
+
+ if !assignee || currentUser != assignee
+ additionalChoices.push id: currentUser, text: translate('assignedToMe')
+
+ if !!assignee
+ additionalChoices.push id: '', text: translate('unassigned')
+
+ select2Options =
+ allowClear: false
+ width: '250px'
+ formatNoMatches: -> translate('select2.noMatches')
+ formatSearching: -> translate('select2.searching')
+ formatInputTooShort: -> translate('select2.tooShort')
+
+ if additionalChoices.length > 0
+ select2Options.minimumInputLength = 0
+ select2Options.query = (query) ->
+ if query.term.length == 0
+ query.callback results: additionalChoices
+ else if query.term.length >= 2
+ $.ajax
+ url: baseUrl + '/api/users/search?f=s2'
+ data: s: query.term
+ dataType: 'jsonp'
+ .done (data) -> query.callback data
+ else
+ select2Options.minimumInputLength = 2
+ select2Options.ajax =
+ quietMillis: 300
+ url: baseUrl + '/api/users/search?f=s2'
+ data: (term, page) -> s: term, p: page
+ results: (data) -> more: data.more, results: data.results
+
+ @ui.select.select2(select2Options).select2 'open'
+
+
+ cancel: ->
+ @options.detailView.updateAfterAction false
+
+
+ submit: ->
+ @options.detailView.showActionSpinner()
+
+ $.ajax
+ type: 'POST'
+ url: baseUrl + '/api/issues/assign'
+ data:
+ issue: @options.issue.get('key')
+ assignee: @ui.select.val()
+ .done => @options.detailView.updateAfterAction true
+ .fail (r) =>
+ alert _.pluck(r.responseJSON.errors, 'msg').join(' ')
+ @options.detailView.hideActionSpinner() \ No newline at end of file
diff --git a/sonar-server/src/main/coffee/issues/views/comment-form-view.coffee b/sonar-server/src/main/coffee/issues/views/comment-form-view.coffee
new file mode 100644
index 00000000000..57c0393ce0a
--- /dev/null
+++ b/sonar-server/src/main/coffee/issues/views/comment-form-view.coffee
@@ -0,0 +1,60 @@
+define [
+ 'backbone.marionette'
+ 'templates/issues'
+], (
+ Marionette
+ Templates
+) ->
+
+ $ = jQuery
+
+
+ class IssueDetailCommentFormView extends Marionette.ItemView
+ template: Templates['comment-form']
+
+
+ ui:
+ textarea: '#issue-comment-text'
+ cancelButton: '#issue-comment-cancel'
+ submitButton: '#issue-comment-submit'
+
+
+ events:
+ 'keyup #issue-comment-text': 'toggleSubmit'
+ 'click #issue-comment-cancel': 'cancel'
+ 'click #issue-comment-submit': 'submit'
+
+
+ onDomRefresh: ->
+ @ui.textarea.focus()
+
+
+ toggleSubmit: ->
+ @ui.submitButton.prop 'disabled', @ui.textarea.val().length == 0
+
+
+ cancel: ->
+ @options.detailView.updateAfterAction false
+
+
+ submit: ->
+ text = @ui.textarea.val()
+ update = @model && @model.has('key')
+ url = baseUrl + '/api/issues/' + (if update then 'edit_comment' else 'add_comment')
+ data = text: text
+
+ if update
+ data.key = @model.get('key')
+ else
+ data.issue = @options.issue.get('key')
+
+ @options.detailView.showActionSpinner()
+
+ $.ajax
+ type: 'POST'
+ url: url
+ data: data
+ .done => @options.detailView.updateAfterAction true
+ .fail (r) =>
+ alert _.pluck(r.responseJSON.errors 'msg').join(' ')
+ @options.detailView.hideActionSpinner() \ No newline at end of file
diff --git a/sonar-server/src/main/coffee/issues/views/plan-form-view.coffee b/sonar-server/src/main/coffee/issues/views/plan-form-view.coffee
new file mode 100644
index 00000000000..c7130201516
--- /dev/null
+++ b/sonar-server/src/main/coffee/issues/views/plan-form-view.coffee
@@ -0,0 +1,59 @@
+define [
+ 'backbone.marionette'
+ 'templates/issues'
+], (
+ Marionette
+ Templates
+) ->
+
+ $ = jQuery
+
+
+ class PlanFormView extends Marionette.ItemView
+ template: Templates['plan-form']
+
+
+ collectionEvents:
+ 'reset': 'render'
+
+
+ ui:
+ select: '#issue-detail-plan-select'
+
+
+ events:
+ 'click #issue-plan-cancel': 'cancel'
+ 'click #issue-plan-submit': 'submit'
+
+
+ onRender: ->
+ @ui.select.select2
+ width: '250px'
+ minimumResultsForSearch: 100
+
+ @$('.error a').prop('href', baseUrl + '/action_plans/index/' + this.options.issue.get('project'))
+
+
+ cancel: ->
+ @options.detailView.updateAfterAction(false);
+
+
+ submit: ->
+ plan = @ui.select.val()
+ @options.detailView.showActionSpinner()
+
+ $.ajax
+ type: 'POST'
+ url: baseUrl + '/api/issues/plan'
+ data:
+ issue: this.options.issue.get('key'),
+ plan: if plan == '#unplan' then '' else plan
+ .done => @options.detailView.updateAfterAction true
+ .fail (r) =>
+ alert _.pluck(r.responseJSON.errors, 'msg').join(' ')
+ @options.detailView.hideActionSpinner()
+
+
+ serializeData: ->
+ items: @collection.toJSON()
+ issue: @options.issue.toJSON() \ No newline at end of file
diff --git a/sonar-server/src/main/coffee/issues/views/rule-view.coffee b/sonar-server/src/main/coffee/issues/views/rule-view.coffee
new file mode 100644
index 00000000000..caf05d4c72f
--- /dev/null
+++ b/sonar-server/src/main/coffee/issues/views/rule-view.coffee
@@ -0,0 +1,20 @@
+define [
+ 'backbone.marionette'
+ 'templates/issues'
+], (
+ Marionette
+ Templates
+) ->
+
+ class IssueDetailRuleView extends Marionette.ItemView
+ className: 'rule-desc'
+ template: Templates['rule']
+
+ modelEvents:
+ 'change': 'render'
+
+
+ serializeData: ->
+ _.extend super,
+ characteristic: this.options.issue.get 'characteristic'
+ subCharacteristic: this.options.issue.get 'subCharacteristic' \ No newline at end of file
diff --git a/sonar-server/src/main/coffee/issues/views/set-severity-form-view.coffee b/sonar-server/src/main/coffee/issues/views/set-severity-form-view.coffee
new file mode 100644
index 00000000000..96d756db7b4
--- /dev/null
+++ b/sonar-server/src/main/coffee/issues/views/set-severity-form-view.coffee
@@ -0,0 +1,52 @@
+define [
+ 'backbone.marionette'
+ 'templates/issues'
+], (
+ Marionette
+ Templates
+) ->
+
+ $ = jQuery
+
+
+ class SetSeverityFormView extends Marionette.ItemView
+ template: Templates['set-severity-form']
+
+
+ ui:
+ select: '#issue-set-severity-select'
+
+
+ events:
+ 'click #issue-set-severity-cancel': 'cancel'
+ 'click #issue-set-severity-submit': 'submit'
+
+
+ onRender: ->
+ format = (state) ->
+ return state.text unless state.id
+ '<i class="icon-severity-' + state.id.toLowerCase() + '"></i> ' + state.text
+
+ @ui.select.select2
+ minimumResultsForSearch: 100
+ formatResult: format
+ formatSelection: format
+ escapeMarkup: (m) -> m
+
+
+ cancel: ->
+ @options.detailView.updateAfterAction false
+
+
+ submit: ->
+ @options.detailView.showActionSpinner()
+ $.ajax
+ type: 'POST'
+ url: baseUrl + '/api/issues/set_severity'
+ data:
+ issue: @options.issue.get('key')
+ severity: @ui.select.val()
+ .done => @options.detailView.updateAfterAction true
+ .fail (r) =>
+ alert _.pluck(r.responseJSON.errors, 'msg').join(' ')
+ @options.detailView.hideActionSpinner() \ No newline at end of file
diff --git a/sonar-server/src/main/hbs/component-viewer/source.hbs b/sonar-server/src/main/hbs/component-viewer/source.hbs
index 66a08b3844e..53c30e18648 100644
--- a/sonar-server/src/main/hbs/component-viewer/source.hbs
+++ b/sonar-server/src/main/hbs/component-viewer/source.hbs
@@ -28,13 +28,29 @@
<input id="source-scm" type="checkbox" {{#if settings.scm}}checked{{/if}}>
<label for="source-scm">SCM</label>
</li>
+ <li>
+ <input id="source-workspace" type="checkbox" {{#if settings.workspace}}checked{{/if}}>
+ <label for="source-workspace">Workspace</label>
+ </li>
</ul>
+
+ {{qualifierIcon component.qualifier}} <span class="component-viewer-title">{{component.name}}</span>
</th>
</tr>
</thead>
<tbody>
+ <tr class="row" data-line-number="0">
+ {{#if settings.coverage}}
+ <td class="stat coverage-tests"></td>
+ <td class="stat coverage-conditions"></td>
+ {{/if}}
+ <td class="stat lid"></td>
+ <td class="line"></td>
+ </tr>
+
{{#each source}}
- <tr class="row {{#if ../settings.coverage}}{{#if coverageStatus}}coverage-{{coverageStatus}}{{/if}}{{/if}}">
+ <tr class="row {{#if ../settings.coverage}}{{#if coverageStatus}}coverage-{{coverageStatus}}{{/if}}{{/if}}"
+ data-line-number="{{lineNumber}}">
{{#if ../settings.coverage}}
<td class="stat coverage-tests">
diff --git a/sonar-server/src/main/hbs/issues/assign-form.hbs b/sonar-server/src/main/hbs/issues/assign-form.hbs
new file mode 100644
index 00000000000..f25a0503ffa
--- /dev/null
+++ b/sonar-server/src/main/hbs/issues/assign-form.hbs
@@ -0,0 +1,9 @@
+<table class="width100">
+ <tr>
+ <td>
+ <input type="text" id="issue-assignee-select">
+ <input id="issue-assign-submit" type="submit" value="{{t 'issue.assign.submit'}}">&nbsp;
+ <a id="issue-assign-cancel" class="action">{{t 'cancel'}}</a>
+ </td>
+ </tr>
+</table> \ No newline at end of file
diff --git a/sonar-server/src/main/hbs/issues/comment-form.hbs b/sonar-server/src/main/hbs/issues/comment-form.hbs
new file mode 100644
index 00000000000..f517dfd5ac8
--- /dev/null
+++ b/sonar-server/src/main/hbs/issues/comment-form.hbs
@@ -0,0 +1,17 @@
+<table class="width100">
+ <tr>
+ <td style="vertical-align:top" colspan="2">
+ <textarea id="issue-comment-text" rows="4" name="text" style="width: 100%">{{raw}}</textarea>
+ </td>
+ </tr>
+ <tr>
+ <td style="padding-top: 5px">
+ <input id="issue-comment-submit" type="submit"
+ value="{{#if id}}{{t 'save'}}{{else}}{{t 'issue.comment.submit'}}{{/if}}" disabled>
+ <a id="issue-comment-cancel" class="action">{{t 'cancel'}}</a>
+ </td>
+ <td align="right">
+ {{> '_markdown-tips' }}
+ </td>
+ </tr>
+</table> \ No newline at end of file
diff --git a/sonar-server/src/main/hbs/issues/issue.hbs b/sonar-server/src/main/hbs/issues/issue.hbs
new file mode 100644
index 00000000000..8e75cd7ad72
--- /dev/null
+++ b/sonar-server/src/main/hbs/issues/issue.hbs
@@ -0,0 +1,166 @@
+<div class="code-issue 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="../issue/show/{{key}}?layout=false">
+ <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">{{translate "actions.comment" }}</a>
+ </li>
+ {{/inArray}}
+
+
+ <li>
+ {{statusIcon status}}{{translate "statuses" status}}
+ {{#if resolution}}({{translate "resolutions" resolution}}){{/if}}
+
+ {{#ifNotEmpty transitions}}
+ {{#withFirst transitions}}
+ <a class="link-action issue-transition spacer-left" data-transition="{{this}}">{{translate "transitions" this}}</a>
+ {{/withFirst}}
+
+ {{#ifHasExtraTransitions transitions}}
+ <div class="dropdown">
+ <a class="link-action link-more" onclick="showDropdownMenuOnElement($j(this).next('.dropdown-menu')); return false;"></a>
+ <ul style="display: none" class="dropdown-menu">
+ {{#withoutFirst transitions}}
+ <li>
+ <a class="link-action issue-transition" data-transition="{{this}}">{{translate "transitions" this}}</a>
+ </li>
+ {{/withoutFirst}}
+ </ul>
+ </div>
+
+ {{/ifHasExtraTransitions}}
+ {{/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">{{translate "actions.assign" }}</a>
+ {{#inArray actions "assign_to_me"}}
+ [<a id="issue-assign-to-me" class="link-action">{{translate "actions.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">{{translate "actions.set_severity"}}</a>
+ </li>
+ {{/inArray}}
+ {{#pluginActions actions}}
+ <li>
+ <a class="link-action spacer-right issue-action" data-action="{{this}}">{{translate "actions" this}}</a>
+ </li>
+ {{/pluginActions}}
+ </ul>
+ </div>
+ </li>
+ {{/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"></div>
+
+
+ <div class="code-issue-details">
+ <ul class="tabs">
+ <li>
+ <a href="#tab-issue-rule">{{t 'rule'}}</a>
+ </li>
+ <li>
+ <a href="#tab-issue-changelog">{{t 'changelog'}}</a>
+ </li>
+ </ul>
+
+ <div id="tab-issue-rule">
+ <div class="rule-desc"></div>
+ </div>
+
+ <div id="tab-issue-changelog">
+ <table class="spaced">
+ <tbody>
+ {{#each changelog}}
+ <tr>
+ <td class="thin left top" nowrap>{{fCreationDate}}</td>
+ <td class="thin left top" nowrap>{{userName}}</td>
+ <td class="left top">
+ {{#each diffs}}
+ {{this}}<br>
+ {{/each}}
+ </td>
+ </tr>
+ {{/each}}
+ </tbody>
+ </table>
+ </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>
+ ({{fCreatedAge}})
+
+ {{#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="<%= h message('issue.comment.delete_confirm_message') -%>">{{t 'delete'}}</a>
+ {{/if}}
+ </h4>
+ {{{html}}}
+ </div>
+ {{/each}}
+ </div>
+</div> \ No newline at end of file
diff --git a/sonar-server/src/main/hbs/issues/plan-form.hbs b/sonar-server/src/main/hbs/issues/plan-form.hbs
new file mode 100644
index 00000000000..3d96e6e6a72
--- /dev/null
+++ b/sonar-server/src/main/hbs/issues/plan-form.hbs
@@ -0,0 +1,20 @@
+{{#if items}}
+ <select id="issue-detail-plan-select">
+ {{#if issue.actionPlan}}
+ <option value="#unplan">{{t 'issue.unplan.submit'}}</option>
+ {{/if}}
+ {{#each items}}
+ {{#notEq this.status 'CLOSED'}}
+ <option value="{{this.key}}">{{this.name}} {{#if this.fDeadLine}}({{this.fDeadLine}}){{/if}}</option>
+ {{/notEq}}
+ {{/each}}
+ </select>
+ <input id="issue-plan-submit" type="submit" value="{{t 'issue.plan.submit'}}">&nbsp;
+{{else}}
+ <% if is_admin? %>
+ <span class="error">{{t 'issue.plan.error.plan_must_be_created_first_for_admin'}}</span>
+ <% else %>
+ <span class="error">{{t 'issue.plan.error.plan_must_be_created_first_for_other'}}</span>
+ <% end %>
+{{/if}}
+<a id="issue-plan-cancel" class="action">{{t 'cancel'}}</a> \ No newline at end of file
diff --git a/sonar-server/src/main/hbs/issues/rule.hbs b/sonar-server/src/main/hbs/issues/rule.hbs
new file mode 100644
index 00000000000..67b635092a3
--- /dev/null
+++ b/sonar-server/src/main/hbs/issues/rule.hbs
@@ -0,0 +1,7 @@
+<h1 class="marginbottom10">{{name}}</h1>
+<div class="marginbottom10">{{{description}}}</div>
+<p class="note">
+ <span class="spacer-right">{{key}}</span>
+ <%= image_tag 'sep12.png', :class => 'spacer-right' -%>
+ {{#all characteristic subCharacteristic}}{{characteristic}} > {{subCharacteristic}}{{else}}{{t 'issue.technical_debt_deleted'}}{{/all}}
+</p> \ No newline at end of file
diff --git a/sonar-server/src/main/hbs/issues/set-severity-form.hbs b/sonar-server/src/main/hbs/issues/set-severity-form.hbs
new file mode 100644
index 00000000000..e85c027873c
--- /dev/null
+++ b/sonar-server/src/main/hbs/issues/set-severity-form.hbs
@@ -0,0 +1,16 @@
+<table class="width100">
+ <tr>
+ <td style="vertical-align:top">
+ <select id="issue-set-severity-select" autofocus>
+ <option class="sev_BLOCKER" value="BLOCKER">{{t 'severity.BLOCKER'}}</option>
+ <option class="sev_CRITICAL" value="CRITICAL">{{t 'severity.CRITICAL'}}</option>
+ <option class="sev_MAJOR" value="MAJOR" selected>{{t 'severity.MAJOR'}}</option>
+ <option class="sev_MINOR" value="MINOR">{{t 'severity.MINOR'}}</option>
+ <option class="sev_INFO" value="INFO">{{t 'severity.INFO'}}</option>
+ </select>
+
+ <input id="issue-set-severity-submit" type="submit" value="{{t 'issue.set_severity.submit'}}">
+ <a id="issue-set-severity-cancel" class="action">{{t 'cancel'}}</a>
+ </td>
+ </tr>
+</table> \ No newline at end of file
diff --git a/sonar-server/src/main/js/issues/extra.js b/sonar-server/src/main/js/issues/extra.js
index 83df044945f..aff0dfcce54 100644
--- a/sonar-server/src/main/js/issues/extra.js
+++ b/sonar-server/src/main/js/issues/extra.js
@@ -4,9 +4,11 @@ define(
'../navigator/filters/filter-bar',
'navigator/filters/base-filters',
'navigator/filters/favorite-filters',
- 'navigator/filters/read-only-filters'
+ 'navigator/filters/read-only-filters',
+ 'component-viewer/main'
],
- function (Backbone, Marionette, FilterBarView, BaseFilters, FavoriteFiltersModule, ReadOnlyFilterView) {
+ function (Backbone, Marionette, FilterBarView, BaseFilters, FavoriteFiltersModule, ReadOnlyFilterView,
+ ComponentViewer) {
var AppState = Backbone.Model.extend({
@@ -171,23 +173,20 @@ define(
var that = this,
app = this.options.app,
- detailView = new IssueDetailView({
- model: this.model
- }),
+ componentViewer = new ComponentViewer(),
showCallback = function () {
jQuery('.navigator-details').removeClass('navigator-fetching');
- app.detailsRegion.show(detailView);
+ app.detailsRegion.show(componentViewer);
+ componentViewer.open(that.model.get('component')).done(function() {
+ componentViewer.showIssues([that.model.toJSON()], true);
+
+ var top = componentViewer.$('.code-issues:first').closest('tr').position().top;
+ jQuery('.navigator-details').scrollTop(top - 40);
+ });
};
jQuery('.navigator-details').empty().addClass('navigator-fetching');
- jQuery.when(detailView.model.fetch()).done(function () {
- if (that.model.get('status') !== 'CLOSED') {
- that.fetchSource(detailView, showCallback);
- } else {
- showCallback();
- }
-
- });
+ jQuery.when(this.model.fetch()).done(showCallback);
},
@@ -571,618 +570,6 @@ define(
});
- var IssueDetailCommentFormView = Marionette.ItemView.extend({
- template: Handlebars.compile(jQuery('#issue-detail-comment-form-template').html() || ''),
-
-
- ui: {
- textarea: '#issue-comment-text',
- cancelButton: '#issue-comment-cancel',
- submitButton: '#issue-comment-submit'
- },
-
-
- events: {
- 'keyup #issue-comment-text': 'toggleSubmit',
- 'click #issue-comment-cancel': 'cancel',
- 'click #issue-comment-submit': 'submit'
- },
-
-
- onDomRefresh: function () {
- this.ui.textarea.focus();
- },
-
-
- toggleSubmit: function () {
- this.ui.submitButton.prop('disabled', this.ui.textarea.val().length === 0);
- },
-
-
- cancel: function () {
- this.options.detailView.updateAfterAction(false);
- },
-
-
- submit: function () {
- var that = this,
- text = this.ui.textarea.val(),
- update = this.model && this.model.has('key'),
- url = baseUrl + '/api/issues/' + (update ? 'edit_comment' : 'add_comment'),
- data = { text: text };
-
- if (update) {
- data.key = this.model.get('key');
- } else {
- data.issue = this.options.issue.get('key');
- }
-
- this.options.detailView.showActionSpinner();
-
- jQuery.ajax({
- type: 'POST',
- url: url,
- data: data
- })
- .done(function () {
- that.options.detailView.updateAfterAction(true);
- })
- .fail(function (r) {
- alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r);
- that.options.detailView.hideActionSpinner();
- });
- }
- });
-
-
- var IssueDetailSetSeverityFormView = Marionette.ItemView.extend({
- template: Handlebars.compile(jQuery('#issue-detail-set-severity-form-template').html() || ''),
-
-
- ui: {
- select: '#issue-set-severity-select'
- },
-
-
- events: {
- 'click #issue-set-severity-cancel': 'cancel',
- 'click #issue-set-severity-submit': 'submit'
- },
-
-
- onRender: function () {
- var format = function(state) {
- if (!state.id) return state.text; // optgroup
- return '<i class="icon-severity-' + state.id.toLowerCase() + '"></i> ' + state.text;
- }
-
- this.ui.select.select2({
- minimumResultsForSearch: 100,
- formatResult: format,
- formatSelection: format,
- escapeMarkup: function(m) { return m; }
- });
- },
-
-
- cancel: function () {
- this.options.detailView.updateAfterAction(false);
- },
-
-
- submit: function () {
- var that = this;
-
- this.options.detailView.showActionSpinner();
-
- jQuery.ajax({
- type: 'POST',
- url: baseUrl + '/api/issues/set_severity',
- data: {
- issue: this.options.issue.get('key'),
- severity: this.ui.select.val()
- }
- })
- .done(function () {
- that.options.detailView.updateAfterAction(true);
- })
- .fail(function (r) {
- alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r);
- that.options.detailView.hideActionSpinner();
- });
- }
- });
-
-
- var IssueDetailAssignFormView = Marionette.ItemView.extend({
- template: Handlebars.compile(jQuery('#issue-detail-assign-form-template').html() || ''),
-
-
- ui: {
- select: '#issue-assignee-select'
- },
-
-
- events: {
- 'click #issue-assign-cancel': 'cancel',
- 'click #issue-assign-submit': 'submit'
- },
-
-
- onRender: function () {
- var currentUser = window.SS.currentUser,
- assignee = this.options.issue.get('assignee'),
- additionalChoices = [];
-
- if (!assignee || currentUser !== assignee) {
- additionalChoices.push({
- id: currentUser,
- text: window.SS.phrases.assignedToMe
- });
- }
-
- if (!!assignee) {
- additionalChoices.push({
- id: '',
- text: window.SS.phrases.unassigned
- });
- }
-
- var select2Options = {
- allowClear: false,
- width: '250px',
- formatNoMatches: function () {
- return window.SS.phrases.select2.noMatches;
- },
- formatSearching: function () {
- return window.SS.phrases.select2.searching;
- },
- formatInputTooShort: function () {
- return window.SS.phrases.select2.tooShort;
- }
- };
-
- if (additionalChoices.length > 0) {
- select2Options.minimumInputLength = 0;
- select2Options.query = function (query) {
- if (query.term.length == 0) {
- query.callback({ results: additionalChoices });
- } else if (query.term.length >= 2) {
- jQuery.ajax({
- url: baseUrl + '/api/users/search?f=s2',
- data: { s: query.term },
- dataType: 'jsonp'
- }).done(function (data) {
- query.callback(data);
- });
- }
- }
- } else {
- select2Options.minimumInputLength = 2;
- select2Options.ajax = {
- quietMillis: 300,
- url: baseUrl + '/api/users/search?f=s2',
- data: function (term, page) {
- return {s: term, p: page}
- },
- results: function (data) {
- return { more: data.more, results: data.results }
- }
- };
- }
-
- this.ui.select.select2(select2Options).select2('open');
- },
-
-
- cancel: function () {
- this.options.detailView.updateAfterAction(false);
- },
-
-
- submit: function () {
- var that = this;
-
- this.options.detailView.showActionSpinner();
-
- jQuery.ajax({
- type: 'POST',
- url: baseUrl + '/api/issues/assign',
- data: {
- issue: this.options.issue.get('key'),
- assignee: this.ui.select.val()
- }
- })
- .done(function () {
- that.options.detailView.updateAfterAction(true);
- })
- .fail(function (r) {
- alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r);
- that.options.detailView.hideActionSpinner();
- });
- }
- });
-
-
- var IssueDetailPlanFormView = Marionette.ItemView.extend({
- template: Handlebars.compile(jQuery('#issue-detail-plan-form-template').html() || ''),
-
-
- collectionEvents: {
- 'reset': 'render'
- },
-
-
- ui: {
- select: '#issue-detail-plan-select'
- },
-
-
- events: {
- 'click #issue-plan-cancel': 'cancel',
- 'click #issue-plan-submit': 'submit'
- },
-
-
- onRender: function () {
- this.ui.select.select2({
- width: '250px',
- minimumResultsForSearch: 100
- });
-
- this.$('.error a')
- .prop('href', baseUrl + '/action_plans/index/' + this.options.issue.get('project'));
- },
-
-
- cancel: function () {
- this.options.detailView.updateAfterAction(false);
- },
-
-
- submit: function () {
- var that = this,
- plan = this.ui.select.val();
-
- this.options.detailView.showActionSpinner();
-
- jQuery.ajax({
- type: 'POST',
- url: baseUrl + '/api/issues/plan',
- data: {
- issue: this.options.issue.get('key'),
- plan: plan === '#unplan' ? '' : plan
- }
- })
- .done(function () {
- that.options.detailView.updateAfterAction(true);
- })
- .fail(function (r) {
- alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r);
- that.options.detailView.hideActionSpinner();
- });
- },
-
-
- serializeData: function () {
- return {
- items: this.collection.toJSON(),
- issue: this.options.issue.toJSON()
- }
- }
- });
-
-
- var IssueDetailRuleView = Marionette.ItemView.extend({
- template: Handlebars.compile(jQuery('#issue-detail-rule-template').html() || ''),
- className: 'rule-desc',
- modelEvents: { 'change': 'render' },
-
-
- serializeData: function () {
- return _.extend({
- characteristic: this.options.issue.get('characteristic'),
- subCharacteristic: this.options.issue.get('subCharacteristic')
- }, this.model.toJSON());
- }
- });
-
-
- var IssueDetailView = Marionette.Layout.extend({
- template: Handlebars.compile(jQuery('#issue-detail-template').html() || ''),
-
-
- regions: {
- formRegion: '.code-issue-form',
- ruleRegion: '#tab-issue-rule'
- },
-
-
- events: {
- 'click': 'setDetailScope',
-
- 'click .code-issue-toggle': 'toggleCollapsed',
-
- 'click [href=#tab-issue-rule]': 'fetchRule',
-
- 'click #issue-comment': 'comment',
- 'click .issue-comment-edit': 'editComment',
- 'click .issue-comment-delete': 'deleteComment',
- 'click .issue-transition': 'transition',
- 'click #issue-set-severity': 'setSeverity',
- 'click #issue-assign': 'assign',
- 'click #issue-assign-to-me': 'assignToMe',
- 'click #issue-plan': 'plan',
- 'click .issue-action': 'action'
- },
-
-
- modelEvents: {
- 'change': 'render'
- },
-
-
- setDetailScope: function() {
- key.setScope('detail');
- },
-
-
- onRender: function () {
- this.$('.code-issue-details').tabs();
- this.$('.code-issue-form').hide();
- this.rule = new Rule({ key: this.model.get('rule') });
- this.ruleRegion.show(new IssueDetailRuleView({
- model: this.rule,
- issue: this.model
- }));
- this.initReferenceLinks();
- },
-
-
- initReferenceLinks: function () {
- var sourcesId = 'sources_' + this.model.get('key');
- this.$('#' + sourcesId).on('click', 'span.sym', { id: sourcesId }, highlight_usages);
- },
-
-
- onDomRefresh: function () {
- var sourceTitleHeight = this.$('.source_title').outerHeight();
- },
-
-
- onClose: function () {
- if (this.ruleRegion) {
- this.ruleRegion.reset();
- }
- },
-
-
- resetIssue: function (options) {
- var key = this.model.get('key');
- this.model.clear({ silent: true });
- this.model.set({ key: key }, { silent: true });
- return this.model.fetch(options);
- },
-
-
- toggleCollapsed: function () {
- this.$('.code-issue').toggleClass('code-issue-collapsed');
- this.fetchRule();
- },
-
-
- fetchRule: function () {
- var that = this;
- if (!this.rule.has('name')) {
- this.$('#tab-issue-rule').addClass('navigator-fetching');
- this.rule.fetch({
- success: function () {
- that.$('#tab-issue-rule').removeClass('navigator-fetching');
- }
- });
- }
- },
-
-
- showActionView: function (view) {
- this.$('.code-issue-actions').hide();
- this.$('.code-issue-form').show();
- this.formRegion.show(view);
- },
-
-
- showActionSpinner: function () {
- this.$('.code-issue-actions').addClass('navigator-fetching');
- },
-
-
- hideActionSpinner: function () {
- this.$('.code-issue-actions').removeClass('navigator-fetching');
- },
-
-
- updateAfterAction: function (fetch) {
- var that = this;
-
- that.formRegion.reset();
- that.$('.code-issue-actions').show();
- that.$('.code-issue-form').hide();
- that.$('[data-comment-key]').show();
-
- if (fetch) {
- jQuery.when(this.resetIssue()).done(function () {
- that.hideActionSpinner();
- });
- }
- },
-
-
- comment: function () {
- var commentFormView = new IssueDetailCommentFormView({
- issue: this.model,
- detailView: this
- });
- this.showActionView(commentFormView);
- },
-
-
- editComment: function (e) {
- var commentEl = jQuery(e.target).closest('[data-comment-key]'),
- commentKey = commentEl.data('comment-key'),
- comment = _.findWhere(this.model.get('comments'), { key: commentKey });
-
- commentEl.hide();
-
- var commentFormView = new IssueDetailCommentFormView({
- model: new Backbone.Model(comment),
- issue: this.model,
- detailView: this
- });
- this.showActionView(commentFormView);
- },
-
-
- deleteComment: function (e) {
- var that = this,
- commentKey = jQuery(e.target).closest('[data-comment-key]').data('comment-key'),
- confirmMsg = jQuery(e.target).data('confirm-msg');
-
- if (confirm(confirmMsg)) {
- this.showActionSpinner();
-
- jQuery.ajax({
- type: "POST",
- url: baseUrl + "/issue/delete_comment?id=" + commentKey
- })
- .done(function () {
- that.updateAfterAction(true);
- })
- .fail(function (r) {
- alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r);
- that.hideActionSpinner();
- });
- }
- },
-
-
- transition: function (e) {
- var that = this;
-
- this.showActionSpinner();
-
- jQuery.ajax({
- type: 'POST',
- url: baseUrl + '/api/issues/do_transition',
- data: {
- issue: this.model.get('key'),
- transition: jQuery(e.target).data('transition')
- }
- })
- .done(function () {
- that.resetIssue();
- })
- .fail(function (r) {
- alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r);
- that.hideActionSpinner();
- });
- },
-
-
- setSeverity: function () {
- var setSeverityFormView = new IssueDetailSetSeverityFormView({
- issue: this.model,
- detailView: this
- });
- this.showActionView(setSeverityFormView);
- },
-
-
- assign: function () {
- var assignFormView = new IssueDetailAssignFormView({
- issue: this.model,
- detailView: this
- });
- this.showActionView(assignFormView);
- },
-
-
- assignToMe: function () {
- var that = this;
-
- this.showActionSpinner();
-
- jQuery.ajax({
- type: 'POST',
- url: baseUrl + '/api/issues/assign',
- data: {
- issue: this.model.get('key'),
- assignee: window.SS.currentUser
- }
- })
- .done(function () {
- that.resetIssue();
- })
- .fail(function (r) {
- alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r);
- that.hideActionSpinner();
- });
- },
-
-
- plan: function () {
- var that = this,
- actionPlans = new ActionPlans(),
- planFormView = new IssueDetailPlanFormView({
- collection: actionPlans,
- issue: this.model,
- detailView: this
- });
-
- this.showActionSpinner();
-
- actionPlans.fetch({
- reset: true,
- data: { project: this.model.get('project') },
- success: function () {
- that.hideActionSpinner();
- that.showActionView(planFormView);
- }
- });
- },
-
- action: function (e) {
- var that = this,
- actionKey = jQuery(e.target).data('action');
-
- this.showActionSpinner();
-
- jQuery.ajax({
- type: 'POST',
- url: baseUrl + '/api/issues/do_action',
- data: {
- issue: this.model.get('key'),
- actionKey: actionKey
- }
- })
- .done(function () {
- that.resetIssue();
- })
- .fail(function (r) {
- alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r);
- that.hideActionSpinner();
- });
- },
-
-
- serializeData: function () {
- return _.extend({
- source: this.source,
- scm: this.scm
- }, this.model.toJSON());
- }
-
- });
-
var IssuesDetailsFavoriteFilterView = FavoriteFiltersModule.DetailsFavoriteFilterView.extend({
template: Handlebars.compile(jQuery('#issues-details-favorite-filter-template').html() || ''),
@@ -1308,7 +695,6 @@ define(
IssuesFilterBarView: IssuesFilterBarView,
IssuesHeaderView: IssuesHeaderView,
IssuesFavoriteFilterView: IssuesFavoriteFilterView,
- IssueDetailView: IssueDetailView,
IssuesRouter: IssuesRouter
};
diff --git a/sonar-server/src/main/less/component-viewer.less b/sonar-server/src/main/less/component-viewer.less
index 2fed3795c7a..03d66e988da 100644
--- a/sonar-server/src/main/less/component-viewer.less
+++ b/sonar-server/src/main/less/component-viewer.less
@@ -8,11 +8,27 @@
width: 100%;
min-width: 600px;
border: 1px solid @barBorderColor;
+ border-left-width: 0;
.box-sizing(border-box);
}
+.component-viewer-workspace-enabled {
+ border-left-width: 1px;
+
+ .component-viewer-workspace { display: block; }
+ .component-viewer-source { border-left: @workspaceWidth solid @barBackgroundColor; }
+}
+
+
+.component-viewer-title {
+ color: @baseFontColor;
+ font-size: @baseFontSize;
+ font-weight: bold;
+}
+
.component-viewer-workspace {
+ display: none;
float: left;
width: @workspaceWidth;
.box-sizing(border-box);
@@ -53,7 +69,6 @@
.component-viewer-source {
- border-left: @workspaceWidth solid @barBackgroundColor;
.sources2 {
border-left: 1px solid @barBorderColor;
@@ -119,6 +134,10 @@
cursor: pointer;
}
+ .sources2 .issue pre {
+ background-color: #ff9090;
+ }
+
.sources2 .row {
&.coverage-green td.stat {
@@ -136,27 +155,15 @@
&.lid { border-right-color: lighten(@red, 15%); }
}
-
- &:hover {
- td.line { background-color: @barBackgroundColor; }
-
- td.stat {
- background-color: darken(@barBackgroundColor, 3%);
- color: @baseFontColor;
- }
-
- &.coverage-green td.stat { background-color: lighten(@green, 10%); }
- &.coverage-red td.stat { background-color: lighten(@red, 10%); }
- }
}
}
.component-viewer-source-settings {
- visibility: hidden;
+ display: none;
- &.open { visibility: visible; }
+ &.open { display: inline-block; }
& > li {
display: inline;
diff --git a/sonar-server/src/main/less/icons.less b/sonar-server/src/main/less/icons.less
index 37b2f1d458d..f599f328130 100644
--- a/sonar-server/src/main/less/icons.less
+++ b/sonar-server/src/main/less/icons.less
@@ -338,6 +338,7 @@ a[class^="icon-"], a[class*=" icon-"] {
}
.icon-comment:before {
content: "\f075";
+ font-size: @iconSmallFontSize;
}
.icon-delete:before {
content: "\f00d";
diff --git a/sonar-server/src/main/less/navigator/base.less b/sonar-server/src/main/less/navigator/base.less
index d9edbf66b8b..32877861e08 100644
--- a/sonar-server/src/main/less/navigator/base.less
+++ b/sonar-server/src/main/less/navigator/base.less
@@ -75,6 +75,8 @@
.navigator-details {
position: relative;
margin: 0 @navigatorPadding;
+
+ .component-viewer { margin-bottom: 10px; }
}
.navigator-resizer {
diff --git a/sonar-server/src/main/less/style.less b/sonar-server/src/main/less/style.less
index 2e75b6fd2e3..5cdb6e32113 100644
--- a/sonar-server/src/main/less/style.less
+++ b/sonar-server/src/main/less/style.less
@@ -857,6 +857,8 @@ th.operations, td.operations {
.code-issue-permalink {
position: absolute;
top: 4px; right: 4px;
+
+ a { text-decoration: none; }
}
.code-issue-name-extra {
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/component_viewer_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/component_viewer_controller.rb
deleted file mode 100644
index fdff4bfdff5..00000000000
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/component_viewer_controller.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-
-class ComponentViewerController < ApplicationController
-
- def index
-
- end
-
-end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issues/search.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issues/search.html.erb
index cba925a13ba..c30a3ecbef0 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/issues/search.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/issues/search.html.erb
@@ -174,4 +174,40 @@
'Sa': '<%= escape_javascript message("Sa") -%>'
}
});
+
+ window.messages = {
+ 'all': '<%= escape_javascript message('all') -%>',
+ 'assigned_to': '<%= escape_javascript message('assigned_to') -%>',
+ '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') -%>'
+ };
</script>