From eaee386f735b6fe74a424eaefaa0e834b14f16fb Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Fri, 25 Apr 2014 13:21:14 +0600 Subject: [PATCH] Component Viewer: integrate into issue page --- sonar-server/Gruntfile.coffee | 8 +- .../main/coffee/component-viewer/app.coffee | 32 - .../main/coffee/component-viewer/main.coffee | 35 +- .../coffee/component-viewer/source.coffee | 27 +- .../issues/collections/action-plans.coffee | 14 + .../src/main/coffee/issues/issue-view.coffee | 229 +++++++ .../main/coffee/issues/models/issue.coffee | 14 + .../src/main/coffee/issues/models/rule.coffee | 14 + .../issues/views/assign-form-view.coffee | 81 +++ .../issues/views/comment-form-view.coffee | 60 ++ .../coffee/issues/views/plan-form-view.coffee | 59 ++ .../main/coffee/issues/views/rule-view.coffee | 20 + .../views/set-severity-form-view.coffee | 52 ++ .../src/main/hbs/component-viewer/source.hbs | 18 +- .../src/main/hbs/issues/assign-form.hbs | 9 + .../src/main/hbs/issues/comment-form.hbs | 17 + sonar-server/src/main/hbs/issues/issue.hbs | 166 +++++ .../src/main/hbs/issues/plan-form.hbs | 20 + sonar-server/src/main/hbs/issues/rule.hbs | 7 + .../src/main/hbs/issues/set-severity-form.hbs | 16 + sonar-server/src/main/js/issues/extra.js | 640 +----------------- .../src/main/less/component-viewer.less | 37 +- sonar-server/src/main/less/icons.less | 1 + .../src/main/less/navigator/base.less | 2 + sonar-server/src/main/less/style.less | 2 + .../component_viewer_controller.rb | 27 - .../WEB-INF/app/views/issues/search.html.erb | 36 + 27 files changed, 933 insertions(+), 710 deletions(-) delete mode 100644 sonar-server/src/main/coffee/component-viewer/app.coffee create mode 100644 sonar-server/src/main/coffee/issues/collections/action-plans.coffee create mode 100644 sonar-server/src/main/coffee/issues/issue-view.coffee create mode 100644 sonar-server/src/main/coffee/issues/models/issue.coffee create mode 100644 sonar-server/src/main/coffee/issues/models/rule.coffee create mode 100644 sonar-server/src/main/coffee/issues/views/assign-form-view.coffee create mode 100644 sonar-server/src/main/coffee/issues/views/comment-form-view.coffee create mode 100644 sonar-server/src/main/coffee/issues/views/plan-form-view.coffee create mode 100644 sonar-server/src/main/coffee/issues/views/rule-view.coffee create mode 100644 sonar-server/src/main/coffee/issues/views/set-severity-form-view.coffee create mode 100644 sonar-server/src/main/hbs/issues/assign-form.hbs create mode 100644 sonar-server/src/main/hbs/issues/comment-form.hbs create mode 100644 sonar-server/src/main/hbs/issues/issue.hbs create mode 100644 sonar-server/src/main/hbs/issues/plan-form.hbs create mode 100644 sonar-server/src/main/hbs/issues/rule.hbs create mode 100644 sonar-server/src/main/hbs/issues/set-severity-form.hbs delete mode 100644 sonar-server/src/main/webapp/WEB-INF/app/controllers/component_viewer_controller.rb diff --git a/sonar-server/Gruntfile.coffee b/sonar-server/Gruntfile.coffee index 5a5d14c69b5..498263d1a77 100644 --- a/sonar-server/Gruntfile.coffee +++ b/sonar-server/Gruntfile.coffee @@ -161,10 +161,6 @@ module.exports = (grunt) -> name: 'common/select-list' out: '<%= pkg.assets %>build/js/common/select-list.js' - componentViewer: options: - name: 'component-viewer/app' - out: '<%= pkg.assets %>build/js/component-viewer/app.js' - handlebars: options: @@ -191,6 +187,10 @@ module.exports = (grunt) -> '<%= pkg.assets %>js/templates/component-viewer.js': [ '<%= pkg.sources %>hbs/component-viewer/**/*.hbs' ] + '<%= pkg.assets %>js/templates/issues.js': [ + '<%= pkg.sources %>hbs/common/**/*.hbs' + '<%= pkg.sources %>hbs/issues/**/*.hbs' + ] clean: diff --git a/sonar-server/src/main/coffee/component-viewer/app.coffee b/sonar-server/src/main/coffee/component-viewer/app.coffee deleted file mode 100644 index 85ec0f9026d..00000000000 --- a/sonar-server/src/main/coffee/component-viewer/app.coffee +++ /dev/null @@ -1,32 +0,0 @@ -requirejs.config - baseUrl: "#{baseUrl}/js" - - paths: - 'backbone': 'third-party/backbone' - 'backbone.marionette': 'third-party/backbone.marionette' - 'handlebars': 'third-party/handlebars' - 'jquery.mockjax': 'third-party/jquery.mockjax' - - shim: - 'backbone.marionette': - deps: ['backbone'] - exports: 'Marionette' - 'backbone': - exports: 'Backbone' - 'handlebars': - exports: 'Handlebars' - - -requirejs [ - 'component-viewer/main', -], ( - ComponentViewer -) -> - - TEST_RESOURCE_KEY = 'org.codehaus.sonar:sonar-plugin-api:src/main/java/org/sonar/api/resources/ResourceTypeTree.java' - - - @componentViewer = new ComponentViewer() - @componentViewer.render().$el.appendTo '#body' - - @componentViewer.open TEST_RESOURCE_KEY \ No newline at end of file diff --git a/sonar-server/src/main/coffee/component-viewer/main.coffee b/sonar-server/src/main/coffee/component-viewer/main.coffee index c92b1c74c36..c231570efd1 100644 --- a/sonar-server/src/main/coffee/component-viewer/main.coffee +++ b/sonar-server/src/main/coffee/component-viewer/main.coffee @@ -40,11 +40,20 @@ define [ model: @source main: @ - @settings = new Backbone.Model issues: false, coverage: true, duplications: false, scm: false + @settings = new Backbone.Model + issues: false + coverage: false + duplications: false + scm: false + workspace: false onRender: -> - @workspaceRegion.show @workspaceView + if @settings.get 'workspace' + @workspaceRegion.show @workspaceView + @$el.addClass 'component-viewer-workspace-enabled' + else + @$el.removeClass 'component-viewer-workspace-enabled' @sourceRegion.show @sourceView @@ -109,6 +118,28 @@ define [ @sourceView.render() + showWorkspace: -> + @settings.set 'workspace', true + @render() + + + hideWorkspace: -> + @settings.set 'workspace', false + @render() + + + showIssues: (issues, scrollToFirst) -> + @settings.set 'issues', true + if _.isArray(issues) && issues.length > 0 + @source.set 'issues', issues + @sourceView.render() + + + hideIssues: -> + @settings.set 'issues', false + @sourceView.render() + + addTransition: (key, transition, optionsForCurrent, options) -> if optionsForCurrent? last = @workspace.at(@workspace.length - 1) diff --git a/sonar-server/src/main/coffee/component-viewer/source.coffee b/sonar-server/src/main/coffee/component-viewer/source.coffee index 27f72f9da40..72f7c7e7b14 100644 --- a/sonar-server/src/main/coffee/component-viewer/source.coffee +++ b/sonar-server/src/main/coffee/component-viewer/source.coffee @@ -2,11 +2,15 @@ define [ 'backbone.marionette' 'templates/component-viewer' 'component-viewer/coverage-popup' + 'issues/issue-view' + 'issues/models/issue' 'common/handlebars-extensions' ], ( Marionette Templates CoveragePopupView + IssueView + Issue ) -> $ = jQuery @@ -19,6 +23,7 @@ define [ events: 'click .settings-toggle button': 'toggleSettings' 'change #source-coverage': 'toggleCoverage' + 'change #source-workspace': 'toggleWorkspace' 'click .coverage-tests': 'showCoveragePopup' @@ -26,12 +31,23 @@ define [ @delegateEvents() @showSettings = false + @renderIssues() if @options.main.settings.get('issues') && @model.has('issues') + + + renderIssues: -> + issues = @model.get 'issues' + issues.forEach (issue) => + line = issue.line || 0 + container = @$("[data-line-number=#{line}]").children('.line') + container.addClass 'issue' if line > 0 + issueView = new IssueView model: new Issue issue + issueView.render().$el.appendTo container + showSpinner: -> @$el.html '
' - toggleSettings: -> @$('.settings-toggle button').toggleClass 'open' @$('.component-viewer-source-settings').toggleClass 'open' @@ -43,6 +59,12 @@ define [ if active then @options.main.showCoverage() else @options.main.hideCoverage() + toggleWorkspace: (e) -> + active = $(e.currentTarget).is ':checked' + @showSettings = true + if active then @options.main.showWorkspace() else @options.main.hideWorkspace() + + showCoveragePopup: (e) -> e.stopPropagation() $('body').click() @@ -88,4 +110,5 @@ define [ serializeData: -> source: @prepareSource() settings: @options.main.settings.toJSON() - showSettings: @showSettings \ No newline at end of file + showSettings: @showSettings + component: @options.main.component.toJSON() \ No newline at end of file diff --git a/sonar-server/src/main/coffee/issues/collections/action-plans.coffee b/sonar-server/src/main/coffee/issues/collections/action-plans.coffee new file mode 100644 index 00000000000..69e0d459de1 --- /dev/null +++ b/sonar-server/src/main/coffee/issues/collections/action-plans.coffee @@ -0,0 +1,14 @@ +define [ + 'backbone' +], ( + Backbone +) -> + + class ActionPlans extends Backbone.Collection + + url: -> + "#{baseUrl}/api/action_plans/search" + + + parse: (r) -> + r.actionPlans \ No newline at end of file diff --git a/sonar-server/src/main/coffee/issues/issue-view.coffee b/sonar-server/src/main/coffee/issues/issue-view.coffee new file mode 100644 index 00000000000..0c1dc02ceb8 --- /dev/null +++ b/sonar-server/src/main/coffee/issues/issue-view.coffee @@ -0,0 +1,229 @@ +define [ + 'backbone.marionette' + 'templates/issues' + + 'issues/models/rule' + 'issues/views/rule-view' + + 'issues/collections/action-plans' + + 'issues/views/assign-form-view' + 'issues/views/comment-form-view' + 'issues/views/plan-form-view' + 'issues/views/set-severity-form-view' + +], ( + Marionette + Templates + + Rule + RuleView + + ActionPlans + + AssignFormView + CommentFormView + PlanFormView + SetSeverityFormView + +) -> + + $ = jQuery + + + class IssueView extends Marionette.Layout + className: 'code-issues' + template: Templates['issue'] + + + regions: + formRegion: '.code-issue-form' + ruleRegion: '#tab-issue-rule' + + + modelEvents: + 'change': 'render' + + + events: + 'click': 'setDetailScope', + + 'click .code-issue-toggle': 'toggleCollapsed', + + 'click [href=#tab-issue-rule]': 'fetchRule', + + 'click #issue-comment': 'comment', + 'click .issue-comment-edit': 'editComment', + 'click .issue-comment-delete': 'deleteComment', + 'click .issue-transition': 'transition', + 'click #issue-set-severity': 'setSeverity', + 'click #issue-assign': 'assign', + 'click #issue-assign-to-me': 'assignToMe', + 'click #issue-plan': 'plan', + 'click .issue-action': 'action' + + + onRender: -> + @$('.code-issue-details').tabs() + @$('.code-issue-form').hide() + @rule = new Rule key: this.model.get('rule') + @ruleRegion.show new RuleView model: @rule, issue: @model + + + setDetailScope: -> + key.setScope 'detail' + + + onClose: -> + @ruleRegion.reset() if @ruleRegion + + + resetIssue: (options) -> + key = @model.get 'key' + @model.clear silent: true + @model.set { key: key }, { silent: true } + @model.fetch options + + + toggleCollapsed: -> + @$('.code-issue').toggleClass 'code-issue-collapsed' + @fetchRule() + + + fetchRule: -> + unless @rule.has 'name' + @$('#tab-issue-rule').addClass 'navigator-fetching' + @rule.fetch + success: => @$('#tab-issue-rule').removeClass 'navigator-fetching' + + + showActionView: (view) -> + @$('.code-issue-actions').hide() + @$('.code-issue-form').show() + @formRegion.show view + + + showActionSpinner: -> + @$('.code-issue-actions').addClass 'navigator-fetching' + + + hideActionSpinner: -> + @$('.code-issue-actions').removeClass 'navigator-fetching' + + + updateAfterAction: (fetch) -> + @formRegion.reset() + @$('.code-issue-actions').show() + @$('.code-issue-form').hide() + @$('[data-comment-key]').show() + + if fetch + $.when(@resetIssue()).done => @hideActionSpinner() + + + comment: -> + commentFormView = new CommentFormView + issue: @model + detailView: @ + @showActionView commentFormView + + + editComment: (e) -> + commentEl = $(e.target).closest '[data-comment-key]' + commentKey = commentEl.data 'comment-key' + comment = _.findWhere this.model.get('comments'), { key: commentKey } + + commentEl.hide(); + + commentFormView = new CommentFormView + model: new Backbone.Model comment + issue: @model + detailView: @ + @showActionView commentFormView + + + deleteComment: (e) -> + commentKey = $(e.target).closest('[data-comment-key]').data 'comment-key' + confirmMsg = $(e.target).data 'confirm-msg' + + if confirm(confirmMsg) + @showActionSpinner() + $.ajax + type: "POST" + url: baseUrl + "/issue/delete_comment?id=" + commentKey + .done => @updateAfterAction true + .fail (r) => + alert _.pluck(r.responseJSON.errors, 'msg').join(' ') + @hideActionSpinner() + + + transition: (e) -> + @showActionSpinner(); + $.ajax + type: 'POST', + url: baseUrl + '/api/issues/do_transition', + data: + issue: @model.get('key') + transition: $(e.target).data('transition') + .done => @resetIssue() + .fail (r) => + alert _.pluck(r.responseJSON.errors, 'msg').join(' ') + @hideActionSpinner() + + + setSeverity: -> + setSeverityFormView = new SetSeverityFormView + issue: @model + detailView: @ + @showActionView setSeverityFormView + + + assign: -> + assignFormView = new AssignFormView + issue: @model + detailView: this + @showActionView assignFormView + + + assignToMe: -> + @showActionSpinner() + $.ajax + type: 'POST' + url: baseUrl + '/api/issues/assign' + data: + issue: @model.get('key') + assignee: window.SS.currentUser + .done => @resetIssue() + .fail (r) => + alert _.pluck(r.responseJSON.errors, 'msg').join(' ') + @hideActionSpinner() + + + plan: -> + actionPlans = new ActionPlans() + planFormView = new PlanFormView + collection: actionPlans + issue: @model + detailView: @ + @showActionSpinner() + actionPlans.fetch + reset: true + data: project: @model.get('project') + success: => + @hideActionSpinner() + @showActionView planFormView + + + action: (e) -> + actionKey = $(e.target).data 'action' + @showActionSpinner() + $.ajax + type: 'POST' + url: baseUrl + '/api/issues/do_action' + data: + issue: @model.get('key') + actionKey: actionKey + .done => @resetIssue() + .fail (r) => + alert _.pluck(r.responseJSON.errors, 'msg').join(' ') + @hideActionSpinner() \ No newline at end of file diff --git a/sonar-server/src/main/coffee/issues/models/issue.coffee b/sonar-server/src/main/coffee/issues/models/issue.coffee new file mode 100644 index 00000000000..9011a28c596 --- /dev/null +++ b/sonar-server/src/main/coffee/issues/models/issue.coffee @@ -0,0 +1,14 @@ +define [ + 'backbone' +], ( + Backbone +) -> + + class Issue extends Backbone.Model + + url: -> + "#{baseUrl}/api/issues/show?key=#{@get('key')}" + + + parse: (r) -> + if r.issue then r.issue else r \ No newline at end of file diff --git a/sonar-server/src/main/coffee/issues/models/rule.coffee b/sonar-server/src/main/coffee/issues/models/rule.coffee new file mode 100644 index 00000000000..570feb88e94 --- /dev/null +++ b/sonar-server/src/main/coffee/issues/models/rule.coffee @@ -0,0 +1,14 @@ +define [ + 'backbone' +], ( + Backbone +) -> + + class Rule extends Backbone.Model + + url: -> + "#{baseUrl}/api/rules/show/?key=#{@get('key')}" + + + parse: (r) -> + if r.rule then r.rule else r \ No newline at end of file diff --git a/sonar-server/src/main/coffee/issues/views/assign-form-view.coffee b/sonar-server/src/main/coffee/issues/views/assign-form-view.coffee new file mode 100644 index 00000000000..433a1b68ed3 --- /dev/null +++ b/sonar-server/src/main/coffee/issues/views/assign-form-view.coffee @@ -0,0 +1,81 @@ +define [ + 'backbone.marionette' + 'templates/issues' +], ( + Marionette + Templates +) -> + + $ = jQuery + + + class AssignFormView extends Marionette.ItemView + template: Templates['assign-form'] + + + ui: + select: '#issue-assignee-select' + + + events: + 'click #issue-assign-cancel': 'cancel' + 'click #issue-assign-submit': 'submit' + + + onRender: -> + currentUser = window.SS.currentUser + assignee = @options.issue.get('assignee') + additionalChoices = [] + + if !assignee || currentUser != assignee + additionalChoices.push id: currentUser, text: translate('assignedToMe') + + if !!assignee + additionalChoices.push id: '', text: translate('unassigned') + + select2Options = + allowClear: false + width: '250px' + formatNoMatches: -> translate('select2.noMatches') + formatSearching: -> translate('select2.searching') + formatInputTooShort: -> translate('select2.tooShort') + + if additionalChoices.length > 0 + select2Options.minimumInputLength = 0 + select2Options.query = (query) -> + if query.term.length == 0 + query.callback results: additionalChoices + else if query.term.length >= 2 + $.ajax + url: baseUrl + '/api/users/search?f=s2' + data: s: query.term + dataType: 'jsonp' + .done (data) -> query.callback data + else + select2Options.minimumInputLength = 2 + select2Options.ajax = + quietMillis: 300 + url: baseUrl + '/api/users/search?f=s2' + data: (term, page) -> s: term, p: page + results: (data) -> more: data.more, results: data.results + + @ui.select.select2(select2Options).select2 'open' + + + cancel: -> + @options.detailView.updateAfterAction false + + + submit: -> + @options.detailView.showActionSpinner() + + $.ajax + type: 'POST' + url: baseUrl + '/api/issues/assign' + data: + issue: @options.issue.get('key') + assignee: @ui.select.val() + .done => @options.detailView.updateAfterAction true + .fail (r) => + alert _.pluck(r.responseJSON.errors, 'msg').join(' ') + @options.detailView.hideActionSpinner() \ No newline at end of file diff --git a/sonar-server/src/main/coffee/issues/views/comment-form-view.coffee b/sonar-server/src/main/coffee/issues/views/comment-form-view.coffee new file mode 100644 index 00000000000..57c0393ce0a --- /dev/null +++ b/sonar-server/src/main/coffee/issues/views/comment-form-view.coffee @@ -0,0 +1,60 @@ +define [ + 'backbone.marionette' + 'templates/issues' +], ( + Marionette + Templates +) -> + + $ = jQuery + + + class IssueDetailCommentFormView extends Marionette.ItemView + template: Templates['comment-form'] + + + ui: + textarea: '#issue-comment-text' + cancelButton: '#issue-comment-cancel' + submitButton: '#issue-comment-submit' + + + events: + 'keyup #issue-comment-text': 'toggleSubmit' + 'click #issue-comment-cancel': 'cancel' + 'click #issue-comment-submit': 'submit' + + + onDomRefresh: -> + @ui.textarea.focus() + + + toggleSubmit: -> + @ui.submitButton.prop 'disabled', @ui.textarea.val().length == 0 + + + cancel: -> + @options.detailView.updateAfterAction false + + + submit: -> + text = @ui.textarea.val() + update = @model && @model.has('key') + url = baseUrl + '/api/issues/' + (if update then 'edit_comment' else 'add_comment') + data = text: text + + if update + data.key = @model.get('key') + else + data.issue = @options.issue.get('key') + + @options.detailView.showActionSpinner() + + $.ajax + type: 'POST' + url: url + data: data + .done => @options.detailView.updateAfterAction true + .fail (r) => + alert _.pluck(r.responseJSON.errors 'msg').join(' ') + @options.detailView.hideActionSpinner() \ No newline at end of file diff --git a/sonar-server/src/main/coffee/issues/views/plan-form-view.coffee b/sonar-server/src/main/coffee/issues/views/plan-form-view.coffee new file mode 100644 index 00000000000..c7130201516 --- /dev/null +++ b/sonar-server/src/main/coffee/issues/views/plan-form-view.coffee @@ -0,0 +1,59 @@ +define [ + 'backbone.marionette' + 'templates/issues' +], ( + Marionette + Templates +) -> + + $ = jQuery + + + class PlanFormView extends Marionette.ItemView + template: Templates['plan-form'] + + + collectionEvents: + 'reset': 'render' + + + ui: + select: '#issue-detail-plan-select' + + + events: + 'click #issue-plan-cancel': 'cancel' + 'click #issue-plan-submit': 'submit' + + + onRender: -> + @ui.select.select2 + width: '250px' + minimumResultsForSearch: 100 + + @$('.error a').prop('href', baseUrl + '/action_plans/index/' + this.options.issue.get('project')) + + + cancel: -> + @options.detailView.updateAfterAction(false); + + + submit: -> + plan = @ui.select.val() + @options.detailView.showActionSpinner() + + $.ajax + type: 'POST' + url: baseUrl + '/api/issues/plan' + data: + issue: this.options.issue.get('key'), + plan: if plan == '#unplan' then '' else plan + .done => @options.detailView.updateAfterAction true + .fail (r) => + alert _.pluck(r.responseJSON.errors, 'msg').join(' ') + @options.detailView.hideActionSpinner() + + + serializeData: -> + items: @collection.toJSON() + issue: @options.issue.toJSON() \ No newline at end of file diff --git a/sonar-server/src/main/coffee/issues/views/rule-view.coffee b/sonar-server/src/main/coffee/issues/views/rule-view.coffee new file mode 100644 index 00000000000..caf05d4c72f --- /dev/null +++ b/sonar-server/src/main/coffee/issues/views/rule-view.coffee @@ -0,0 +1,20 @@ +define [ + 'backbone.marionette' + 'templates/issues' +], ( + Marionette + Templates +) -> + + class IssueDetailRuleView extends Marionette.ItemView + className: 'rule-desc' + template: Templates['rule'] + + modelEvents: + 'change': 'render' + + + serializeData: -> + _.extend super, + characteristic: this.options.issue.get 'characteristic' + subCharacteristic: this.options.issue.get 'subCharacteristic' \ No newline at end of file diff --git a/sonar-server/src/main/coffee/issues/views/set-severity-form-view.coffee b/sonar-server/src/main/coffee/issues/views/set-severity-form-view.coffee new file mode 100644 index 00000000000..96d756db7b4 --- /dev/null +++ b/sonar-server/src/main/coffee/issues/views/set-severity-form-view.coffee @@ -0,0 +1,52 @@ +define [ + 'backbone.marionette' + 'templates/issues' +], ( + Marionette + Templates +) -> + + $ = jQuery + + + class SetSeverityFormView extends Marionette.ItemView + template: Templates['set-severity-form'] + + + ui: + select: '#issue-set-severity-select' + + + events: + 'click #issue-set-severity-cancel': 'cancel' + 'click #issue-set-severity-submit': 'submit' + + + onRender: -> + format = (state) -> + return state.text unless state.id + ' ' + state.text + + @ui.select.select2 + minimumResultsForSearch: 100 + formatResult: format + formatSelection: format + escapeMarkup: (m) -> m + + + cancel: -> + @options.detailView.updateAfterAction false + + + submit: -> + @options.detailView.showActionSpinner() + $.ajax + type: 'POST' + url: baseUrl + '/api/issues/set_severity' + data: + issue: @options.issue.get('key') + severity: @ui.select.val() + .done => @options.detailView.updateAfterAction true + .fail (r) => + alert _.pluck(r.responseJSON.errors, 'msg').join(' ') + @options.detailView.hideActionSpinner() \ No newline at end of file diff --git a/sonar-server/src/main/hbs/component-viewer/source.hbs b/sonar-server/src/main/hbs/component-viewer/source.hbs index 66a08b3844e..53c30e18648 100644 --- a/sonar-server/src/main/hbs/component-viewer/source.hbs +++ b/sonar-server/src/main/hbs/component-viewer/source.hbs @@ -28,13 +28,29 @@ +
  • + + +
  • + + {{qualifierIcon component.qualifier}} {{component.name}} + + {{#if settings.coverage}} + + + {{/if}} + + + + {{#each source}} - + {{#if ../settings.coverage}} diff --git a/sonar-server/src/main/hbs/issues/assign-form.hbs b/sonar-server/src/main/hbs/issues/assign-form.hbs new file mode 100644 index 00000000000..f25a0503ffa --- /dev/null +++ b/sonar-server/src/main/hbs/issues/assign-form.hbs @@ -0,0 +1,9 @@ + + + + +
    + +   + {{t 'cancel'}} +
    \ No newline at end of file diff --git a/sonar-server/src/main/hbs/issues/comment-form.hbs b/sonar-server/src/main/hbs/issues/comment-form.hbs new file mode 100644 index 00000000000..f517dfd5ac8 --- /dev/null +++ b/sonar-server/src/main/hbs/issues/comment-form.hbs @@ -0,0 +1,17 @@ + + + + + + + + +
    + +
    + + {{t 'cancel'}} + + {{> '_markdown-tips' }} +
    \ No newline at end of file diff --git a/sonar-server/src/main/hbs/issues/issue.hbs b/sonar-server/src/main/hbs/issues/issue.hbs new file mode 100644 index 00000000000..8e75cd7ad72 --- /dev/null +++ b/sonar-server/src/main/hbs/issues/issue.hbs @@ -0,0 +1,166 @@ +
    +
    +
    + {{severityIcon severity}} {{message}} +
    + + +
    + + + + +
    + + +
    + + +
    +
    +
    + +
    + + + {{#each changelog}} + + + + + + {{/each}} + +
    {{fCreationDate}}{{userName}} + {{#each diffs}} + {{this}}
    + {{/each}} +
    +
    +
    + + +
    + {{#each comments}} +
    +

    + + {{userName}} + ({{fCreatedAge}}) + + {{#if updatable}} +    + {{t 'edit'}}  + {{t 'delete'}} + {{/if}} +

    + {{{html}}} +
    + {{/each}} +
    +
    \ No newline at end of file diff --git a/sonar-server/src/main/hbs/issues/plan-form.hbs b/sonar-server/src/main/hbs/issues/plan-form.hbs new file mode 100644 index 00000000000..3d96e6e6a72 --- /dev/null +++ b/sonar-server/src/main/hbs/issues/plan-form.hbs @@ -0,0 +1,20 @@ +{{#if items}} + +   +{{else}} + <% if is_admin? %> + {{t 'issue.plan.error.plan_must_be_created_first_for_admin'}} + <% else %> + {{t 'issue.plan.error.plan_must_be_created_first_for_other'}} + <% end %> +{{/if}} +{{t 'cancel'}} \ No newline at end of file diff --git a/sonar-server/src/main/hbs/issues/rule.hbs b/sonar-server/src/main/hbs/issues/rule.hbs new file mode 100644 index 00000000000..67b635092a3 --- /dev/null +++ b/sonar-server/src/main/hbs/issues/rule.hbs @@ -0,0 +1,7 @@ +

    {{name}}

    +
    {{{description}}}
    +

    + {{key}} + <%= image_tag 'sep12.png', :class => 'spacer-right' -%> + {{#all characteristic subCharacteristic}}{{characteristic}} > {{subCharacteristic}}{{else}}{{t 'issue.technical_debt_deleted'}}{{/all}} +

    \ No newline at end of file diff --git a/sonar-server/src/main/hbs/issues/set-severity-form.hbs b/sonar-server/src/main/hbs/issues/set-severity-form.hbs new file mode 100644 index 00000000000..e85c027873c --- /dev/null +++ b/sonar-server/src/main/hbs/issues/set-severity-form.hbs @@ -0,0 +1,16 @@ + + + + +
    + + + + {{t 'cancel'}} +
    \ No newline at end of file diff --git a/sonar-server/src/main/js/issues/extra.js b/sonar-server/src/main/js/issues/extra.js index 83df044945f..aff0dfcce54 100644 --- a/sonar-server/src/main/js/issues/extra.js +++ b/sonar-server/src/main/js/issues/extra.js @@ -4,9 +4,11 @@ define( '../navigator/filters/filter-bar', 'navigator/filters/base-filters', 'navigator/filters/favorite-filters', - 'navigator/filters/read-only-filters' + 'navigator/filters/read-only-filters', + 'component-viewer/main' ], - function (Backbone, Marionette, FilterBarView, BaseFilters, FavoriteFiltersModule, ReadOnlyFilterView) { + function (Backbone, Marionette, FilterBarView, BaseFilters, FavoriteFiltersModule, ReadOnlyFilterView, + ComponentViewer) { var AppState = Backbone.Model.extend({ @@ -171,23 +173,20 @@ define( var that = this, app = this.options.app, - detailView = new IssueDetailView({ - model: this.model - }), + componentViewer = new ComponentViewer(), showCallback = function () { jQuery('.navigator-details').removeClass('navigator-fetching'); - app.detailsRegion.show(detailView); + app.detailsRegion.show(componentViewer); + componentViewer.open(that.model.get('component')).done(function() { + componentViewer.showIssues([that.model.toJSON()], true); + + var top = componentViewer.$('.code-issues:first').closest('tr').position().top; + jQuery('.navigator-details').scrollTop(top - 40); + }); }; jQuery('.navigator-details').empty().addClass('navigator-fetching'); - jQuery.when(detailView.model.fetch()).done(function () { - if (that.model.get('status') !== 'CLOSED') { - that.fetchSource(detailView, showCallback); - } else { - showCallback(); - } - - }); + jQuery.when(this.model.fetch()).done(showCallback); }, @@ -571,618 +570,6 @@ define( }); - var IssueDetailCommentFormView = Marionette.ItemView.extend({ - template: Handlebars.compile(jQuery('#issue-detail-comment-form-template').html() || ''), - - - ui: { - textarea: '#issue-comment-text', - cancelButton: '#issue-comment-cancel', - submitButton: '#issue-comment-submit' - }, - - - events: { - 'keyup #issue-comment-text': 'toggleSubmit', - 'click #issue-comment-cancel': 'cancel', - 'click #issue-comment-submit': 'submit' - }, - - - onDomRefresh: function () { - this.ui.textarea.focus(); - }, - - - toggleSubmit: function () { - this.ui.submitButton.prop('disabled', this.ui.textarea.val().length === 0); - }, - - - cancel: function () { - this.options.detailView.updateAfterAction(false); - }, - - - submit: function () { - var that = this, - text = this.ui.textarea.val(), - update = this.model && this.model.has('key'), - url = baseUrl + '/api/issues/' + (update ? 'edit_comment' : 'add_comment'), - data = { text: text }; - - if (update) { - data.key = this.model.get('key'); - } else { - data.issue = this.options.issue.get('key'); - } - - this.options.detailView.showActionSpinner(); - - jQuery.ajax({ - type: 'POST', - url: url, - data: data - }) - .done(function () { - that.options.detailView.updateAfterAction(true); - }) - .fail(function (r) { - alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r); - that.options.detailView.hideActionSpinner(); - }); - } - }); - - - var IssueDetailSetSeverityFormView = Marionette.ItemView.extend({ - template: Handlebars.compile(jQuery('#issue-detail-set-severity-form-template').html() || ''), - - - ui: { - select: '#issue-set-severity-select' - }, - - - events: { - 'click #issue-set-severity-cancel': 'cancel', - 'click #issue-set-severity-submit': 'submit' - }, - - - onRender: function () { - var format = function(state) { - if (!state.id) return state.text; // optgroup - return ' ' + state.text; - } - - this.ui.select.select2({ - minimumResultsForSearch: 100, - formatResult: format, - formatSelection: format, - escapeMarkup: function(m) { return m; } - }); - }, - - - cancel: function () { - this.options.detailView.updateAfterAction(false); - }, - - - submit: function () { - var that = this; - - this.options.detailView.showActionSpinner(); - - jQuery.ajax({ - type: 'POST', - url: baseUrl + '/api/issues/set_severity', - data: { - issue: this.options.issue.get('key'), - severity: this.ui.select.val() - } - }) - .done(function () { - that.options.detailView.updateAfterAction(true); - }) - .fail(function (r) { - alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r); - that.options.detailView.hideActionSpinner(); - }); - } - }); - - - var IssueDetailAssignFormView = Marionette.ItemView.extend({ - template: Handlebars.compile(jQuery('#issue-detail-assign-form-template').html() || ''), - - - ui: { - select: '#issue-assignee-select' - }, - - - events: { - 'click #issue-assign-cancel': 'cancel', - 'click #issue-assign-submit': 'submit' - }, - - - onRender: function () { - var currentUser = window.SS.currentUser, - assignee = this.options.issue.get('assignee'), - additionalChoices = []; - - if (!assignee || currentUser !== assignee) { - additionalChoices.push({ - id: currentUser, - text: window.SS.phrases.assignedToMe - }); - } - - if (!!assignee) { - additionalChoices.push({ - id: '', - text: window.SS.phrases.unassigned - }); - } - - var select2Options = { - allowClear: false, - width: '250px', - formatNoMatches: function () { - return window.SS.phrases.select2.noMatches; - }, - formatSearching: function () { - return window.SS.phrases.select2.searching; - }, - formatInputTooShort: function () { - return window.SS.phrases.select2.tooShort; - } - }; - - if (additionalChoices.length > 0) { - select2Options.minimumInputLength = 0; - select2Options.query = function (query) { - if (query.term.length == 0) { - query.callback({ results: additionalChoices }); - } else if (query.term.length >= 2) { - jQuery.ajax({ - url: baseUrl + '/api/users/search?f=s2', - data: { s: query.term }, - dataType: 'jsonp' - }).done(function (data) { - query.callback(data); - }); - } - } - } else { - select2Options.minimumInputLength = 2; - select2Options.ajax = { - quietMillis: 300, - url: baseUrl + '/api/users/search?f=s2', - data: function (term, page) { - return {s: term, p: page} - }, - results: function (data) { - return { more: data.more, results: data.results } - } - }; - } - - this.ui.select.select2(select2Options).select2('open'); - }, - - - cancel: function () { - this.options.detailView.updateAfterAction(false); - }, - - - submit: function () { - var that = this; - - this.options.detailView.showActionSpinner(); - - jQuery.ajax({ - type: 'POST', - url: baseUrl + '/api/issues/assign', - data: { - issue: this.options.issue.get('key'), - assignee: this.ui.select.val() - } - }) - .done(function () { - that.options.detailView.updateAfterAction(true); - }) - .fail(function (r) { - alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r); - that.options.detailView.hideActionSpinner(); - }); - } - }); - - - var IssueDetailPlanFormView = Marionette.ItemView.extend({ - template: Handlebars.compile(jQuery('#issue-detail-plan-form-template').html() || ''), - - - collectionEvents: { - 'reset': 'render' - }, - - - ui: { - select: '#issue-detail-plan-select' - }, - - - events: { - 'click #issue-plan-cancel': 'cancel', - 'click #issue-plan-submit': 'submit' - }, - - - onRender: function () { - this.ui.select.select2({ - width: '250px', - minimumResultsForSearch: 100 - }); - - this.$('.error a') - .prop('href', baseUrl + '/action_plans/index/' + this.options.issue.get('project')); - }, - - - cancel: function () { - this.options.detailView.updateAfterAction(false); - }, - - - submit: function () { - var that = this, - plan = this.ui.select.val(); - - this.options.detailView.showActionSpinner(); - - jQuery.ajax({ - type: 'POST', - url: baseUrl + '/api/issues/plan', - data: { - issue: this.options.issue.get('key'), - plan: plan === '#unplan' ? '' : plan - } - }) - .done(function () { - that.options.detailView.updateAfterAction(true); - }) - .fail(function (r) { - alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r); - that.options.detailView.hideActionSpinner(); - }); - }, - - - serializeData: function () { - return { - items: this.collection.toJSON(), - issue: this.options.issue.toJSON() - } - } - }); - - - var IssueDetailRuleView = Marionette.ItemView.extend({ - template: Handlebars.compile(jQuery('#issue-detail-rule-template').html() || ''), - className: 'rule-desc', - modelEvents: { 'change': 'render' }, - - - serializeData: function () { - return _.extend({ - characteristic: this.options.issue.get('characteristic'), - subCharacteristic: this.options.issue.get('subCharacteristic') - }, this.model.toJSON()); - } - }); - - - var IssueDetailView = Marionette.Layout.extend({ - template: Handlebars.compile(jQuery('#issue-detail-template').html() || ''), - - - regions: { - formRegion: '.code-issue-form', - ruleRegion: '#tab-issue-rule' - }, - - - events: { - 'click': 'setDetailScope', - - 'click .code-issue-toggle': 'toggleCollapsed', - - 'click [href=#tab-issue-rule]': 'fetchRule', - - 'click #issue-comment': 'comment', - 'click .issue-comment-edit': 'editComment', - 'click .issue-comment-delete': 'deleteComment', - 'click .issue-transition': 'transition', - 'click #issue-set-severity': 'setSeverity', - 'click #issue-assign': 'assign', - 'click #issue-assign-to-me': 'assignToMe', - 'click #issue-plan': 'plan', - 'click .issue-action': 'action' - }, - - - modelEvents: { - 'change': 'render' - }, - - - setDetailScope: function() { - key.setScope('detail'); - }, - - - onRender: function () { - this.$('.code-issue-details').tabs(); - this.$('.code-issue-form').hide(); - this.rule = new Rule({ key: this.model.get('rule') }); - this.ruleRegion.show(new IssueDetailRuleView({ - model: this.rule, - issue: this.model - })); - this.initReferenceLinks(); - }, - - - initReferenceLinks: function () { - var sourcesId = 'sources_' + this.model.get('key'); - this.$('#' + sourcesId).on('click', 'span.sym', { id: sourcesId }, highlight_usages); - }, - - - onDomRefresh: function () { - var sourceTitleHeight = this.$('.source_title').outerHeight(); - }, - - - onClose: function () { - if (this.ruleRegion) { - this.ruleRegion.reset(); - } - }, - - - resetIssue: function (options) { - var key = this.model.get('key'); - this.model.clear({ silent: true }); - this.model.set({ key: key }, { silent: true }); - return this.model.fetch(options); - }, - - - toggleCollapsed: function () { - this.$('.code-issue').toggleClass('code-issue-collapsed'); - this.fetchRule(); - }, - - - fetchRule: function () { - var that = this; - if (!this.rule.has('name')) { - this.$('#tab-issue-rule').addClass('navigator-fetching'); - this.rule.fetch({ - success: function () { - that.$('#tab-issue-rule').removeClass('navigator-fetching'); - } - }); - } - }, - - - showActionView: function (view) { - this.$('.code-issue-actions').hide(); - this.$('.code-issue-form').show(); - this.formRegion.show(view); - }, - - - showActionSpinner: function () { - this.$('.code-issue-actions').addClass('navigator-fetching'); - }, - - - hideActionSpinner: function () { - this.$('.code-issue-actions').removeClass('navigator-fetching'); - }, - - - updateAfterAction: function (fetch) { - var that = this; - - that.formRegion.reset(); - that.$('.code-issue-actions').show(); - that.$('.code-issue-form').hide(); - that.$('[data-comment-key]').show(); - - if (fetch) { - jQuery.when(this.resetIssue()).done(function () { - that.hideActionSpinner(); - }); - } - }, - - - comment: function () { - var commentFormView = new IssueDetailCommentFormView({ - issue: this.model, - detailView: this - }); - this.showActionView(commentFormView); - }, - - - editComment: function (e) { - var commentEl = jQuery(e.target).closest('[data-comment-key]'), - commentKey = commentEl.data('comment-key'), - comment = _.findWhere(this.model.get('comments'), { key: commentKey }); - - commentEl.hide(); - - var commentFormView = new IssueDetailCommentFormView({ - model: new Backbone.Model(comment), - issue: this.model, - detailView: this - }); - this.showActionView(commentFormView); - }, - - - deleteComment: function (e) { - var that = this, - commentKey = jQuery(e.target).closest('[data-comment-key]').data('comment-key'), - confirmMsg = jQuery(e.target).data('confirm-msg'); - - if (confirm(confirmMsg)) { - this.showActionSpinner(); - - jQuery.ajax({ - type: "POST", - url: baseUrl + "/issue/delete_comment?id=" + commentKey - }) - .done(function () { - that.updateAfterAction(true); - }) - .fail(function (r) { - alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r); - that.hideActionSpinner(); - }); - } - }, - - - transition: function (e) { - var that = this; - - this.showActionSpinner(); - - jQuery.ajax({ - type: 'POST', - url: baseUrl + '/api/issues/do_transition', - data: { - issue: this.model.get('key'), - transition: jQuery(e.target).data('transition') - } - }) - .done(function () { - that.resetIssue(); - }) - .fail(function (r) { - alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r); - that.hideActionSpinner(); - }); - }, - - - setSeverity: function () { - var setSeverityFormView = new IssueDetailSetSeverityFormView({ - issue: this.model, - detailView: this - }); - this.showActionView(setSeverityFormView); - }, - - - assign: function () { - var assignFormView = new IssueDetailAssignFormView({ - issue: this.model, - detailView: this - }); - this.showActionView(assignFormView); - }, - - - assignToMe: function () { - var that = this; - - this.showActionSpinner(); - - jQuery.ajax({ - type: 'POST', - url: baseUrl + '/api/issues/assign', - data: { - issue: this.model.get('key'), - assignee: window.SS.currentUser - } - }) - .done(function () { - that.resetIssue(); - }) - .fail(function (r) { - alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r); - that.hideActionSpinner(); - }); - }, - - - plan: function () { - var that = this, - actionPlans = new ActionPlans(), - planFormView = new IssueDetailPlanFormView({ - collection: actionPlans, - issue: this.model, - detailView: this - }); - - this.showActionSpinner(); - - actionPlans.fetch({ - reset: true, - data: { project: this.model.get('project') }, - success: function () { - that.hideActionSpinner(); - that.showActionView(planFormView); - } - }); - }, - - action: function (e) { - var that = this, - actionKey = jQuery(e.target).data('action'); - - this.showActionSpinner(); - - jQuery.ajax({ - type: 'POST', - url: baseUrl + '/api/issues/do_action', - data: { - issue: this.model.get('key'), - actionKey: actionKey - } - }) - .done(function () { - that.resetIssue(); - }) - .fail(function (r) { - alert(r.responseJSON.errors ? _.pluck(r.responseJSON.errors, 'msg').join(' ') : r); - that.hideActionSpinner(); - }); - }, - - - serializeData: function () { - return _.extend({ - source: this.source, - scm: this.scm - }, this.model.toJSON()); - } - - }); - var IssuesDetailsFavoriteFilterView = FavoriteFiltersModule.DetailsFavoriteFilterView.extend({ template: Handlebars.compile(jQuery('#issues-details-favorite-filter-template').html() || ''), @@ -1308,7 +695,6 @@ define( IssuesFilterBarView: IssuesFilterBarView, IssuesHeaderView: IssuesHeaderView, IssuesFavoriteFilterView: IssuesFavoriteFilterView, - IssueDetailView: IssueDetailView, IssuesRouter: IssuesRouter }; diff --git a/sonar-server/src/main/less/component-viewer.less b/sonar-server/src/main/less/component-viewer.less index 2fed3795c7a..03d66e988da 100644 --- a/sonar-server/src/main/less/component-viewer.less +++ b/sonar-server/src/main/less/component-viewer.less @@ -8,11 +8,27 @@ width: 100%; min-width: 600px; border: 1px solid @barBorderColor; + border-left-width: 0; .box-sizing(border-box); } +.component-viewer-workspace-enabled { + border-left-width: 1px; + + .component-viewer-workspace { display: block; } + .component-viewer-source { border-left: @workspaceWidth solid @barBackgroundColor; } +} + + +.component-viewer-title { + color: @baseFontColor; + font-size: @baseFontSize; + font-weight: bold; +} + .component-viewer-workspace { + display: none; float: left; width: @workspaceWidth; .box-sizing(border-box); @@ -53,7 +69,6 @@ .component-viewer-source { - border-left: @workspaceWidth solid @barBackgroundColor; .sources2 { border-left: 1px solid @barBorderColor; @@ -119,6 +134,10 @@ cursor: pointer; } + .sources2 .issue pre { + background-color: #ff9090; + } + .sources2 .row { &.coverage-green td.stat { @@ -136,27 +155,15 @@ &.lid { border-right-color: lighten(@red, 15%); } } - - &:hover { - td.line { background-color: @barBackgroundColor; } - - td.stat { - background-color: darken(@barBackgroundColor, 3%); - color: @baseFontColor; - } - - &.coverage-green td.stat { background-color: lighten(@green, 10%); } - &.coverage-red td.stat { background-color: lighten(@red, 10%); } - } } } .component-viewer-source-settings { - visibility: hidden; + display: none; - &.open { visibility: visible; } + &.open { display: inline-block; } & > li { display: inline; diff --git a/sonar-server/src/main/less/icons.less b/sonar-server/src/main/less/icons.less index 37b2f1d458d..f599f328130 100644 --- a/sonar-server/src/main/less/icons.less +++ b/sonar-server/src/main/less/icons.less @@ -338,6 +338,7 @@ a[class^="icon-"], a[class*=" icon-"] { } .icon-comment:before { content: "\f075"; + font-size: @iconSmallFontSize; } .icon-delete:before { content: "\f00d"; diff --git a/sonar-server/src/main/less/navigator/base.less b/sonar-server/src/main/less/navigator/base.less index d9edbf66b8b..32877861e08 100644 --- a/sonar-server/src/main/less/navigator/base.less +++ b/sonar-server/src/main/less/navigator/base.less @@ -75,6 +75,8 @@ .navigator-details { position: relative; margin: 0 @navigatorPadding; + + .component-viewer { margin-bottom: 10px; } } .navigator-resizer { diff --git a/sonar-server/src/main/less/style.less b/sonar-server/src/main/less/style.less index 2e75b6fd2e3..5cdb6e32113 100644 --- a/sonar-server/src/main/less/style.less +++ b/sonar-server/src/main/less/style.less @@ -857,6 +857,8 @@ th.operations, td.operations { .code-issue-permalink { position: absolute; top: 4px; right: 4px; + + a { text-decoration: none; } } .code-issue-name-extra { diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/component_viewer_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/component_viewer_controller.rb deleted file mode 100644 index fdff4bfdff5..00000000000 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/component_viewer_controller.rb +++ /dev/null @@ -1,27 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2014 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -class ComponentViewerController < ApplicationController - - def index - - end - -end diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issues/search.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issues/search.html.erb index cba925a13ba..c30a3ecbef0 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/issues/search.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/issues/search.html.erb @@ -174,4 +174,40 @@ 'Sa': '<%= escape_javascript message("Sa") -%>' } }); + + window.messages = { + 'all': '<%= escape_javascript message('all') -%>', + 'assigned_to': '<%= escape_javascript message('assigned_to') -%>', + 'issue.planned_for': '<%= escape_javascript message('issue.planned_for') -%>', + 'issue.do_plan': '<%= escape_javascript message('issue.do_plan') -%>', + 'issue.planned_for': '<%= escape_javascript message('issue.planned_for') -%>', + 'more_actions': '<%= escape_javascript message('more_actions') -%>', + 'reporter': '<%= escape_javascript message('reporter') -%>', + 'author': '<%= escape_javascript message('author') -%>', + 'cancel': '<%= escape_javascript message('cancel') -%>', + 'save': '<%= escape_javascript message('save') -%>', + 'issue.technical_debt_short': '<%= escape_javascript message('issue.technical_debt_short') -%>', + 'issue.technical_debt_deleted': '<%= escape_javascript message('issue.technical_debt_deleted') -%>', + 'rule': '<%= escape_javascript message('rule') -%>', + 'changelog': '<%= escape_javascript message('changelog') -%>', + 'edit': '<%= escape_javascript message('edit') -%>', + 'delete': '<%= escape_javascript message('delete') -%>', + 'moreCriteria': '<%= escape_javascript message('issue_filter.more_criteria') -%>', + 'issue.plan.submit': '<%= escape_javascript message('issue.plan.submit') -%>', + 'issue.unplan.submit': '<%= escape_javascript message('issue.unplan.submit') -%>', + 'issue.assign.submit': '<%= escape_javascript message('issue.assign.submit') -%>', + 'issue.set_severity.submit': '<%= escape_javascript message('issue.set_severity.submit') -%>', + 'issue.comment.submit': '<%= escape_javascript message('issue.comment.submit') -%>', + 'issue.plan.error.plan_must_be_created_first_for_admin': '<%= escape_javascript message('issue.plan.error.plan_must_be_created_first_for_admin') -%>', + 'issue.plan.error.plan_must_be_created_first_for_other': '<%= escape_javascript message('issue.plan.error.plan_must_be_created_first_for_other') -%>', + 'markdown.helplink': '<%= escape_javascript message('markdown.helplink') -%>', + 'bold': '<%= escape_javascript message('bold') -%>', + 'code': '<%= escape_javascript message('code') -%>', + 'bulleted_point': '<%= escape_javascript message('bulleted_point') -%>', + 'severity.BLOCKER': '<%= escape_javascript message('severity.BLOCKER') -%>', + 'severity.CRITICAL': '<%= escape_javascript message('severity.CRITICAL') -%>', + 'severity.MAJOR': '<%= escape_javascript message('severity.MAJOR') -%>', + 'severity.MINOR': '<%= escape_javascript message('severity.MINOR') -%>', + 'severity.INFO': '<%= escape_javascript message('severity.INFO') -%>' + }; -- 2.39.5