]> source.dussan.org Git - sonarqube.git/commitdiff
move issue component from coffee to js
authorStas Vilchik <vilchiks@gmail.com>
Thu, 28 May 2015 15:03:02 +0000 (17:03 +0200)
committerStas Vilchik <vilchiks@gmail.com>
Fri, 29 May 2015 12:28:48 +0000 (14:28 +0200)
57 files changed:
server/sonar-web/Gruntfile.coffee
server/sonar-web/src/main/coffee/components/issue/collections/action-plans.coffee [deleted file]
server/sonar-web/src/main/coffee/components/issue/collections/issues.coffee [deleted file]
server/sonar-web/src/main/coffee/components/issue/issue-view.coffee [deleted file]
server/sonar-web/src/main/coffee/components/issue/manual-issue-view.coffee [deleted file]
server/sonar-web/src/main/coffee/components/issue/models/changelog.coffee [deleted file]
server/sonar-web/src/main/coffee/components/issue/models/issue.coffee [deleted file]
server/sonar-web/src/main/coffee/components/issue/templates/comment-form.hbs [deleted file]
server/sonar-web/src/main/coffee/components/issue/templates/issue-assign-form-option.hbs [deleted file]
server/sonar-web/src/main/coffee/components/issue/templates/issue-assign-form.hbs [deleted file]
server/sonar-web/src/main/coffee/components/issue/templates/issue-changelog.hbs [deleted file]
server/sonar-web/src/main/coffee/components/issue/templates/issue-more-actions.hbs [deleted file]
server/sonar-web/src/main/coffee/components/issue/templates/issue-plan-form.hbs [deleted file]
server/sonar-web/src/main/coffee/components/issue/templates/issue-set-severity-form.hbs [deleted file]
server/sonar-web/src/main/coffee/components/issue/templates/issue-tags-form-option.hbs [deleted file]
server/sonar-web/src/main/coffee/components/issue/templates/issue-tags-form.hbs [deleted file]
server/sonar-web/src/main/coffee/components/issue/templates/issue-transitions-form.hbs [deleted file]
server/sonar-web/src/main/coffee/components/issue/templates/issue.hbs [deleted file]
server/sonar-web/src/main/coffee/components/issue/templates/manual-issue.hbs [deleted file]
server/sonar-web/src/main/coffee/components/issue/views/action-options-view.coffee [deleted file]
server/sonar-web/src/main/coffee/components/issue/views/assign-form-view.coffee [deleted file]
server/sonar-web/src/main/coffee/components/issue/views/changelog-view.coffee [deleted file]
server/sonar-web/src/main/coffee/components/issue/views/comment-form-view.coffee [deleted file]
server/sonar-web/src/main/coffee/components/issue/views/issue-popup.coffee [deleted file]
server/sonar-web/src/main/coffee/components/issue/views/more-actions-view.coffee [deleted file]
server/sonar-web/src/main/coffee/components/issue/views/plan-form-view.coffee [deleted file]
server/sonar-web/src/main/coffee/components/issue/views/set-severity-form-view.coffee [deleted file]
server/sonar-web/src/main/coffee/components/issue/views/tags-form-view.coffee [deleted file]
server/sonar-web/src/main/coffee/components/issue/views/transitions-form-view.coffee [deleted file]
server/sonar-web/src/main/js/components/issue/collections/action-plans.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/collections/issues.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/issue-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/manual-issue-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/models/changelog.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/models/issue.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/templates/comment-form.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/templates/issue-assign-form-option.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/templates/issue-assign-form.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/templates/issue-changelog.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/templates/issue-more-actions.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/templates/issue-plan-form.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/templates/issue-set-severity-form.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/templates/issue-tags-form-option.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/templates/issue-tags-form.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/templates/issue-transitions-form.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/templates/issue.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/templates/manual-issue.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/views/action-options-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/views/assign-form-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/views/changelog-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/views/comment-form-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/views/issue-popup.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/views/more-actions-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/views/plan-form-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/views/set-severity-form-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/views/tags-form-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/views/transitions-form-view.js [new file with mode: 0644]

