]> source.dussan.org Git - sonarqube.git/commitdiff
Component Viewer: integrate into issue page
authorStas Vilchik <vilchiks@gmail.com>
Fri, 25 Apr 2014 07:21:14 +0000 (13:21 +0600)
committerStas Vilchik <vilchiks@gmail.com>
Fri, 25 Apr 2014 07:21:25 +0000 (13:21 +0600)
27 files changed:
sonar-server/Gruntfile.coffee
sonar-server/src/main/coffee/component-viewer/app.coffee [deleted file]
sonar-server/src/main/coffee/component-viewer/main.coffee
sonar-server/src/main/coffee/component-viewer/source.coffee
sonar-server/src/main/coffee/issues/collections/action-plans.coffee [new file with mode: 0644]
sonar-server/src/main/coffee/issues/issue-view.coffee [new file with mode: 0644]
sonar-server/src/main/coffee/issues/models/issue.coffee [new file with mode: 0644]
sonar-server/src/main/coffee/issues/models/rule.coffee [new file with mode: 0644]
sonar-server/src/main/coffee/issues/views/assign-form-view.coffee [new file with mode: 0644]
sonar-server/src/main/coffee/issues/views/comment-form-view.coffee [new file with mode: 0644]
sonar-server/src/main/coffee/issues/views/plan-form-view.coffee [new file with mode: 0644]
sonar-server/src/main/coffee/issues/views/rule-view.coffee [new file with mode: 0644]
sonar-server/src/main/coffee/issues/views/set-severity-form-view.coffee [new file with mode: 0644]
sonar-server/src/main/hbs/component-viewer/source.hbs
sonar-server/src/main/hbs/issues/assign-form.hbs [new file with mode: 0644]
sonar-server/src/main/hbs/issues/comment-form.hbs [new file with mode: 0644]
sonar-server/src/main/hbs/issues/issue.hbs [new file with mode: 0644]
sonar-server/src/main/hbs/issues/plan-form.hbs [new file with mode: 0644]
sonar-server/src/main/hbs/issues/rule.hbs [new file with mode: 0644]
sonar-server/src/main/hbs/issues/set-severity-form.hbs [new file with mode: 0644]
sonar-server/src/main/js/issues/extra.js
sonar-server/src/main/less/component-viewer.less
sonar-server/src/main/less/icons.less
sonar-server/src/main/less/navigator/base.less
sonar-server/src/main/less/style.less
sonar-server/src/main/webapp/WEB-INF/app/controllers/component_viewer_controller.rb [deleted file]
sonar-server/src/main/webapp/WEB-INF/app/views/issues/search.html.erb

index 5a5d14c69b5a428e923821db0b30ddeb48ad6909..498263d1a7734f9bd2e0c578203210125b40cd61 100644 (file)
@@ -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 (file)
index 85ec0f9..0000000
+++ /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
index c92b1c74c36db32765a33b85cb52aea288a1f662..c231570efd1c4ebbc85239eac6513e77b5d4210b 100644 (file)
@@ -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)
index 27f72f9da408cb3ac7d8777121eda1963bcf542a..72f7c7e7b14158e80deeb4559c16540e9274811e 100644 (file)
@@ -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 (file)
index 0000000..69e0d45
--- /dev/null
@@ -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 (file)
index 0000000..0c1dc02
--- /dev/null
@@ -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 (file)
index 0000000..9011a28
--- /dev/null
@@ -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 (file)
index 0000000..570feb8
--- /dev/null
@@ -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 (file)
index 0000000..433a1b6
--- /dev/null
@@ -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 (file)
index 0000000..57c0393
--- /dev/null
@@ -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 (file)
index 0000000..c713020
--- /dev/null
@@ -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 (file)
index 0000000..caf05d4
--- /dev/null
@@ -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 (file)
index 0000000..96d756d
--- /dev/null
@@ -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
index 66a08b3844ebeb83eed386f6b8511fc40a05647b..53c30e18648a7899a039f227c2d4d7c9d2437407 100644 (file)
           <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 (file)
index 0000000..f25a050
--- /dev/null
@@ -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 (file)
index 0000000..f517dfd
--- /dev/null
@@ -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 (file)
index 0000000..8e75cd7
--- /dev/null
@@ -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 (file)
index 0000000..3d96e6e
--- /dev/null
@@ -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 (file)
index 0000000..67b6350
--- /dev/null
@@ -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 (file)
index 0000000..e85c027
--- /dev/null
@@ -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
index 83df044945fcd561d6ea95138c13b783e22abd90..aff0dfcce54b4323f683a7c1f7f109381b829c51 100644 (file)
@@ -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
       };
 
index 2fed3795c7ab63a0afca44b328fad776c6a7fb89..03d66e988da37b36bce788dbf49d2a5f0d3440b3 100644 (file)
@@ -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;
     cursor: pointer;
   }
 
+  .sources2 .issue pre {
+    background-color: #ff9090;
+  }
+
   .sources2 .row {
 
     &.coverage-green td.stat {
 
       &.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;
index 37b2f1d458d4bce93251edc44fcabeb5b673e4e4..f599f328130fda3372d5180ff994b52202a93d66 100644 (file)
@@ -338,6 +338,7 @@ a[class^="icon-"], a[class*=" icon-"] {
 }
 .icon-comment:before {
   content: "\f075";
+  font-size: @iconSmallFontSize;
 }
 .icon-delete:before {
   content: "\f00d";
index d9edbf66b8b27757d9d1bef5714cba4762960bbb..32877861e084f335758112f3fdac72b74b089345 100644 (file)
@@ -75,6 +75,8 @@
 .navigator-details {
   position: relative;
   margin: 0 @navigatorPadding;
+
+  .component-viewer { margin-bottom: 10px; }
 }
 
 .navigator-resizer {
index 2e75b6fd2e3954febc2a212f5c6bd5e15cec6a67..5cdb6e32113fcc75da78058224c6cd72ec6c4d6c 100644 (file)
@@ -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 (file)
index fdff4bf..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-class ComponentViewerController < ApplicationController
-
-  def index
-
-  end
-
-end
index cba925a13ba681e17052e88c4712b7d3d641eb7e..c30a3ecbef0428680343d9fc28b9bc0fc469c955 100644 (file)
       '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>