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