index 9b8eb566cc9cd3f8ad9be25ea42fd7f89fa9d5df..2339f6fe636223145665df8479804424d0c419ba 100644 (file)
@@ -203,7 +203,7 @@ module.exports = (grunt) ->
           ]
           '<%= BUILD_PATH %>/js/components/issue/templates.js': [
             '<%= SOURCE_PATH %>/js/components/common/templates/**/*.hbs'
-            '<%= SOURCE_PATH %>/coffee/components/issue/templates/**/*.hbs'
+            '<%= SOURCE_PATH %>/js/components/issue/templates/**/*.hbs'
           ]
           '<%= BUILD_PATH %>/js/apps/issues/templates.js': [
             '<%= SOURCE_PATH %>/coffee/apps/issues/templates/**/*.hbs'
diff --git a/server/sonar-web/src/main/coffee/components/issue/collections/action-plans.coffee b/server/sonar-web/src/main/coffee/components/issue/collections/action-plans.coffee
deleted file mode 100644 (file)
index 91b5a35..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define ->
-
-  class ActionPlans extends Backbone.Collection
-
-    url: ->
-      "#{baseUrl}/api/action_plans/search"
-
-
-    parse: (r) ->
-      r.actionPlans
diff --git a/server/sonar-web/src/main/coffee/components/issue/collections/issues.coffee b/server/sonar-web/src/main/coffee/components/issue/collections/issues.coffee
deleted file mode 100644 (file)
index 208a06f..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  '../models/issue'
-], (
-  Issue
-) ->
-
-  class extends Backbone.Collection
-    model: Issue
-
-    url: ->
-      "#{baseUrl}/api/issues/search"
-
-
-    parse: (r) ->
-      find = (source, key, keyField) ->
-        searchDict = {}
-        searchDict[keyField || 'key'] = key
-        _.findWhere(source, searchDict) || key
-
-      @paging =
-        p: r.p
-        ps: r.ps
-        total: r.total
-        maxResultsReached: r.p * r.ps >= r.total
-
-      r.issues.map (issue) ->
-        component = find r.components, issue.component
-        project = find r.projects, issue.project
-        rule = find r.rules, issue.rule
-        assignee = find r.users, issue.assignee, 'login'
-
-        if component
-          _.extend issue,
-            componentLongName: component.longName
-            componentQualifier: component.qualifier
-
-        if project
-          _.extend issue,
-            projectLongName: project.longName
-            projectUuid: project.uuid
-
-        if rule
-          _.extend issue,
-            ruleName: rule.name
-
-        if assignee
-          _.extend issue,
-            assigneeEmail: assignee.email
-
-        issue
diff --git a/server/sonar-web/src/main/coffee/components/issue/issue-view.coffee b/server/sonar-web/src/main/coffee/components/issue/issue-view.coffee
deleted file mode 100644 (file)
index dd8189f..0000000
+++ /dev/null
@@ -1,350 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  './models/changelog'
-  './views/changelog-view'
-
-  './collections/action-plans'
-
-  './views/issue-popup'
-
-  './views/transitions-form-view'
-  './views/assign-form-view'
-  './views/comment-form-view'
-  './views/plan-form-view'
-  './views/set-severity-form-view'
-  './views/more-actions-view'
-  './views/tags-form-view'
-
-  'components/workspace/main'
-
-  './templates'
-
-], (
-  ChangeLog
-  ChangeLogView
-
-  ActionPlans
-
-  IssuePopup
-
-  TransitionsFormView
-  AssignFormView
-  CommentFormView
-  PlanFormView
-  SetSeverityFormView
-  MoreActionsView
-  TagsFormView
-
-  Workspace
-
-) ->
-
-  $ = jQuery
-
-
-  class extends Marionette.ItemView
-    className: 'issue'
-    template: Templates['issue']
-
-
-    modelEvents:
-      'change': 'render'
-
-
-    ui:
-      tagsChange: '.js-issue-edit-tags'
-      tagInput: '.issue-tag-input'
-      tagsEdit: '.issue-tag-edit'
-      tagsEditDone: '.issue-tag-edit-done'
-      tagsEditCancel: '.issue-tag-edit-cancel'
-      tagsList: '.issue-tag-list'
-
-
-    events: ->
-      'click .js-issue-comment': 'comment'
-      'click .js-issue-comment-edit': 'editComment'
-      'click .js-issue-comment-delete': 'deleteComment'
-
-      'click .js-issue-transition': 'transition'
-      'click .js-issue-set-severity': 'setSeverity'
-      'click .js-issue-assign': 'assign'
-      'click .js-issue-assign-to-me': 'assignToMe'
-      'click .js-issue-plan': 'plan'
-      'click .js-issue-show-changelog': 'showChangeLog'
-      'click .js-issue-more': 'showMoreActions'
-      'click .js-issue-rule': 'showRule'
-      'click .js-issue-edit-tags': 'editTags'
-
-
-    onRender: ->
-      @ui.tagsEdit.hide()
-      @$el.attr 'data-key', @model.get('key')
-
-
-    resetIssue: (options) ->
-      key = @model.get 'key'
-      componentUuid = @model.get 'componentUuid'
-      @model.clear silent: true
-      @model.set { key: key, componentUuid: componentUuid }, { silent: true }
-      @model.fetch(options)
-      .done =>
-        @trigger 'reset'
-
-
-    showChangeLog: (e) ->
-      t = $(e.currentTarget)
-      changeLog = new ChangeLog()
-      changeLog.fetch
-        data: issue: @model.get 'key'
-      .done =>
-        @popup.close() if @popup
-        @popup = new ChangeLogView
-          triggerEl: t
-          bottomRight: true
-          collection: changeLog
-          issue: @model
-        @popup.render()
-
-
-    showActionView: (view, el, position) ->
-      if el
-        @popup.close() if @popup
-        options =
-          view: view
-          triggerEl: el
-        if position?
-          options[position] = true
-        else
-          options['bottom'] = true
-        @popup = new IssuePopup options
-        @popup.render()
-
-
-    showActionSpinner: ->
-      @$('.code-issue-actions').addClass 'navigator-fetching'
-
-
-    hideActionSpinner: ->
-      @$('.code-issue-actions').removeClass 'navigator-fetching'
-
-
-    updateAfterAction: (fetch) ->
-      @popup.close() if @popup
-      if fetch
-        @resetIssue()
-
-
-    comment: (e) ->
-      e.stopPropagation()
-      $('body').click()
-      @popup = new CommentFormView
-        triggerEl: $(e.currentTarget)
-        bottom: true
-        issue: @model
-        detailView: @
-      @popup.render()
-
-
-    editComment: (e) ->
-      e.stopPropagation()
-      $('body').click()
-      commentEl = $(e.currentTarget).closest '.issue-comment'
-      commentKey = commentEl.data 'comment-key'
-      comment = _.findWhere @model.get('comments'), { key: commentKey }
-      @popup = new CommentFormView
-        triggerEl: $(e.currentTarget)
-        bottomRight: true
-        model: new Backbone.Model comment
-        issue: @model
-        detailView: @
-      @popup.render()
-
-
-    deleteComment: (e) ->
-      commentKey = $(e.target).closest('[data-comment-key]').data 'comment-key'
-      confirmMsg = $(e.target).data 'confirm-msg'
-
-      if confirm(confirmMsg)
-        $.ajax
-          type: "POST"
-          url: baseUrl + "/api/issues/delete_comment?key=" + commentKey
-        .done =>
-          @updateAfterAction true
-
-
-    transition: (e) ->
-      e.stopPropagation()
-      $('body').click()
-      @popup = new TransitionsFormView
-        triggerEl: $(e.currentTarget)
-        bottom: true
-        model: @model
-        view: @
-      @popup.render()
-
-
-    setSeverity: (e) ->
-      e.stopPropagation()
-      $('body').click()
-      @popup = new SetSeverityFormView
-        triggerEl: $(e.currentTarget)
-        bottom: true
-        model: @model
-      @popup.render()
-
-
-    assign: (e) ->
-      e.stopPropagation()
-      $('body').click()
-      @popup = new AssignFormView
-        triggerEl: $(e.currentTarget)
-        bottom: true
-        model: @model
-      @popup.render()
-
-
-    assignToMe: ->
-      view = new AssignFormView model: @model, triggerEl: $('body')
-      view.submit window.SS.user, window.SS.userName
-      view.close()
-
-
-    plan: (e) ->
-      t = $(e.currentTarget)
-      actionPlans = new ActionPlans()
-      actionPlans.fetch
-        reset: true
-        data: project: @model.get('project')
-      .done =>
-        e.stopPropagation()
-        $('body').click()
-        @popup = new PlanFormView
-          triggerEl: t
-          bottom: true
-          model: @model
-          collection: actionPlans
-        @popup.render()
-
-
-    showMoreActions: (e) ->
-      e.stopPropagation()
-      $('body').click()
-      @popup = new MoreActionsView
-        triggerEl: $(e.currentTarget)
-        bottomRight: true
-        model: @model
-        detailView: @
-      @popup.render()
-
-
-    action: (action) ->
-      $.post "#{baseUrl}/api/issues/do_action", issue: @model.id, actionKey: action
-      .done =>
-        @resetIssue()
-
-
-    showRule: ->
-      unless Workspace?
-        Workspace = require 'components/workspace/main'
-      ruleKey = @model.get 'rule'
-      Workspace.openRule key: ruleKey
-
-
-    editTags: (e)->
-      e.stopPropagation()
-      $('body').click()
-      @popup = new TagsFormView
-        triggerEl: $(e.currentTarget)
-        bottomRight: true
-        model: @model
-      @popup.render()
-
-
-    changeTags: ->
-      jQuery.ajax
-        url: "#{baseUrl}/api/issues/tags?ps=0"
-      .done (r) =>
-        if @ui.tagInput.select2
-          # Prevent synchronization issue with navigation
-          @ui.tagInput.select2
-            tags: (_.difference r.tags, @model.get 'tags')
-            width: '300px'
-        if @ui.tagsEdit.show
-          @ui.tagsEdit.show()
-        if @ui.tagsList.hide
-          @ui.tagsList.hide()
-        @tagsBuffer = @ui.tagInput.select2 'val'
-
-        keyScope = key.getScope()
-        if keyScope != 'tags'
-          @previousKeyScope = keyScope
-        key.setScope 'tags'
-        key 'escape', 'tags', => @cancelEdit()
-
-        @$('.select2-input').keyup((event) =>
-          if (event.which == 27)
-            @cancelEdit()
-        )
-
-        @ui.tagInput.select2 'focus'
-
-
-    cancelEdit: ->
-      @resetKeyScope()
-
-      if @ui.tagsList.show
-        @ui.tagsList.show()
-      if @ui.tagInput.select2
-        @ui.tagInput.select2 'val', @tagsBuffer
-        @ui.tagInput.select2 'close'
-      if @ui.tagsEdit.hide
-        @ui.tagsEdit.hide()
-
-
-    editDone: ->
-      @resetKeyScope()
-
-      _tags = @model.get 'tags'
-      tags = @ui.tagInput.val()
-      splitTags = if tags then tags.split(',') else null
-
-      @model.set 'tags', splitTags
-      $.post "#{baseUrl}/api/issues/set_tags", key: @model.get('key'), tags: tags
-      .done =>
-        @cancelEdit()
-      .fail =>
-        @model.set 'tags', _tags
-      .always =>
-        @render()
-
-
-    resetKeyScope: ->
-      key.unbind 'escape', 'tags'
-      if @previousKeyScope
-        key.setScope @previousKeyScope
-        @previousKeyScope = null
-
-
-    serializeData: ->
-      issueKey = encodeURIComponent @model.get 'key'
-      _.extend super,
-        permalink: "#{baseUrl}/issues/search#issues=#{issueKey}"
diff --git a/server/sonar-web/src/main/coffee/components/issue/manual-issue-view.coffee b/server/sonar-web/src/main/coffee/components/issue/manual-issue-view.coffee
deleted file mode 100644 (file)
index c0030e1..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  './templates'
-], ->
-
-  $ = jQuery
-  API_ISSUE = "#{baseUrl}/api/issues/show"
-  API_ADD_MANUAL_ISSUE = "#{baseUrl}/api/issues/create"
-
-
-  class extends Marionette.ItemView
-    template: Templates['manual-issue']
-
-
-    events:
-      'submit .js-manual-issue-form': 'formSubmit'
-      'click .js-cancel': 'cancel'
-
-
-    initialize: ->
-      @rules = []
-      $.get("#{baseUrl}/api/rules/search?repositories=manual&f=name&ps=999999").done (data) =>
-        @rules = data.rules
-        @render()
-
-
-    onRender: ->
-      @delegateEvents()
-      @$('[name=rule]').select2
-        width: '250px'
-        minimumResultsForSearch: 10
-      @$('[name=rule]').select2 'open' if @rules.length > 0
-      if key?
-        @key = key.getScope()
-        key.setScope ''
-
-
-    onClose: ->
-      key.setScope @key if key? && @key?
-
-
-    showSpinner: ->
-      @$('.js-submit').hide()
-      @$('.js-spinner').show()
-
-
-    hideSpinner: ->
-      @$('.js-submit').show()
-      @$('.js-spinner').hide()
-
-
-    validateFields: ->
-      message = @$('[name=message]')
-      unless message.val()
-        message.addClass('invalid').focus()
-        return false
-      return true
-
-
-    formSubmit: (e) ->
-      e.preventDefault()
-      return unless @validateFields()
-      @showSpinner()
-      data = $(e.currentTarget).serialize()
-      $.post API_ADD_MANUAL_ISSUE, data
-        .done (r) =>
-          r = JSON.parse(r) if typeof r == 'string'
-          @addIssue r.issue.key
-        .fail (r) =>
-          @hideSpinner()
-          if r.responseJSON?.errors?
-            @showError _.pluck(r.responseJSON.errors, 'msg').join '. '
-
-
-    addIssue: (key) ->
-      $.get API_ISSUE, key: key, (r) =>
-        @trigger 'add', r.issue
-        @close()
-
-
-    showError: (msg) ->
-      @$('.code-issue-errors').removeClass('hidden').text msg
-
-
-    cancel: (e) ->
-      e.preventDefault()
-      @close()
-
-
-    serializeData: ->
-      _.extend super,
-        line: @options.line
-        component: @options.component
-        rules: _.sortBy @rules, 'name'
diff --git a/server/sonar-web/src/main/coffee/components/issue/models/changelog.coffee b/server/sonar-web/src/main/coffee/components/issue/models/changelog.coffee
deleted file mode 100644 (file)
index 3aadd10..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define ->
-
-  class ChangeLog extends Backbone.Collection
-
-    url: ->
-      "#{baseUrl}/api/issues/changelog"
-
-
-    parse: (r) ->
-      return r.changelog
diff --git a/server/sonar-web/src/main/coffee/components/issue/models/issue.coffee b/server/sonar-web/src/main/coffee/components/issue/models/issue.coffee
deleted file mode 100644 (file)
index e5f99dc..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define ->
-
-  class Issue extends Backbone.Model
-    idAttribute: 'key'
-
-
-    url: ->
-      "#{baseUrl}/api/issues/show?key=#{@get('key')}"
-
-
-    parse: (r) ->
-      if r.issue then r.issue else r
diff --git a/server/sonar-web/src/main/coffee/components/issue/templates/comment-form.hbs b/server/sonar-web/src/main/coffee/components/issue/templates/comment-form.hbs
deleted file mode 100644 (file)
index 9bd5139..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<div class="issue-comment-form-text">
-  <textarea rows="2">{{show raw markdown}}</textarea>
-</div>
-
-<div class="issue-comment-form-footer">
-  <div class="issue-comment-form-actions">
-    <div class="button-group">
-      <button class="js-issue-comment-submit" disabled>
-        {{#if id}}{{t 'save'}}{{else}}{{t 'issue.comment.submit'}}{{/if}}
-      </button>
-    </div>
-    <a class="js-issue-comment-cancel">{{t 'cancel'}}</a>
-  </div>
-
-  <div class="issue-comment-form-tips">{{> '_markdown-tips' }}</div>
-</div>
-
-<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/coffee/components/issue/templates/issue-assign-form-option.hbs b/server/sonar-web/src/main/coffee/components/issue/templates/issue-assign-form-option.hbs
deleted file mode 100644 (file)
index e35905f..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-<a href="#" class="issue-action-option js-issue-assignee" data-value="{{id}}" data-text="{{text}}">
-  {{text}}
-</a>
diff --git a/server/sonar-web/src/main/coffee/components/issue/templates/issue-assign-form.hbs b/server/sonar-web/src/main/coffee/components/issue/templates/issue-assign-form.hbs
deleted file mode 100644 (file)
index 9d20be8..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<div class="issue-action-options">
-  <i class="icon-search issue-action-options-search-icon"></i>
-  <input class="issue-action-options-search" type="text" placeholder="Search..." value="{{query}}">
-</div>
-
-<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/coffee/components/issue/templates/issue-changelog.hbs b/server/sonar-web/src/main/coffee/components/issue/templates/issue-changelog.hbs
deleted file mode 100644 (file)
index fd6f33d..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<div class="issue-changelog">
-  <table class="spaced">
-    <tbody>
-
-    <tr>
-      <td class="thin text-left text-top" nowrap>{{dt issue.creationDate}}</td>
-      <td class="thin text-left text-top" nowrap></td>
-      <td class="text-left text-top">
-        {{#if issue.reporter}}
-          {{t 'issue.reported_by'}} {{default issue.reporterName issue.reporter}}
-        {{else}}
-          {{#if issue.author}}
-            {{t 'created_by'}} {{issue.author}}
-          {{else}}
-            {{t 'created'}}
-          {{/if}}
-        {{/if}}
-      </td>
-    </tr>
-
-    {{#each items}}
-      <tr>
-        <td class="thin text-left text-top" nowrap>{{dt creationDate}}</td>
-        <td class="thin text-left text-top" nowrap>
-          {{#ifShowAvatars}}{{avatarHelper email 16}}{{/ifShowAvatars}}
-          {{userName}}
-        </td>
-        <td class="text-left text-top">
-          {{#each diffs}}
-            {{changelog this}}<br>
-          {{/each}}
-        </td>
-      </tr>
-    {{/each}}
-    </tbody>
-  </table>
-</div>
-
-<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/coffee/components/issue/templates/issue-more-actions.hbs b/server/sonar-web/src/main/coffee/components/issue/templates/issue-more-actions.hbs
deleted file mode 100644 (file)
index 18917b5..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<ul class="issue-more-actions">
-  {{#pluginActions actions}}
-    <li>
-      <a class="issue-action js-issue-action" data-action="{{this}}">{{t "issue.action" this "formlink"}}</a>
-    </li>
-  {{/pluginActions}}
-</ul>
-
-<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/coffee/components/issue/templates/issue-plan-form.hbs b/server/sonar-web/src/main/coffee/components/issue/templates/issue-plan-form.hbs
deleted file mode 100644 (file)
index d4e693a..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<div class="issue-action-options">
-  {{#each items}}
-    {{#notEq status 'CLOSED'}}
-      <a href="#" class="issue-action-option js-issue-assignee" data-value="{{key}}" data-text="{{name}}">
-        {{name}}
-      </a>
-    {{/notEq}}
-  {{/each}}
-</div>
-
-<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/coffee/components/issue/templates/issue-set-severity-form.hbs b/server/sonar-web/src/main/coffee/components/issue/templates/issue-set-severity-form.hbs
deleted file mode 100644 (file)
index 7007fdd..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<div class="issue-action-options">
-  {{#each items}}
-    <a href="#" class="issue-action-option js-issue-severity" data-value="{{this}}">
-      {{severityIcon this}} {{t 'severity' this}}
-    </a>
-  {{/each}}
-</div>
-
-<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/coffee/components/issue/templates/issue-tags-form-option.hbs b/server/sonar-web/src/main/coffee/components/issue/templates/issue-tags-form-option.hbs
deleted file mode 100644 (file)
index df054fe..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<a href="#" class="issue-action-option" data-value="{{tag}}" data-text="{{tag}}"
-   {{#if selected}}data-selected{{/if}}>
-
-  {{#if selected}}
-    <i class="icon-checkbox icon-checkbox-checked"></i>
-  {{else}}
-    <i class="icon-checkbox"></i>
-  {{/if}}
-
-  {{#if custom}}
-    + {{tag}}
-  {{else}}
-    {{tag}}
-  {{/if}}
-</a>
diff --git a/server/sonar-web/src/main/coffee/components/issue/templates/issue-tags-form.hbs b/server/sonar-web/src/main/coffee/components/issue/templates/issue-tags-form.hbs
deleted file mode 100644 (file)
index 9d20be8..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<div class="issue-action-options">
-  <i class="icon-search issue-action-options-search-icon"></i>
-  <input class="issue-action-options-search" type="text" placeholder="Search..." value="{{query}}">
-</div>
-
-<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/coffee/components/issue/templates/issue-transitions-form.hbs b/server/sonar-web/src/main/coffee/components/issue/templates/issue-transitions-form.hbs
deleted file mode 100644 (file)
index 2fa1f4c..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<div class="issue-action-options">
-  {{#each transitions}}
-    <a href="#" class="issue-action-option js-issue-transition" data-value="{{this}}"
-        title="{{t 'issue.transition' this 'description'}}" data-placement="right" data-toggle="tooltip">
-      {{t 'issue.transition' this}}
-    </a>
-  {{/each}}
-</div>
-
-<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/coffee/components/issue/templates/issue.hbs b/server/sonar-web/src/main/coffee/components/issue/templates/issue.hbs
deleted file mode 100644 (file)
index f5a2d5e..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-<div class="issue-inner">
-
-  <table class="issue-table">
-    <tr>
-      <td>
-        <div class="issue-message">{{message}} <a class="js-issue-rule issue-rule icon-ellipsis-h"></a></div>
-      </td>
-
-      <td class="issue-table-meta-cell issue-table-meta-cell-first">
-        <div class="issue-meta-list">
-          <div class="issue-meta">
-            <a class="issue-action issue-action-with-options js-issue-show-changelog" title="{{dt creationDate}}">
-              <span class="issue-meta-label">{{fromNow creationDate}}</span>&nbsp;<i class="icon-dropdown"></i>
-            </a>
-          </div>
-
-          {{#if line}}
-            <div class="issue-meta">
-              <span class="issue-meta-label" title="{{t 'line_number'}}">L{{line}}</span>
-            </div>
-          {{/if}}
-
-          <div class="issue-meta">
-            <a class="issue-action js-issue-permalink icon-link" href="{{permalink}}" target="_blank"></a>
-          </div>
-        </div>
-      </td>
-    </tr>
-  </table>
-
-  <table class="issue-table">
-    <tr>
-      <td>
-        <div class="issue-meta-list">
-          <div class="issue-meta">
-            {{#inArray actions "set_severity"}}
-              <a class="issue-action issue-action-with-options js-issue-set-severity">
-                <span class="issue-meta-label">{{severityHelper severity}}</span>&nbsp;<i class="icon-dropdown"></i>
-              </a>
-            {{else}}
-              {{severityHelper severity}}
-            {{/inArray}}
-          </div>
-
-          <div class="issue-meta">
-            {{#notEmpty transitions}}
-              <a class="issue-action issue-action-with-options js-issue-transition">
-                <span class="issue-meta-label">{{statusHelper status resolution}}</span>&nbsp;<i
-                  class="icon-dropdown"></i>
-              </a>
-            {{else}}
-              {{statusHelper status resolution}}
-            {{/notEmpty}}
-          </div>
-
-          <div class="issue-meta">
-            {{#inArray actions "assign"}}
-              <a class="issue-action issue-action-with-options js-issue-assign">
-                {{#if assignee}}
-                  {{#ifShowAvatars}}
-                    <span class="text-top">{{avatarHelper assigneeEmail 16}}</span>
-                  {{/ifShowAvatars}}
-                {{/if}}
-                <span class="issue-meta-label">{{#if assignee}}{{default assigneeName assignee}}{{else}}{{t 'unassigned'}}{{/if}}</span>&nbsp;<i class="icon-dropdown"></i>
-              </a>
-            {{else}}
-              {{#if assignee}}
-                {{#ifShowAvatars}}
-                  <span class="text-top">{{avatarHelper assigneeEmail 16}}</span>
-                {{/ifShowAvatars}}
-              {{/if}}
-              <span class="issue-meta-label">{{#if assignee}}{{default assigneeName assignee}}{{else}}{{t 'unassigned'}}{{/if}}</span>
-            {{/inArray}}
-          </div>
-
-          {{#inArray actions "assign_to_me"}}
-            <a class="js-issue-assign-to-me"></a>
-          {{/inArray}}
-
-          <div class="issue-meta">
-            {{#inArray actions "plan"}}
-              <a class="issue-action issue-action-with-options js-issue-plan">
-          <span
-              class="issue-meta-label">{{#if actionPlan}}{{default actionPlanName actionPlan}}{{else}}{{t 'issue.unplanned'}}{{/if}}</span>&nbsp;<i
-                  class="icon-dropdown"></i>
-              </a>
-            {{else}}
-              <span
-                  class="issue-meta-label">{{#if actionPlan}}{{default actionPlanName actionPlan}}{{else}}{{t 'issue.unplanned'}}{{/if}}</span>
-            {{/inArray}}
-          </div>
-
-          {{#if debt}}
-            <div class="issue-meta">
-              <span class="issue-meta-label">
-                {{tp 'issue.x_debt' debt}}
-              </span>
-            </div>
-          {{/if}}
-
-          {{#inArray actions "comment"}}
-            <div class="issue-meta">
-              <a class="issue-action js-issue-comment"><span
-                  class="issue-meta-label">{{t 'issue.comment.formlink' }}</span></a>
-            </div>
-          {{/inArray}}
-
-          {{#ifHasExtraActions actions}}
-            <div class="issue-meta">
-              <a class="issue-action issue-action-with-options js-issue-more">
-                <span class="issue-meta-label">{{t 'more'}}</span>&nbsp;<i class="icon-dropdown"></i>
-              </a>
-            </div>
-          {{/ifHasExtraActions}}
-        </div>
-      </td>
-
-      <td class="issue-table-meta-cell">
-        <div class="issue-meta js-issue-tags">
-          {{#inArray actions "set_tags"}}
-            <a class="issue-action issue-action-with-options js-issue-edit-tags">
-              <span>
-                <i class="icon-tags icon-half-transparent"></i>&nbsp;<span>{{#if tags}}{{join tags ', '}}{{else}}{{t 'issue.no_tag'}}{{/if}}</span>
-              </span>&nbsp;<i class="icon-dropdown"></i>
-            </a>
-          {{else}}
-            <span>
-              <i class="icon-tags icon-half-transparent"></i>&nbsp;<span>{{#if tags}}{{join tags ', '}}{{else}}{{t 'issue.no_tag'}}{{/if}}</span>
-            </span>
-          {{/inArray}}
-        </div>
-      </td>
-    </tr>
-  </table>
-
-  {{#notEmpty comments}}
-    <div class="issue-comments">
-      {{#each comments}}
-        <div class="issue-comment" data-comment-key="{{key}}">
-          <div class="issue-comment-author" title="{{userName}}">
-            {{#ifShowAvatars}}{{avatarHelper email 16}}{{else}}<i class="icon-comment icon-half-transparent"></i>{{/ifShowAvatars}}&nbsp;{{userName}}
-          </div>
-          <div class="issue-comment-text markdown">{{{show html htmlText}}}</div>
-          <div class="issue-comment-age">({{fromNow createdAt}})</div>
-          <div class="issue-comment-actions">
-            {{#if updatable}}
-              <a class="js-issue-comment-edit icon-edit icon-half-transparent"></a>
-              <a class="js-issue-comment-delete icon-delete icon-half-transparent"
-                 data-confirm-msg="{{t 'issue.comment.delete_confirm_message'}}"></a>
-            {{/if}}
-          </div>
-        </div>
-      {{/each}}
-    </div>
-  {{/notEmpty}}
-
-</div>
-
-<a class="issue-navigate js-issue-navigate">
-  <i class="issue-navigate-to-left icon-chevron-left"></i>
-  <i class="issue-navigate-to-right icon-chevron-right"></i>
-</a>
diff --git a/server/sonar-web/src/main/coffee/components/issue/templates/manual-issue.hbs b/server/sonar-web/src/main/coffee/components/issue/templates/manual-issue.hbs
deleted file mode 100644 (file)
index bbcf333..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<form action="" class="js-manual-issue-form code-issue-create-form">
-  {{! no manual rules }}
-  {{! <div class="warning" style="margin: 10px"> }}
-  {{!   <% if is_admin %> }}
-  {{!   <%= message('issue.manual.no_rules.admin') -%> }}
-  {{!   &nbsp;<a href="<%= ApplicationController.root_context -%>/manual_rules/index"><%= message('manage') -%></a> }}
-  {{!   <% else %> }}
-  {{!   <%= message('issue.manual.no_rules.non_admin') -%> }}
-  {{!   <% end %> }}
-  {{!   &nbsp;<%= link_to_function message('cancel'), 'closeCreateIssueForm(this)' -%> }}
-  {{! </div> }}
-
-  <input type="hidden" name="line" value="{{line}}">
-  <input type="hidden" name="component" value="{{component}}">
-
-  <div class="code-issue-name">
-    <select name="rule">
-      {{#each rules}}
-        <option value="{{key}}">{{name}}</option>
-      {{/each}}
-    </select>
-  </div>
-
-  <div class="code-issue-msg">
-    <table class="width100">
-      <tr>
-        <td>
-          <textarea rows="4" name="message" class="width100 marginbottom5"></textarea>
-        </td>
-      </tr>
-      <tr>
-        <td class="js-submit">
-          <input type="submit" value="{{t 'create'}}">
-          <a class="js-cancel" href="#">{{t 'cancel'}}</a>
-        </td>
-        <td class="js-spinner" style="display: none;">
-          <i class="spinner"></i>
-        </td>
-      </tr>
-    </table>
-    <div class="code-issue-errors alert alert-danger hidden"></div>
-  </div>
-
-</form>
diff --git a/server/sonar-web/src/main/coffee/components/issue/views/action-options-view.coffee b/server/sonar-web/src/main/coffee/components/issue/views/action-options-view.coffee
deleted file mode 100644 (file)
index 878d172..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  'components/common/popup'
-], (
-  PopupView
-) ->
-
-  $ = jQuery
-
-
-  class extends PopupView
-    keyScope: 'issue-action-options'
-
-
-    ui:
-      options: '.issue-action-option'
-
-
-    events: ->
-      'click .issue-action-option': 'selectOption'
-      'mouseenter .issue-action-option': 'activateOptionByPointer'
-
-
-    initialize: ->
-      @bindShortcuts()
-
-
-    onRender: ->
-      super
-      @selectInitialOption()
-      @$('[data-toggle="tooltip"]').tooltip container: 'body'
-
-
-    getOptions: ->
-      @$('.issue-action-option')
-
-
-    getActiveOption: ->
-      @getOptions().filter('.active')
-
-
-    makeActive: (option) ->
-      if option.length > 0
-        @getOptions().removeClass 'active'
-        option.addClass 'active'
-
-
-    selectInitialOption: ->
-      @makeActive @getOptions().first()
-
-
-    selectNextOption: ->
-      @makeActive @getActiveOption().nextAll('.issue-action-option').first()
-      false # return `false` to use with keymaster
-
-
-    selectPreviousOption: ->
-      @makeActive @getActiveOption().prevAll('.issue-action-option').first()
-      false # return `false` to use with keymaster
-
-
-    activateOptionByPointer: (e) ->
-      @makeActive $(e.currentTarget)
-
-
-    bindShortcuts: ->
-      @currentKeyScope = key.getScope()
-      key.setScope @keyScope
-      key 'down', @keyScope, => @selectNextOption()
-      key 'up', @keyScope, => @selectPreviousOption()
-      key 'return', @keyScope, => @selectActiveOption()
-      key 'escape', @keyScope, => @close()
-      key 'backspace', @keyScope, => false # disable go back through the history
-      key 'shift+tab', @keyScope, => false
-
-
-    unbindShortcuts: ->
-      key.unbind 'down', @keyScope
-      key.unbind 'up', @keyScope
-      key.unbind 'return', @keyScope
-      key.unbind 'escape', @keyScope
-      key.unbind 'backspace', @keyScope
-      key.unbind 'tab', @keyScope
-      key.unbind 'shift+tab', @keyScope
-      key.setScope @currentKeyScope
-
-
-    onClose: ->
-      super
-      @unbindShortcuts()
-      @$('[data-toggle="tooltip"]').tooltip 'destroy'
-      $('.tooltip').remove()
-
-
-    selectOption: (e) ->
-      e.preventDefault()
-      @close()
-
-
-    selectActiveOption: ->
-      @getActiveOption().click()
diff --git a/server/sonar-web/src/main/coffee/components/issue/views/assign-form-view.coffee b/server/sonar-web/src/main/coffee/components/issue/views/assign-form-view.coffee
deleted file mode 100644 (file)
index ae00ec8..0000000
+++ /dev/null
@@ -1,148 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  './action-options-view'
-  '../templates'
-], (
-  ActionOptionsView
-) ->
-
-  $ = jQuery
-
-
-  class extends ActionOptionsView
-    template: Templates['issue-assign-form']
-    optionTemplate: Templates['issue-assign-form-option']
-
-
-    events: ->
-      _.extend super,
-        'click input': 'onInputClick'
-        'keydown input': 'onInputKeydown'
-        'keyup input': 'onInputKeyup'
-
-
-    initialize: ->
-      super
-      @assignees = []
-      @debouncedSearch = _.debounce @search, 250
-
-
-    getAssignee: ->
-      @model.get 'assignee'
-
-
-    getAssigneeName: ->
-      @model.get 'assigneeName'
-
-
-    onRender: ->
-      super
-      @renderTags()
-      setTimeout (=> @$('input').focus()), 100
-
-
-    renderTags: ->
-      @$('.issue-action-option').remove()
-      @getAssignees().forEach @renderAssignee, @
-      @selectInitialOption()
-
-
-    renderAssignee: (assignee) ->
-      html = @optionTemplate assignee
-      @$('.issue-action-options').append html
-
-
-    selectOption: (e) ->
-      assignee = $(e.currentTarget).data 'value'
-      assigneeName = $(e.currentTarget).data 'text'
-      @submit assignee, assigneeName
-      super
-
-
-    submit: (assignee, assigneeName) ->
-      _assignee = @getAssignee()
-      _assigneeName = @getAssigneeName()
-      return if assignee == _assignee
-      if assignee == ''
-        @model.set assignee: null, assigneeName: null
-      else
-        @model.set assignee: assignee, assigneeName: assigneeName
-      $.ajax
-        type: 'POST'
-        url: "#{baseUrl}/api/issues/assign"
-        data:
-          issue: @model.id
-          assignee: assignee
-      .fail =>
-        @model.set assignee: _assignee, assigneeName: _assigneeName
-
-
-    onInputClick: (e) ->
-      e.stopPropagation()
-
-
-    onInputKeydown: (e) ->
-      @query = @$('input').val()
-      return @selectPreviousOption() if e.keyCode == 38 # up
-      return @selectNextOption() if e.keyCode == 40 # down
-      return @selectActiveOption() if e.keyCode == 13 # return
-      return false if e.keyCode == 9 # tab
-      @close() if e.keyCode == 27 # escape
-
-
-    onInputKeyup: ->
-      query = @$('input').val()
-      if query != @query
-        query = '' if query.length < 2
-        @query = query
-        @debouncedSearch query
-
-
-    search: (query) ->
-      if query.length > 1
-        $.get "#{baseUrl}/api/users/search", q: query
-        .done (data) =>
-          @resetAssignees data.users
-      else
-        @resetAssignees []
-
-
-    resetAssignees: (users) ->
-      @assignees = users.map (user) ->
-        id: user.login
-        text: user.name
-      @renderTags()
-
-
-    getAssignees: ->
-      return @assignees if @assignees.length > 0
-      assignees = [{ id: '', text: t('unassigned') }]
-      currentUser = window.SS.user
-      currentUserName = window.SS.userName
-      assignees.push id: currentUser, text: currentUserName
-      if @getAssignee()
-        assignees.push id: @getAssignee(), text: @getAssigneeName()
-      @makeUnique assignees
-
-
-    makeUnique: (assignees) ->
-      _.uniq assignees, false, (assignee) -> assignee.id
diff --git a/server/sonar-web/src/main/coffee/components/issue/views/changelog-view.coffee b/server/sonar-web/src/main/coffee/components/issue/views/changelog-view.coffee
deleted file mode 100644 (file)
index 34e77c9..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  'components/common/popup'
-  '../templates'
-], (
-  PopupView
-) ->
-
-  class extends PopupView
-    template: Templates['issue-changelog']
-
-
-    collectionEvents:
-      'sync': 'render'
-
-
-    serializeData: ->
-      _.extend super,
-        issue: @options.issue.toJSON()
diff --git a/server/sonar-web/src/main/coffee/components/issue/views/comment-form-view.coffee b/server/sonar-web/src/main/coffee/components/issue/views/comment-form-view.coffee
deleted file mode 100644 (file)
index 95fa673..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  'components/common/popup'
-  '../templates'
-], (
-  PopupView
-) ->
-
-  $ = jQuery
-  
-
-  class extends PopupView
-    className: 'bubble-popup issue-comment-bubble-popup'
-    template: Templates['comment-form']
-
-
-    ui:
-      textarea: '.issue-comment-form-text textarea'
-      cancelButton: '.js-issue-comment-cancel'
-      submitButton: '.js-issue-comment-submit'
-
-
-    events:
-      'click': 'onClick'
-      'keydown @ui.textarea': 'onKeydown'
-      'keyup @ui.textarea': 'toggleSubmit'
-      'click @ui.cancelButton': 'cancel'
-      'click @ui.submitButton': 'submit'
-
-
-    onRender: ->
-      super
-      setTimeout (=> @ui.textarea.focus()), 100
-
-
-    toggleSubmit: ->
-      @ui.submitButton.prop 'disabled', @ui.textarea.val().length == 0
-
-
-    onClick: (e) ->
-      # disable close by clicking inside
-      e.stopPropagation()
-
-
-    onKeydown: (e) ->
-      @close() if e.keyCode == 27 # escape
-
-
-    cancel: ->
-      @options.detailView.updateAfterAction false
-
-
-    submit: ->
-      text = @ui.textarea.val()
-      update = @model && @model.has('key')
-      method = if update then 'edit_comment' else 'add_comment'
-      url = "#{baseUrl}/api/issues/#{method}"
-      data = text: text
-      if update
-        data.key = @model.get('key')
-      else
-        data.issue = @options.issue.id
-      $.post url, data
-      .done =>
-        @options.detailView.updateAfterAction true
diff --git a/server/sonar-web/src/main/coffee/components/issue/views/issue-popup.coffee b/server/sonar-web/src/main/coffee/components/issue/views/issue-popup.coffee
deleted file mode 100644 (file)
index fa54c06..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  'components/common/popup'
-], (
-  Popup
-) ->
-
-  class extends Popup
-    className: 'bubble-popup issue-bubble-popup'
-
-
-    template: -> '<div class="bubble-popup-arrow"></div>'
-
-
-    events: ->
-      'click .js-issue-form-cancel': 'close'
-
-
-    onRender: ->
-      super
-      @options.view.$el.appendTo @$el
-      @options.view.render()
-
-
-    onClose: ->
-      @options.view.close()
-
-
-    attachCloseEvents: ->
diff --git a/server/sonar-web/src/main/coffee/components/issue/views/more-actions-view.coffee b/server/sonar-web/src/main/coffee/components/issue/views/more-actions-view.coffee
deleted file mode 100644 (file)
index 868dbb2..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  'components/common/popup'
-  '../templates'
-], (
-  PopupView
-) ->
-
-  $ = jQuery
-
-
-  class extends PopupView
-    template: Templates['issue-more-actions']
-
-
-    events: ->
-      'click .js-issue-action': 'action'
-
-
-    action: (e) ->
-      actionKey = $(e.currentTarget).data 'action'
-      @options.detailView.action actionKey
diff --git a/server/sonar-web/src/main/coffee/components/issue/views/plan-form-view.coffee b/server/sonar-web/src/main/coffee/components/issue/views/plan-form-view.coffee
deleted file mode 100644 (file)
index bface05..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  './action-options-view'
-  '../templates'
-], (
-  ActionOptionsView
-) ->
-
-  $ = jQuery
-
-
-  class extends ActionOptionsView
-    template: Templates['issue-plan-form']
-
-
-    getActionPlan: ->
-      @model.get('actionPlan') || ''
-
-
-    getActionPlanName: ->
-      @model.get 'actionPlanName'
-
-
-    selectInitialOption: ->
-      @makeActive @getOptions().filter("[data-value=#{@getActionPlan()}]")
-
-
-    selectOption: (e) ->
-      actionPlan = $(e.currentTarget).data 'value'
-      actionPlanName = $(e.currentTarget).data 'text'
-      @submit actionPlan, actionPlanName
-      super
-
-
-    submit: (actionPlan, actionPlanName) ->
-      _actionPlan = @getActionPlan()
-      _actionPlanName = @getActionPlanName()
-      return if actionPlan == _actionPlan
-      if actionPlan == ''
-        @model.set actionPlan: null, actionPlanName: null
-      else
-        @model.set actionPlan: actionPlan, actionPlanName: actionPlanName
-      $.ajax
-        type: 'POST'
-        url: "#{baseUrl}/api/issues/plan"
-        data:
-          issue: @model.id
-          plan: actionPlan
-      .fail =>
-        @model.set assignee: _actionPlan, assigneeName: _actionPlanName
-
-
-    getActionPlans: ->
-      [{ key: '', name: t 'issue.unplanned' }].concat @collection.toJSON()
-
-
-    serializeData: ->
-      _.extend super,
-        items: @getActionPlans()
diff --git a/server/sonar-web/src/main/coffee/components/issue/views/set-severity-form-view.coffee b/server/sonar-web/src/main/coffee/components/issue/views/set-severity-form-view.coffee
deleted file mode 100644 (file)
index bdd813f..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  './action-options-view'
-  '../templates'
-], (
-  ActionOptionsView
-) ->
-
-  $ = jQuery
-  
-
-  class extends ActionOptionsView
-    template: Templates['issue-set-severity-form']
-
-
-    getTransition: ->
-      @model.get 'severity'
-
-
-    selectInitialOption: ->
-      @makeActive @getOptions().filter("[data-value=#{@getTransition()}]")
-
-
-    selectOption: (e) ->
-      severity = $(e.currentTarget).data 'value'
-      @submit severity
-      super
-
-
-    submit: (severity) ->
-      _severity = @getTransition()
-      return if severity == _severity
-      @model.set severity: severity
-      $.ajax
-        type: 'POST'
-        url: "#{baseUrl}/api/issues/set_severity"
-        data:
-          issue: @model.id
-          severity: severity
-      .fail =>
-        @model.set severity: _severity
-
-
-    serializeData: ->
-      _.extend super,
-        items: ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO']
diff --git a/server/sonar-web/src/main/coffee/components/issue/views/tags-form-view.coffee b/server/sonar-web/src/main/coffee/components/issue/views/tags-form-view.coffee
deleted file mode 100644 (file)
index ea08c2c..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  './action-options-view'
-  '../templates'
-], (
-  ActionOptionsView
-) ->
-
-  $ = jQuery
-
-
-  class extends ActionOptionsView
-    template: Templates['issue-tags-form']
-    optionTemplate: Templates['issue-tags-form-option']
-
-
-    modelEvents:
-      'change:tags': 'renderTags'
-
-
-    events: ->
-      _.extend super,
-        'click input': 'onInputClick'
-        'keydown input': 'onInputKeydown'
-        'keyup input': 'onInputKeyup'
-
-
-    initialize: ->
-      super
-      @query = ''
-      @tags = []
-      @selected = 0
-      @debouncedSearch = _.debounce @search, 250
-      @requestTags()
-
-
-    requestTags: ->
-      $.get "#{baseUrl}/api/issues/tags", ps: 25
-      .done (data) =>
-        @tags = data.tags
-        @renderTags()
-
-
-    onRender: ->
-      super
-      @renderTags()
-      setTimeout (=> @$('input').focus()), 100
-
-
-    selectInitialOption: ->
-      @selected = Math.max Math.min(@selected, @getOptions().length - 1), 0
-      @makeActive @getOptions().eq @selected
-
-
-    filterTags: (tags) ->
-      _.filter tags, (tag) => tag.indexOf(@query) != -1
-
-
-    renderTags: ->
-      @$('.issue-action-option').remove()
-      @filterTags(@getTags()).forEach @renderSelectedTag, @
-      @filterTags(_.difference(@tags, @getTags())).forEach @renderTag, @
-      if @query.length > 0 && @tags.indexOf(@query) == -1 && @getTags().indexOf(@query) == -1
-        @renderCustomTag @query
-      @selectInitialOption()
-
-
-    renderSelectedTag: (tag) ->
-      html = @optionTemplate { tag: tag, selected: true, custom: false }
-      @$('.issue-action-options').append html
-
-
-    renderTag: (tag) ->
-      html = @optionTemplate { tag: tag, selected: false, custom: false }
-      @$('.issue-action-options').append html
-
-
-    renderCustomTag: (tag) ->
-      html = @optionTemplate { tag: tag, selected: false, custom: true }
-      @$('.issue-action-options').append html
-
-
-    selectOption: (e) ->
-      e.preventDefault()
-      e.stopPropagation()
-      tags = @getTags().slice()
-      tag = $(e.currentTarget).data 'value'
-      if $(e.currentTarget).data('selected')?
-        tags = _.without tags, tag
-      else
-        tags.push tag
-      @selected = @getOptions().index $(e.currentTarget)
-      @submit tags
-
-
-    submit: (tags) ->
-      _tags = @getTags()
-      @model.set tags: tags
-      $.ajax
-        type: 'POST'
-        url: "#{baseUrl}/api/issues/set_tags"
-        data:
-          key: @model.id
-          tags: tags.join()
-      .fail =>
-        @model.set tags: _tags
-
-
-    onInputClick: (e) ->
-      e.stopPropagation()
-
-
-    onInputKeydown: (e) ->
-      @query = @$('input').val()
-      return @selectPreviousOption() if e.keyCode == 38 # up
-      return @selectNextOption() if e.keyCode == 40 # down
-      return @selectActiveOption() if e.keyCode == 13 # return
-      return false if e.keyCode == 9 # tab
-      @close() if e.keyCode == 27 # escape
-
-
-    onInputKeyup: ->
-      query = @$('input').val()
-      if query != @query
-        @query = query
-        @debouncedSearch query
-
-
-    search: (query) ->
-      @query = query
-      @renderTags()
-
-
-    resetAssignees: (users) ->
-      @assignees = users.map (user) ->
-        id: user.login
-        text: user.name
-      @renderTags()
-
-
-    getTags: ->
-      @model.get('tags') || []
diff --git a/server/sonar-web/src/main/coffee/components/issue/views/transitions-form-view.coffee b/server/sonar-web/src/main/coffee/components/issue/views/transitions-form-view.coffee
deleted file mode 100644 (file)
index cf4d47d..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-define [
-  './action-options-view'
-  '../templates'
-], (
-  ActionOptionsView
-) ->
-
-  $ = jQuery
-  
-
-  class extends ActionOptionsView
-    template: Templates['issue-transitions-form']
-
-
-    selectInitialOption: ->
-      @makeActive @getOptions().first()
-
-
-    selectOption: (e) ->
-      transition = $(e.currentTarget).data 'value'
-      @submit transition
-      super
-
-
-    submit: (transition) ->
-      $.ajax
-        type: 'POST',
-        url: baseUrl + '/api/issues/do_transition',
-        data:
-          issue: @model.get('key')
-          transition: transition
-      .done =>
-        @options.view.resetIssue {}
diff --git a/server/sonar-web/src/main/js/components/issue/collections/action-plans.js b/server/sonar-web/src/main/js/components/issue/collections/action-plans.js
new file mode 100644 (file)
index 0000000..8cbef0a
--- /dev/null
@@ -0,0 +1,13 @@
+define([], function () {
+
+  return Backbone.Collection.extend({
+    url: function () {
+      return baseUrl + '/api/action_plans/search';
+    },
+
+    parse: function (r) {
+      return r.actionPlans;
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/components/issue/collections/issues.js b/server/sonar-web/src/main/js/components/issue/collections/issues.js
new file mode 100644 (file)
index 0000000..5925fc0
--- /dev/null
@@ -0,0 +1,54 @@
+define([
+  '../models/issue'
+], function (Issue) {
+
+  return Backbone.Collection.extend({
+    model: Issue,
+
+    url: function () {
+      return baseUrl + '/api/issues/search';
+    },
+
+    parse: function (r) {
+      function find (source, key, keyField) {
+        var searchDict = {};
+        searchDict[keyField || 'key'] = key;
+        return _.findWhere(source, searchDict) || key;
+      }
+
+      this.paging = {
+        p: r.p,
+        ps: r.ps,
+        total: r.total,
+        maxResultsReached: r.p * r.ps >= r.total
+      };
+
+      return r.issues.map(function (issue) {
+        var component = find(r.components, issue.component),
+            project = find(r.projects, issue.project),
+            rule = find(r.rules, issue.rule),
+            assignee = find(r.users, issue.assignee, 'login');
+        if (component) {
+          _.extend(issue, {
+            componentLongName: component.longName,
+            componentQualifier: component.qualifier
+          });
+        }
+        if (project) {
+          _.extend(issue, {
+            projectLongName: project.longName,
+            projectUuid: project.uuid
+          });
+        }
+        if (rule) {
+          _.extend(issue, { ruleName: rule.name });
+        }
+        if (assignee) {
+          _.extend(issue, { assigneeEmail: assignee.email });
+        }
+        return issue;
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/components/issue/issue-view.js b/server/sonar-web/src/main/js/components/issue/issue-view.js
new file mode 100644 (file)
index 0000000..98d095d
--- /dev/null
@@ -0,0 +1,332 @@
+define([
+  './models/changelog',
+  './views/changelog-view',
+  './collections/action-plans',
+  './views/issue-popup',
+  './views/transitions-form-view',
+  './views/assign-form-view',
+  './views/comment-form-view',
+  './views/plan-form-view',
+  './views/set-severity-form-view',
+  './views/more-actions-view',
+  './views/tags-form-view',
+  'components/workspace/main',
+  './templates'
+], function (ChangeLog, ChangeLogView, ActionPlans, IssuePopup, TransitionsFormView, AssignFormView, CommentFormView,
+             PlanFormView, SetSeverityFormView, MoreActionsView, TagsFormView, Workspace) {
+
+  var $ = jQuery;
+
+  return Marionette.ItemView.extend({
+    className: 'issue',
+    template: Templates.issue,
+
+    modelEvents: {
+      'change': 'render'
+    },
+
+    ui: {
+      tagsChange: '.js-issue-edit-tags',
+      tagInput: '.issue-tag-input',
+      tagsEdit: '.issue-tag-edit',
+      tagsEditDone: '.issue-tag-edit-done',
+      tagsEditCancel: '.issue-tag-edit-cancel',
+      tagsList: '.issue-tag-list'
+    },
+
+    events: function () {
+      return {
+        'click .js-issue-comment': 'comment',
+        'click .js-issue-comment-edit': 'editComment',
+        'click .js-issue-comment-delete': 'deleteComment',
+        'click .js-issue-transition': 'transition',
+        'click .js-issue-set-severity': 'setSeverity',
+        'click .js-issue-assign': 'assign',
+        'click .js-issue-assign-to-me': 'assignToMe',
+        'click .js-issue-plan': 'plan',
+        'click .js-issue-show-changelog': 'showChangeLog',
+        'click .js-issue-more': 'showMoreActions',
+        'click .js-issue-rule': 'showRule',
+        'click .js-issue-edit-tags': 'editTags'
+      };
+    },
+
+    onRender: function () {
+      this.ui.tagsEdit.hide();
+      this.$el.attr('data-key', this.model.get('key'));
+    },
+
+    resetIssue: function (options) {
+      var that = this;
+      var key = this.model.get('key'),
+          componentUuid = this.model.get('componentUuid');
+      this.model.clear({ silent: true });
+      this.model.set({
+        key: key,
+        componentUuid: componentUuid
+      }, { silent: true });
+      return this.model.fetch(options).done(function () {
+        return that.trigger('reset');
+      });
+    },
+
+    showChangeLog: function (e) {
+      var that = this;
+      var t = $(e.currentTarget),
+          changeLog = new ChangeLog();
+      return changeLog.fetch({
+        data: { issue: this.model.get('key') }
+      }).done(function () {
+        if (that.popup) {
+          that.popup.close();
+        }
+        that.popup = new ChangeLogView({
+          triggerEl: t,
+          bottomRight: true,
+          collection: changeLog,
+          issue: that.model
+        });
+        that.popup.render();
+      });
+    },
+
+    updateAfterAction: function (fetch) {
+      if (this.popup) {
+        this.popup.close();
+      }
+      if (fetch) {
+        this.resetIssue();
+      }
+    },
+
+    comment: function (e) {
+      e.stopPropagation();
+      $('body').click();
+      this.popup = new CommentFormView({
+        triggerEl: $(e.currentTarget),
+        bottom: true,
+        issue: this.model,
+        detailView: this
+      });
+      this.popup.render();
+    },
+
+    editComment: function (e) {
+      e.stopPropagation();
+      $('body').click();
+      var commentEl = $(e.currentTarget).closest('.issue-comment'),
+          commentKey = commentEl.data('comment-key'),
+          comment = _.findWhere(this.model.get('comments'), { key: commentKey });
+      this.popup = new CommentFormView({
+        triggerEl: $(e.currentTarget),
+        bottomRight: true,
+        model: new Backbone.Model(comment),
+        issue: this.model,
+        detailView: this
+      });
+      this.popup.render();
+    },
+
+    deleteComment: function (e) {
+      var that = this;
+      var commentKey = $(e.target).closest('[data-comment-key]').data('comment-key'),
+          confirmMsg = $(e.target).data('confirm-msg');
+      if (confirm(confirmMsg)) {
+        return $.ajax({
+          type: 'POST',
+          url: baseUrl + '/api/issues/delete_comment?key=' + commentKey
+        }).done(function () {
+          that.updateAfterAction(true);
+        });
+      }
+    },
+
+    transition: function (e) {
+      e.stopPropagation();
+      $('body').click();
+      this.popup = new TransitionsFormView({
+        triggerEl: $(e.currentTarget),
+        bottom: true,
+        model: this.model,
+        view: this
+      });
+      this.popup.render();
+    },
+
+    setSeverity: function (e) {
+      e.stopPropagation();
+      $('body').click();
+      this.popup = new SetSeverityFormView({
+        triggerEl: $(e.currentTarget),
+        bottom: true,
+        model: this.model
+      });
+      this.popup.render();
+    },
+
+    assign: function (e) {
+      e.stopPropagation();
+      $('body').click();
+      this.popup = new AssignFormView({
+        triggerEl: $(e.currentTarget),
+        bottom: true,
+        model: this.model
+      });
+      this.popup.render();
+    },
+
+    assignToMe: function () {
+      var view = new AssignFormView({
+        model: this.model,
+        triggerEl: $('body')
+      });
+      view.submit(window.SS.user, window.SS.userName);
+      view.close();
+    },
+
+    plan: function (e) {
+      var that = this;
+      var t = $(e.currentTarget),
+          actionPlans = new ActionPlans();
+      return actionPlans.fetch({
+        reset: true,
+        data: { project: this.model.get('project') }
+      }).done(function () {
+        e.stopPropagation();
+        $('body').click();
+        that.popup = new PlanFormView({
+          triggerEl: t,
+          bottom: true,
+          model: that.model,
+          collection: actionPlans
+        });
+        that.popup.render();
+      });
+    },
+
+    showMoreActions: function (e) {
+      e.stopPropagation();
+      $('body').click();
+      this.popup = new MoreActionsView({
+        triggerEl: $(e.currentTarget),
+        bottomRight: true,
+        model: this.model,
+        detailView: this
+      });
+      this.popup.render();
+    },
+
+    action: function (action) {
+      var that = this;
+      return $.post(baseUrl + '/api/issues/do_action', {
+        issue: this.model.id,
+        actionKey: action
+      }).done(function () {
+        that.resetIssue();
+      });
+    },
+
+    showRule: function () {
+      if (Workspace == null) {
+        Workspace = require('components/workspace/main');
+      }
+      var ruleKey = this.model.get('rule');
+      Workspace.openRule({ key: ruleKey });
+    },
+
+    editTags: function (e) {
+      e.stopPropagation();
+      $('body').click();
+      this.popup = new TagsFormView({
+        triggerEl: $(e.currentTarget),
+        bottomRight: true,
+        model: this.model
+      });
+      this.popup.render();
+    },
+
+    changeTags: function () {
+      var that = this;
+      return jQuery.ajax({
+        url: baseUrl + '/api/issues/tags?ps=0'
+      }).done(function (r) {
+        if (that.ui.tagInput.select2) {
+          that.ui.tagInput.select2({
+            tags: _.difference(r.tags, that.model.get('tags')),
+            width: '300px'
+          });
+        }
+        if (that.ui.tagsEdit.show) {
+          that.ui.tagsEdit.show();
+        }
+        if (that.ui.tagsList.hide) {
+          that.ui.tagsList.hide();
+        }
+        that.tagsBuffer = that.ui.tagInput.select2('val');
+        var keyScope = key.getScope();
+        if (keyScope !== 'tags') {
+          that.previousKeyScope = keyScope;
+        }
+        key.setScope('tags');
+        key('escape', 'tags', function () {
+          return that.cancelEdit();
+        });
+        that.$('.select2-input').keyup(function (event) {
+          if (event.which === 27) {
+            return that.cancelEdit();
+          }
+        });
+        that.ui.tagInput.select2('focus');
+      });
+    },
+
+    cancelEdit: function () {
+      this.resetKeyScope();
+      if (this.ui.tagsList.show) {
+        this.ui.tagsList.show();
+      }
+      if (this.ui.tagInput.select2) {
+        this.ui.tagInput.select2('val', this.tagsBuffer);
+        this.ui.tagInput.select2('close');
+      }
+      if (this.ui.tagsEdit.hide) {
+        return this.ui.tagsEdit.hide();
+      }
+    },
+
+    editDone: function () {
+      var that = this;
+      this.resetKeyScope();
+      var _tags = this.model.get('tags'),
+          tags = this.ui.tagInput.val(),
+          splitTags = tags ? tags.split(',') : null;
+      this.model.set('tags', splitTags);
+      return $.post(baseUrl + '/api/issues/set_tags', {
+        key: this.model.get('key'),
+        tags: tags
+      }).done(function () {
+        that.cancelEdit();
+      }).fail(function () {
+        that.model.set('tags', _tags);
+      }).always(function () {
+        that.render();
+      });
+    },
+
+    resetKeyScope: function () {
+      key.unbind('escape', 'tags');
+      if (this.previousKeyScope) {
+        key.setScope(this.previousKeyScope);
+        this.previousKeyScope = null;
+      }
+    },
+
+    serializeData: function () {
+      var issueKey = encodeURIComponent(this.model.get('key'));
+      return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
+        permalink: baseUrl + '/issues/search#issues=' + issueKey
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/components/issue/manual-issue-view.js b/server/sonar-web/src/main/js/components/issue/manual-issue-view.js
new file mode 100644 (file)
index 0000000..c1f60b4
--- /dev/null
@@ -0,0 +1,114 @@
+define([
+  './templates'
+], function () {
+
+  var $ = jQuery,
+      API_ISSUE = baseUrl + '/api/issues/show',
+      API_ADD_MANUAL_ISSUE = baseUrl + '/api/issues/create';
+
+  return Marionette.ItemView.extend({
+    template: Templates['manual-issue'],
+
+    events: {
+      'submit .js-manual-issue-form': 'formSubmit',
+      'click .js-cancel': 'cancel'
+    },
+
+    initialize: function () {
+      var that = this;
+      this.rules = [];
+      $.get(baseUrl + '/api/rules/search?repositories=manual&f=name&ps=9999999').done(function (r) {
+        that.rules = r.rules;
+        that.render();
+      });
+    },
+
+    onRender: function () {
+      this.delegateEvents();
+      this.$('[name=rule]').select2({
+        width: '250px',
+        minimumResultsForSearch: 10
+      });
+      if (this.rules.length > 0) {
+        this.$('[name=rule]').select2('open');
+      }
+      if (key != null) {
+        this.key = key.getScope();
+        key.setScope('');
+      }
+    },
+
+    onClose: function () {
+      if (key != null && this.key != null) {
+        key.setScope(this.key);
+      }
+    },
+
+    showSpinner: function () {
+      this.$('.js-submit').hide();
+      this.$('.js-spinner').show();
+    },
+
+    hideSpinner: function () {
+      this.$('.js-submit').show();
+      this.$('.js-spinner').hide();
+    },
+
+    validateFields: function () {
+      var message = this.$('[name=message]');
+      if (!message.val()) {
+        message.addClass('invalid').focus();
+        return false;
+      }
+      return true;
+    },
+
+    formSubmit: function (e) {
+      var that = this;
+      e.preventDefault();
+      if (!this.validateFields()) {
+        return;
+      }
+      this.showSpinner();
+      var data = $(e.currentTarget).serialize();
+      $.post(API_ADD_MANUAL_ISSUE, data)
+          .done(function (r) {
+            if (typeof r === 'string') {
+              r = JSON.parse(r);
+            }
+            that.addIssue(r.issue.key);
+          }).fail(function (r) {
+            that.hideSpinner();
+            if (r.responseJSON && r.responseJSON.errors) {
+              that.showError(_.pluck(r.responseJSON.errors, 'msg').join('. '));
+            }
+          });
+    },
+
+    addIssue: function (key) {
+      var that = this;
+      return $.get(API_ISSUE, { key: key }).done(function (r) {
+        that.trigger('add', r.issue);
+        that.close();
+      });
+    },
+
+    showError: function (msg) {
+      this.$('.code-issue-errors').removeClass('hidden').text(msg);
+    },
+
+    cancel: function (e) {
+      e.preventDefault();
+      this.close();
+    },
+
+    serializeData: function () {
+      return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
+        line: this.options.line,
+        component: this.options.component,
+        rules: _.sortBy(this.rules, 'name')
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/components/issue/models/changelog.js b/server/sonar-web/src/main/js/components/issue/models/changelog.js
new file mode 100644 (file)
index 0000000..a7efe2b
--- /dev/null
@@ -0,0 +1,13 @@
+define([], function () {
+
+  return Backbone.Collection.extend({
+    url: function () {
+      return baseUrl + '/api/issues/changelog';
+    },
+
+    parse: function (r) {
+      return r.changelog;
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/components/issue/models/issue.js b/server/sonar-web/src/main/js/components/issue/models/issue.js
new file mode 100644 (file)
index 0000000..1c5e928
--- /dev/null
@@ -0,0 +1,15 @@
+define([], function () {
+
+  return Backbone.Model.extend({
+    idAttribute: 'key',
+
+    url: function () {
+      return baseUrl + '/api/issues/show?key=' + this.get('key');
+    },
+
+    parse: function (r) {
+      return r.issue ? r.issue : r;
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/components/issue/templates/comment-form.hbs b/server/sonar-web/src/main/js/components/issue/templates/comment-form.hbs
new file mode 100644 (file)
index 0000000..9bd5139
--- /dev/null
@@ -0,0 +1,18 @@
+<div class="issue-comment-form-text">
+  <textarea rows="2">{{show raw markdown}}</textarea>
+</div>
+
+<div class="issue-comment-form-footer">
+  <div class="issue-comment-form-actions">
+    <div class="button-group">
+      <button class="js-issue-comment-submit" disabled>
+        {{#if id}}{{t 'save'}}{{else}}{{t 'issue.comment.submit'}}{{/if}}
+      </button>
+    </div>
+    <a class="js-issue-comment-cancel">{{t 'cancel'}}</a>
+  </div>
+
+  <div class="issue-comment-form-tips">{{> '_markdown-tips' }}</div>
+</div>
+
+<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/js/components/issue/templates/issue-assign-form-option.hbs b/server/sonar-web/src/main/js/components/issue/templates/issue-assign-form-option.hbs
new file mode 100644 (file)
index 0000000..e35905f
--- /dev/null
@@ -0,0 +1,3 @@
+<a href="#" class="issue-action-option js-issue-assignee" data-value="{{id}}" data-text="{{text}}">
+  {{text}}
+</a>
diff --git a/server/sonar-web/src/main/js/components/issue/templates/issue-assign-form.hbs b/server/sonar-web/src/main/js/components/issue/templates/issue-assign-form.hbs
new file mode 100644 (file)
index 0000000..9d20be8
--- /dev/null
@@ -0,0 +1,6 @@
+<div class="issue-action-options">
+  <i class="icon-search issue-action-options-search-icon"></i>
+  <input class="issue-action-options-search" type="text" placeholder="Search..." value="{{query}}">
+</div>
+
+<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/js/components/issue/templates/issue-changelog.hbs b/server/sonar-web/src/main/js/components/issue/templates/issue-changelog.hbs
new file mode 100644 (file)
index 0000000..fd6f33d
--- /dev/null
@@ -0,0 +1,39 @@
+<div class="issue-changelog">
+  <table class="spaced">
+    <tbody>
+
+    <tr>
+      <td class="thin text-left text-top" nowrap>{{dt issue.creationDate}}</td>
+      <td class="thin text-left text-top" nowrap></td>
+      <td class="text-left text-top">
+        {{#if issue.reporter}}
+          {{t 'issue.reported_by'}} {{default issue.reporterName issue.reporter}}
+        {{else}}
+          {{#if issue.author}}
+            {{t 'created_by'}} {{issue.author}}
+          {{else}}
+            {{t 'created'}}
+          {{/if}}
+        {{/if}}
+      </td>
+    </tr>
+
+    {{#each items}}
+      <tr>
+        <td class="thin text-left text-top" nowrap>{{dt creationDate}}</td>
+        <td class="thin text-left text-top" nowrap>
+          {{#ifShowAvatars}}{{avatarHelper email 16}}{{/ifShowAvatars}}
+          {{userName}}
+        </td>
+        <td class="text-left text-top">
+          {{#each diffs}}
+            {{changelog this}}<br>
+          {{/each}}
+        </td>
+      </tr>
+    {{/each}}
+    </tbody>
+  </table>
+</div>
+
+<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/js/components/issue/templates/issue-more-actions.hbs b/server/sonar-web/src/main/js/components/issue/templates/issue-more-actions.hbs
new file mode 100644 (file)
index 0000000..18917b5
--- /dev/null
@@ -0,0 +1,9 @@
+<ul class="issue-more-actions">
+  {{#pluginActions actions}}
+    <li>
+      <a class="issue-action js-issue-action" data-action="{{this}}">{{t "issue.action" this "formlink"}}</a>
+    </li>
+  {{/pluginActions}}
+</ul>
+
+<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/js/components/issue/templates/issue-plan-form.hbs b/server/sonar-web/src/main/js/components/issue/templates/issue-plan-form.hbs
new file mode 100644 (file)
index 0000000..d4e693a
--- /dev/null
@@ -0,0 +1,11 @@
+<div class="issue-action-options">
+  {{#each items}}
+    {{#notEq status 'CLOSED'}}
+      <a href="#" class="issue-action-option js-issue-assignee" data-value="{{key}}" data-text="{{name}}">
+        {{name}}
+      </a>
+    {{/notEq}}
+  {{/each}}
+</div>
+
+<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/js/components/issue/templates/issue-set-severity-form.hbs b/server/sonar-web/src/main/js/components/issue/templates/issue-set-severity-form.hbs
new file mode 100644 (file)
index 0000000..7007fdd
--- /dev/null
@@ -0,0 +1,9 @@
+<div class="issue-action-options">
+  {{#each items}}
+    <a href="#" class="issue-action-option js-issue-severity" data-value="{{this}}">
+      {{severityIcon this}} {{t 'severity' this}}
+    </a>
+  {{/each}}
+</div>
+
+<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/js/components/issue/templates/issue-tags-form-option.hbs b/server/sonar-web/src/main/js/components/issue/templates/issue-tags-form-option.hbs
new file mode 100644 (file)
index 0000000..df054fe
--- /dev/null
@@ -0,0 +1,15 @@
+<a href="#" class="issue-action-option" data-value="{{tag}}" data-text="{{tag}}"
+   {{#if selected}}data-selected{{/if}}>
+
+  {{#if selected}}
+    <i class="icon-checkbox icon-checkbox-checked"></i>
+  {{else}}
+    <i class="icon-checkbox"></i>
+  {{/if}}
+
+  {{#if custom}}
+    + {{tag}}
+  {{else}}
+    {{tag}}
+  {{/if}}
+</a>
diff --git a/server/sonar-web/src/main/js/components/issue/templates/issue-tags-form.hbs b/server/sonar-web/src/main/js/components/issue/templates/issue-tags-form.hbs
new file mode 100644 (file)
index 0000000..9d20be8
--- /dev/null
@@ -0,0 +1,6 @@
+<div class="issue-action-options">
+  <i class="icon-search issue-action-options-search-icon"></i>
+  <input class="issue-action-options-search" type="text" placeholder="Search..." value="{{query}}">
+</div>
+
+<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/js/components/issue/templates/issue-transitions-form.hbs b/server/sonar-web/src/main/js/components/issue/templates/issue-transitions-form.hbs
new file mode 100644 (file)
index 0000000..2fa1f4c
--- /dev/null
@@ -0,0 +1,10 @@
+<div class="issue-action-options">
+  {{#each transitions}}
+    <a href="#" class="issue-action-option js-issue-transition" data-value="{{this}}"
+        title="{{t 'issue.transition' this 'description'}}" data-placement="right" data-toggle="tooltip">
+      {{t 'issue.transition' this}}
+    </a>
+  {{/each}}
+</div>
+
+<div class="bubble-popup-arrow"></div>
diff --git a/server/sonar-web/src/main/js/components/issue/templates/issue.hbs b/server/sonar-web/src/main/js/components/issue/templates/issue.hbs
new file mode 100644 (file)
index 0000000..f5a2d5e
--- /dev/null
@@ -0,0 +1,162 @@
+<div class="issue-inner">
+
+  <table class="issue-table">
+    <tr>
+      <td>
+        <div class="issue-message">{{message}} <a class="js-issue-rule issue-rule icon-ellipsis-h"></a></div>
+      </td>
+
+      <td class="issue-table-meta-cell issue-table-meta-cell-first">
+        <div class="issue-meta-list">
+          <div class="issue-meta">
+            <a class="issue-action issue-action-with-options js-issue-show-changelog" title="{{dt creationDate}}">
+              <span class="issue-meta-label">{{fromNow creationDate}}</span>&nbsp;<i class="icon-dropdown"></i>
+            </a>
+          </div>
+
+          {{#if line}}
+            <div class="issue-meta">
+              <span class="issue-meta-label" title="{{t 'line_number'}}">L{{line}}</span>
+            </div>
+          {{/if}}
+
+          <div class="issue-meta">
+            <a class="issue-action js-issue-permalink icon-link" href="{{permalink}}" target="_blank"></a>
+          </div>
+        </div>
+      </td>
+    </tr>
+  </table>
+
+  <table class="issue-table">
+    <tr>
+      <td>
+        <div class="issue-meta-list">
+          <div class="issue-meta">
+            {{#inArray actions "set_severity"}}
+              <a class="issue-action issue-action-with-options js-issue-set-severity">
+                <span class="issue-meta-label">{{severityHelper severity}}</span>&nbsp;<i class="icon-dropdown"></i>
+              </a>
+            {{else}}
+              {{severityHelper severity}}
+            {{/inArray}}
+          </div>
+
+          <div class="issue-meta">
+            {{#notEmpty transitions}}
+              <a class="issue-action issue-action-with-options js-issue-transition">
+                <span class="issue-meta-label">{{statusHelper status resolution}}</span>&nbsp;<i
+                  class="icon-dropdown"></i>
+              </a>
+            {{else}}
+              {{statusHelper status resolution}}
+            {{/notEmpty}}
+          </div>
+
+          <div class="issue-meta">
+            {{#inArray actions "assign"}}
+              <a class="issue-action issue-action-with-options js-issue-assign">
+                {{#if assignee}}
+                  {{#ifShowAvatars}}
+                    <span class="text-top">{{avatarHelper assigneeEmail 16}}</span>
+                  {{/ifShowAvatars}}
+                {{/if}}
+                <span class="issue-meta-label">{{#if assignee}}{{default assigneeName assignee}}{{else}}{{t 'unassigned'}}{{/if}}</span>&nbsp;<i class="icon-dropdown"></i>
+              </a>
+            {{else}}
+              {{#if assignee}}
+                {{#ifShowAvatars}}
+                  <span class="text-top">{{avatarHelper assigneeEmail 16}}</span>
+                {{/ifShowAvatars}}
+              {{/if}}
+              <span class="issue-meta-label">{{#if assignee}}{{default assigneeName assignee}}{{else}}{{t 'unassigned'}}{{/if}}</span>
+            {{/inArray}}
+          </div>
+
+          {{#inArray actions "assign_to_me"}}
+            <a class="js-issue-assign-to-me"></a>
+          {{/inArray}}
+
+          <div class="issue-meta">
+            {{#inArray actions "plan"}}
+              <a class="issue-action issue-action-with-options js-issue-plan">
+          <span
+              class="issue-meta-label">{{#if actionPlan}}{{default actionPlanName actionPlan}}{{else}}{{t 'issue.unplanned'}}{{/if}}</span>&nbsp;<i
+                  class="icon-dropdown"></i>
+              </a>
+            {{else}}
+              <span
+                  class="issue-meta-label">{{#if actionPlan}}{{default actionPlanName actionPlan}}{{else}}{{t 'issue.unplanned'}}{{/if}}</span>
+            {{/inArray}}
+          </div>
+
+          {{#if debt}}
+            <div class="issue-meta">
+              <span class="issue-meta-label">
+                {{tp 'issue.x_debt' debt}}
+              </span>
+            </div>
+          {{/if}}
+
+          {{#inArray actions "comment"}}
+            <div class="issue-meta">
+              <a class="issue-action js-issue-comment"><span
+                  class="issue-meta-label">{{t 'issue.comment.formlink' }}</span></a>
+            </div>
+          {{/inArray}}
+
+          {{#ifHasExtraActions actions}}
+            <div class="issue-meta">
+              <a class="issue-action issue-action-with-options js-issue-more">
+                <span class="issue-meta-label">{{t 'more'}}</span>&nbsp;<i class="icon-dropdown"></i>
+              </a>
+            </div>
+          {{/ifHasExtraActions}}
+        </div>
+      </td>
+
+      <td class="issue-table-meta-cell">
+        <div class="issue-meta js-issue-tags">
+          {{#inArray actions "set_tags"}}
+            <a class="issue-action issue-action-with-options js-issue-edit-tags">
+              <span>
+                <i class="icon-tags icon-half-transparent"></i>&nbsp;<span>{{#if tags}}{{join tags ', '}}{{else}}{{t 'issue.no_tag'}}{{/if}}</span>
+              </span>&nbsp;<i class="icon-dropdown"></i>
+            </a>
+          {{else}}
+            <span>
+              <i class="icon-tags icon-half-transparent"></i>&nbsp;<span>{{#if tags}}{{join tags ', '}}{{else}}{{t 'issue.no_tag'}}{{/if}}</span>
+            </span>
+          {{/inArray}}
+        </div>
+      </td>
+    </tr>
+  </table>
+
+  {{#notEmpty comments}}
+    <div class="issue-comments">
+      {{#each comments}}
+        <div class="issue-comment" data-comment-key="{{key}}">
+          <div class="issue-comment-author" title="{{userName}}">
+            {{#ifShowAvatars}}{{avatarHelper email 16}}{{else}}<i class="icon-comment icon-half-transparent"></i>{{/ifShowAvatars}}&nbsp;{{userName}}
+          </div>
+          <div class="issue-comment-text markdown">{{{show html htmlText}}}</div>
+          <div class="issue-comment-age">({{fromNow createdAt}})</div>
+          <div class="issue-comment-actions">
+            {{#if updatable}}
+              <a class="js-issue-comment-edit icon-edit icon-half-transparent"></a>
+              <a class="js-issue-comment-delete icon-delete icon-half-transparent"
+                 data-confirm-msg="{{t 'issue.comment.delete_confirm_message'}}"></a>
+            {{/if}}
+          </div>
+        </div>
+      {{/each}}
+    </div>
+  {{/notEmpty}}
+
+</div>
+
+<a class="issue-navigate js-issue-navigate">
+  <i class="issue-navigate-to-left icon-chevron-left"></i>
+  <i class="issue-navigate-to-right icon-chevron-right"></i>
+</a>
diff --git a/server/sonar-web/src/main/js/components/issue/templates/manual-issue.hbs b/server/sonar-web/src/main/js/components/issue/templates/manual-issue.hbs
new file mode 100644 (file)
index 0000000..bbcf333
--- /dev/null
@@ -0,0 +1,44 @@
+<form action="" class="js-manual-issue-form code-issue-create-form">
+  {{! no manual rules }}
+  {{! <div class="warning" style="margin: 10px"> }}
+  {{!   <% if is_admin %> }}
+  {{!   <%= message('issue.manual.no_rules.admin') -%> }}
+  {{!   &nbsp;<a href="<%= ApplicationController.root_context -%>/manual_rules/index"><%= message('manage') -%></a> }}
+  {{!   <% else %> }}
+  {{!   <%= message('issue.manual.no_rules.non_admin') -%> }}
+  {{!   <% end %> }}
+  {{!   &nbsp;<%= link_to_function message('cancel'), 'closeCreateIssueForm(this)' -%> }}
+  {{! </div> }}
+
+  <input type="hidden" name="line" value="{{line}}">
+  <input type="hidden" name="component" value="{{component}}">
+
+  <div class="code-issue-name">
+    <select name="rule">
+      {{#each rules}}
+        <option value="{{key}}">{{name}}</option>
+      {{/each}}
+    </select>
+  </div>
+
+  <div class="code-issue-msg">
+    <table class="width100">
+      <tr>
+        <td>
+          <textarea rows="4" name="message" class="width100 marginbottom5"></textarea>
+        </td>
+      </tr>
+      <tr>
+        <td class="js-submit">
+          <input type="submit" value="{{t 'create'}}">
+          <a class="js-cancel" href="#">{{t 'cancel'}}</a>
+        </td>
+        <td class="js-spinner" style="display: none;">
+          <i class="spinner"></i>
+        </td>
+      </tr>
+    </table>
+    <div class="code-issue-errors alert alert-danger hidden"></div>
+  </div>
+
+</form>
diff --git a/server/sonar-web/src/main/js/components/issue/views/action-options-view.js b/server/sonar-web/src/main/js/components/issue/views/action-options-view.js
new file mode 100644 (file)
index 0000000..402a0cc
--- /dev/null
@@ -0,0 +1,116 @@
+define([
+  'components/common/popup'
+], function (PopupView) {
+
+  var $ = jQuery;
+
+  return PopupView.extend({
+    keyScope: 'issue-action-options',
+
+    ui: {
+      options: '.issue-action-option'
+    },
+
+    events: function () {
+      return {
+        'click .issue-action-option': 'selectOption',
+        'mouseenter .issue-action-option': 'activateOptionByPointer'
+      };
+    },
+
+    initialize: function () {
+      this.bindShortcuts();
+    },
+
+    onRender: function () {
+      PopupView.prototype.onRender.apply(this, arguments);
+      this.selectInitialOption();
+      this.$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
+    },
+
+    getOptions: function () {
+      return this.$('.issue-action-option');
+    },
+
+    getActiveOption: function () {
+      return this.getOptions().filter('.active');
+    },
+
+    makeActive: function (option) {
+      if (option.length > 0) {
+        this.getOptions().removeClass('active');
+        option.addClass('active');
+      }
+    },
+
+    selectInitialOption: function () {
+      this.makeActive(this.getOptions().first());
+    },
+
+    selectNextOption: function () {
+      this.makeActive(this.getActiveOption().nextAll('.issue-action-option').first());
+      return false;
+    },
+
+    selectPreviousOption: function () {
+      this.makeActive(this.getActiveOption().prevAll('.issue-action-option').first());
+      return false;
+    },
+
+    activateOptionByPointer: function (e) {
+      this.makeActive($(e.currentTarget));
+    },
+
+    bindShortcuts: function () {
+      var that = this;
+      this.currentKeyScope = key.getScope();
+      key.setScope(this.keyScope);
+      key('down', this.keyScope, function () {
+        return that.selectNextOption();
+      });
+      key('up', this.keyScope, function () {
+        return that.selectPreviousOption();
+      });
+      key('return', this.keyScope, function () {
+        return that.selectActiveOption();
+      });
+      key('escape', this.keyScope, function () {
+        return that.close();
+      });
+      key('backspace', this.keyScope, function () {
+        return false;
+      });
+      key('shift+tab', this.keyScope, function () {
+        return false;
+      });
+    },
+
+    unbindShortcuts: function () {
+      key.unbind('down', this.keyScope);
+      key.unbind('up', this.keyScope);
+      key.unbind('return', this.keyScope);
+      key.unbind('escape', this.keyScope);
+      key.unbind('backspace', this.keyScope);
+      key.unbind('tab', this.keyScope);
+      key.unbind('shift+tab', this.keyScope);
+      key.setScope(this.currentKeyScope);
+    },
+
+    onClose: function () {
+      PopupView.prototype.onClose.apply(this, arguments);
+      this.unbindShortcuts();
+      this.$('[data-toggle="tooltip"]').tooltip('destroy');
+      $('.tooltip').remove();
+    },
+
+    selectOption: function (e) {
+      e.preventDefault();
+      this.close();
+    },
+
+    selectActiveOption: function () {
+      this.getActiveOption().click();
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/components/issue/views/assign-form-view.js b/server/sonar-web/src/main/js/components/issue/views/assign-form-view.js
new file mode 100644 (file)
index 0000000..9bda9b4
--- /dev/null
@@ -0,0 +1,158 @@
+define([
+  './action-options-view',
+  '../templates'
+], function (ActionOptionsView) {
+
+  var $ = jQuery;
+
+  return ActionOptionsView.extend({
+    template: Templates['issue-assign-form'],
+    optionTemplate: Templates['issue-assign-form-option'],
+
+    events: function () {
+      return _.extend(ActionOptionsView.prototype.events.apply(this, arguments), {
+        'click input': 'onInputClick',
+        'keydown input': 'onInputKeydown',
+        'keyup input': 'onInputKeyup'
+      });
+    },
+
+    initialize: function () {
+      ActionOptionsView.prototype.initialize.apply(this, arguments);
+      this.assignees = [];
+      this.debouncedSearch = _.debounce(this.search, 250);
+    },
+
+    getAssignee: function () {
+      return this.model.get('assignee');
+    },
+
+    getAssigneeName: function () {
+      return this.model.get('assigneeName');
+    },
+
+    onRender: function () {
+      var that = this;
+      ActionOptionsView.prototype.onRender.apply(this, arguments);
+      this.renderTags();
+      setTimeout(function () {
+        that.$('input').focus();
+      }, 100);
+    },
+
+    renderTags: function () {
+      this.$('.issue-action-option').remove();
+      this.getAssignees().forEach(this.renderAssignee, this);
+      this.selectInitialOption();
+    },
+
+    renderAssignee: function (assignee) {
+      var html = this.optionTemplate(assignee);
+      this.$('.issue-action-options').append(html);
+    },
+
+    selectOption: function (e) {
+      var assignee = $(e.currentTarget).data('value'),
+          assigneeName = $(e.currentTarget).data('text');
+      this.submit(assignee, assigneeName);
+      return ActionOptionsView.prototype.selectOption.apply(this, arguments);
+    },
+
+    submit: function (assignee, assigneeName) {
+      var that = this;
+      var _assignee = this.getAssignee(),
+          _assigneeName = this.getAssigneeName();
+      if (assignee === _assignee) {
+        return;
+      }
+      if (assignee === '') {
+        this.model.set({ assignee: null, assigneeName: null });
+      } else {
+        this.model.set({ assignee: assignee, assigneeName: assigneeName });
+      }
+      return $.ajax({
+        type: 'POST',
+        url: baseUrl + '/api/issues/assign',
+        data: {
+          issue: this.model.id,
+          assignee: assignee
+        }
+      }).fail(function () {
+        that.model.set({ assignee: _assignee, assigneeName: _assigneeName });
+      });
+    },
+
+    onInputClick: function (e) {
+      e.stopPropagation();
+    },
+
+    onInputKeydown: function (e) {
+      this.query = this.$('input').val();
+      if (e.keyCode === 38) {
+        return this.selectPreviousOption();
+      }
+      if (e.keyCode === 40) {
+        return this.selectNextOption();
+      }
+      if (e.keyCode === 13) {
+        return this.selectActiveOption();
+      }
+      if (e.keyCode === 9) {
+        return false;
+      }
+      if (e.keyCode === 27) {
+        return this.close();
+      }
+    },
+
+    onInputKeyup: function () {
+      var query = this.$('input').val();
+      if (query !== this.query) {
+        if (query.length < 2) {
+          query = '';
+        }
+        this.query = query;
+        this.debouncedSearch(query);
+      }
+    },
+
+    search: function (query) {
+      var that = this;
+      if (query.length > 1) {
+        $.get(baseUrl + '/api/users/search', { q: query }).done(function (data) {
+          that.resetAssignees(data.users);
+        });
+      } else {
+        this.resetAssignees([]);
+      }
+    },
+
+    resetAssignees: function (users) {
+      this.assignees = users.map(function (user) {
+        return { id: user.login, text: user.name };
+      });
+      this.renderTags();
+    },
+
+    getAssignees: function () {
+      if (this.assignees.length > 0) {
+        return this.assignees;
+      }
+      var assignees = [{ id: '', text: t('unassigned') }],
+          currentUser = window.SS.user,
+          currentUserName = window.SS.userName;
+      assignees.push({ id: currentUser, text: currentUserName });
+      if (this.getAssignee()) {
+        assignees.push({ id: this.getAssignee(), text: this.getAssigneeName() });
+      }
+      return this.makeUnique(assignees);
+    },
+
+    makeUnique: function (assignees) {
+      return _.uniq(assignees, false, function (assignee) {
+        return assignee.id;
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/components/issue/views/changelog-view.js b/server/sonar-web/src/main/js/components/issue/views/changelog-view.js
new file mode 100644 (file)
index 0000000..b834d90
--- /dev/null
@@ -0,0 +1,20 @@
+define([
+  'components/common/popup',
+  '../templates'
+], function (PopupView) {
+
+  return PopupView.extend({
+    template: Templates['issue-changelog'],
+
+    collectionEvents: {
+      'sync': 'render'
+    },
+
+    serializeData: function () {
+      return _.extend(PopupView.prototype.serializeData.apply(this, arguments), {
+        issue: this.options.issue.toJSON()
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/components/issue/views/comment-form-view.js b/server/sonar-web/src/main/js/components/issue/views/comment-form-view.js
new file mode 100644 (file)
index 0000000..4816ff2
--- /dev/null
@@ -0,0 +1,70 @@
+define([
+  'components/common/popup',
+  '../templates'
+], function (PopupView) {
+
+  var $ = jQuery;
+
+  return PopupView.extend({
+    className: 'bubble-popup issue-comment-bubble-popup',
+    template: Templates['comment-form'],
+
+    ui: {
+      textarea: '.issue-comment-form-text textarea',
+      cancelButton: '.js-issue-comment-cancel',
+      submitButton: '.js-issue-comment-submit'
+    },
+
+    events: {
+      'click': 'onClick',
+      'keydown @ui.textarea': 'onKeydown',
+      'keyup @ui.textarea': 'toggleSubmit',
+      'click @ui.cancelButton': 'cancel',
+      'click @ui.submitButton': 'submit'
+    },
+
+    onRender: function () {
+      var that = this;
+      PopupView.prototype.onRender.apply(this, arguments);
+      setTimeout(function () {
+        that.ui.textarea.focus();
+      }, 100);
+    },
+
+    toggleSubmit: function () {
+      this.ui.submitButton.prop('disabled', this.ui.textarea.val().length === 0);
+    },
+
+    onClick: function (e) {
+      e.stopPropagation();
+    },
+
+    onKeydown: function (e) {
+      if (e.keyCode === 27) {
+        this.close();
+      }
+    },
+
+    cancel: function () {
+      this.options.detailView.updateAfterAction(false);
+    },
+
+    submit: function () {
+      var that = this;
+      var text = this.ui.textarea.val(),
+          update = this.model && this.model.has('key'),
+          method = update ? 'edit_comment' : 'add_comment',
+          url = baseUrl + '/api/issues/' + method,
+          data = { text: text };
+      if (update) {
+        data.key = this.model.get('key');
+      } else {
+        data.issue = this.options.issue.id;
+      }
+      return $.post(url, data).done(function () {
+        that.options.detailView.updateAfterAction(true);
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/components/issue/views/issue-popup.js b/server/sonar-web/src/main/js/components/issue/views/issue-popup.js
new file mode 100644 (file)
index 0000000..575fb50
--- /dev/null
@@ -0,0 +1,33 @@
+define([
+  'components/common/popup'
+], function (PopupView) {
+
+  return PopupView.extend({
+    className: 'bubble-popup issue-bubble-popup',
+
+    template: function () {
+      return '<div class="bubble-popup-arrow"></div>';
+    },
+
+    events: function () {
+      return {
+        'click .js-issue-form-cancel': 'close'
+      };
+    },
+
+    onRender: function () {
+      PopupView.prototype.onRender.apply(this, arguments);
+      this.options.view.$el.appendTo(this.$el);
+      this.options.view.render();
+    },
+
+    onClose: function () {
+      this.options.view.close();
+    },
+
+    attachCloseEvents: function () {
+
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/components/issue/views/more-actions-view.js b/server/sonar-web/src/main/js/components/issue/views/more-actions-view.js
new file mode 100644 (file)
index 0000000..5b1c85e
--- /dev/null
@@ -0,0 +1,23 @@
+define([
+  'components/common/popup',
+  '../templates'
+], function (PopupView) {
+
+  var $ = jQuery;
+
+  return PopupView.extend({
+    template: Templates['issue-more-actions'],
+
+    events: function () {
+      return {
+        'click .js-issue-action': 'action'
+      };
+    },
+
+    action: function (e) {
+      var actionKey = $(e.currentTarget).data('action');
+      return this.options.detailView.action(actionKey);
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/components/issue/views/plan-form-view.js b/server/sonar-web/src/main/js/components/issue/views/plan-form-view.js
new file mode 100644 (file)
index 0000000..5bc566c
--- /dev/null
@@ -0,0 +1,74 @@
+define([
+  './action-options-view',
+  '../templates'
+], function (ActionOptionsView) {
+
+  var $ = jQuery;
+
+  return ActionOptionsView.extend({
+    template: Templates['issue-plan-form'],
+
+    getActionPlan: function () {
+      return this.model.get('actionPlan') || '';
+    },
+
+    getActionPlanName: function () {
+      return this.model.get('actionPlanName');
+    },
+
+    selectInitialOption: function () {
+      this.makeActive(this.getOptions().filter('[data-value="' + this.getActionPlan() + '"]'));
+    },
+
+    selectOption: function (e) {
+      var actionPlan = $(e.currentTarget).data('value'),
+          actionPlanName = $(e.currentTarget).data('text');
+      this.submit(actionPlan, actionPlanName);
+      return ActionOptionsView.prototype.selectOption.apply(this, arguments);
+    },
+
+    submit: function (actionPlan, actionPlanName) {
+      var that = this;
+      var _actionPlan = this.getActionPlan(),
+          _actionPlanName = this.getActionPlanName();
+      if (actionPlan === _actionPlan) {
+        return;
+      }
+      if (actionPlan === '') {
+        this.model.set({
+          actionPlan: null,
+          actionPlanName: null
+        });
+      } else {
+        this.model.set({
+          actionPlan: actionPlan,
+          actionPlanName: actionPlanName
+        });
+      }
+      return $.ajax({
+        type: 'POST',
+        url: baseUrl + '/api/issues/plan',
+        data: {
+          issue: this.model.id,
+          plan: actionPlan
+        }
+      }).fail(function () {
+        return that.model.set({
+          assignee: _actionPlan,
+          assigneeName: _actionPlanName
+        });
+      });
+    },
+
+    getActionPlans: function () {
+      return [{ key: '', name: t('issue.unplanned') }].concat(this.collection.toJSON());
+    },
+
+    serializeData: function () {
+      return _.extend(ActionOptionsView.prototype.serializeData.apply(this, arguments), {
+        items: this.getActionPlans()
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/components/issue/views/set-severity-form-view.js b/server/sonar-web/src/main/js/components/issue/views/set-severity-form-view.js
new file mode 100644 (file)
index 0000000..87e0cd5
--- /dev/null
@@ -0,0 +1,51 @@
+define([
+  './action-options-view',
+  '../templates'
+], function (ActionOptionsView) {
+
+  var $ = jQuery;
+
+  return ActionOptionsView.extend({
+    template: Templates['issue-set-severity-form'],
+
+    getTransition: function () {
+      return this.model.get('severity');
+    },
+
+    selectInitialOption: function () {
+      return this.makeActive(this.getOptions().filter('[data-value="' + this.getTransition() + '"]'));
+    },
+
+    selectOption: function (e) {
+      var severity = $(e.currentTarget).data('value');
+      this.submit(severity);
+      return ActionOptionsView.prototype.selectOption.apply(this, arguments);
+    },
+
+    submit: function (severity) {
+      var that = this;
+      var _severity = this.getTransition();
+      if (severity === _severity) {
+        return;
+      }
+      this.model.set({ severity: severity });
+      return $.ajax({
+        type: 'POST',
+        url: baseUrl + '/api/issues/set_severity',
+        data: {
+          issue: this.model.id,
+          severity: severity
+        }
+      }).fail(function () {
+        that.model.set({ severity: _severity });
+      });
+    },
+
+    serializeData: function () {
+      return _.extend(ActionOptionsView.prototype.serializeData.apply(this, arguments), {
+        items: ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO']
+      });
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/components/issue/views/tags-form-view.js b/server/sonar-web/src/main/js/components/issue/views/tags-form-view.js
new file mode 100644 (file)
index 0000000..a750a24
--- /dev/null
@@ -0,0 +1,177 @@
+define([
+  './action-options-view',
+  '../templates'
+], function (ActionOptionsView) {
+
+  var $ = jQuery;
+
+  return ActionOptionsView.extend({
+    template: Templates['issue-tags-form'],
+    optionTemplate: Templates['issue-tags-form-option'],
+
+    modelEvents: {
+      'change:tags': 'renderTags'
+    },
+
+    events: function () {
+      return _.extend(ActionOptionsView.prototype.events.apply(this, arguments), {
+        'click input': 'onInputClick',
+        'keydown input': 'onInputKeydown',
+        'keyup input': 'onInputKeyup'
+      });
+    },
+
+    initialize: function () {
+      ActionOptionsView.prototype.initialize.apply(this, arguments);
+      this.query = '';
+      this.tags = [];
+      this.selected = 0;
+      this.debouncedSearch = _.debounce(this.search, 250);
+      this.requestTags();
+    },
+
+    requestTags: function () {
+      var that = this;
+      return $.get(baseUrl + '/api/issues/tags', { ps: 25 }).done(function (data) {
+        that.tags = data.tags;
+        that.renderTags();
+      });
+    },
+
+    onRender: function () {
+      var that = this;
+      ActionOptionsView.prototype.onRender.apply(this, arguments);
+      this.renderTags();
+      setTimeout(function () {
+        that.$('input').focus();
+      }, 100);
+    },
+
+    selectInitialOption: function () {
+      this.selected = Math.max(Math.min(this.selected, this.getOptions().length - 1), 0);
+      this.makeActive(this.getOptions().eq(this.selected));
+    },
+
+    filterTags: function (tags) {
+      var that = this;
+      return _.filter(tags, function (tag) {
+        return tag.indexOf(that.query) !== -1;
+      });
+    },
+
+    renderTags: function () {
+      this.$('.issue-action-option').remove();
+      this.filterTags(this.getTags()).forEach(this.renderSelectedTag, this);
+      this.filterTags(_.difference(this.tags, this.getTags())).forEach(this.renderTag, this);
+      if (this.query.length > 0 && this.tags.indexOf(this.query) === -1 && this.getTags().indexOf(this.query) === -1) {
+        this.renderCustomTag(this.query);
+      }
+      this.selectInitialOption();
+    },
+
+    renderSelectedTag: function (tag) {
+      var html = this.optionTemplate({
+        tag: tag,
+        selected: true,
+        custom: false
+      });
+      return this.$('.issue-action-options').append(html);
+    },
+
+    renderTag: function (tag) {
+      var html = this.optionTemplate({
+        tag: tag,
+        selected: false,
+        custom: false
+      });
+      return this.$('.issue-action-options').append(html);
+    },
+
+    renderCustomTag: function (tag) {
+      var html = this.optionTemplate({
+        tag: tag,
+        selected: false,
+        custom: true
+      });
+      return this.$('.issue-action-options').append(html);
+    },
+
+    selectOption: function (e) {
+      e.preventDefault();
+      e.stopPropagation();
+      var tags = this.getTags().slice(),
+          tag = $(e.currentTarget).data('value');
+      if ($(e.currentTarget).data('selected') != null) {
+        tags = _.without(tags, tag);
+      } else {
+        tags.push(tag);
+      }
+      this.selected = this.getOptions().index($(e.currentTarget));
+      return this.submit(tags);
+    },
+
+    submit: function (tags) {
+      var that = this;
+      var _tags = this.getTags();
+      this.model.set({ tags: tags });
+      return $.ajax({
+        type: 'POST',
+        url: baseUrl + '/api/issues/set_tags',
+        data: {
+          key: this.model.id,
+          tags: tags.join()
+        }
+      }).fail(function () {
+        return that.model.set({ tags: _tags });
+      });
+    },
+
+    onInputClick: function (e) {
+      e.stopPropagation();
+    },
+
+    onInputKeydown: function (e) {
+      this.query = this.$('input').val();
+      if (e.keyCode === 38) {
+        return this.selectPreviousOption();
+      }
+      if (e.keyCode === 40) {
+        return this.selectNextOption();
+      }
+      if (e.keyCode === 13) {
+        return this.selectActiveOption();
+      }
+      if (e.keyCode === 9) {
+        return false;
+      }
+      if (e.keyCode === 27) {
+        return this.close();
+      }
+    },
+
+    onInputKeyup: function () {
+      var query = this.$('input').val();
+      if (query !== this.query) {
+        this.query = query;
+        this.debouncedSearch(query);
+      }
+    },
+
+    search: function (query) {
+      this.query = query;
+      return this.renderTags();
+    },
+
+    resetAssignees: function (users) {
+      this.assignees = users.map(function (user) {
+        return { id: user.login, text: user.name };
+      });
+      this.renderTags();
+    },
+
+    getTags: function () {
+      return this.model.get('tags') || [];
+    }
+  });
+
+});
diff --git a/server/sonar-web/src/main/js/components/issue/views/transitions-form-view.js b/server/sonar-web/src/main/js/components/issue/views/transitions-form-view.js
new file mode 100644 (file)
index 0000000..6558537
--- /dev/null
@@ -0,0 +1,36 @@
+define([
+  './action-options-view',
+  '../templates'
+], function (ActionOptionsView) {
+
+  var $ = jQuery;
+
+  return ActionOptionsView.extend({
+    template: Templates['issue-transitions-form'],
+
+    selectInitialOption: function () {
+      this.makeActive(this.getOptions().first());
+    },
+
+    selectOption: function (e) {
+      var transition = $(e.currentTarget).data('value');
+      this.submit(transition);
+      return ActionOptionsView.prototype.selectOption.apply(this, arguments);
+    },
+
+    submit: function (transition) {
+      var that = this;
+      return $.ajax({
+        type: 'POST',
+        url: baseUrl + '/api/issues/do_transition',
+        data: {
+          issue: this.model.get('key'),
+          transition: transition
+        }
+      }).done(function () {
+        return that.options.view.resetIssue({});
+      });
+    }
+  });
+
+});