diff options
84 files changed, 3244 insertions, 441 deletions
diff --git a/server/sonar-web/Gruntfile.coffee b/server/sonar-web/Gruntfile.coffee index 69b39a66782..5892d28aa23 100644 --- a/server/sonar-web/Gruntfile.coffee +++ b/server/sonar-web/Gruntfile.coffee @@ -254,6 +254,9 @@ module.exports = (grunt) -> '<%= pkg.assets %>js/templates/issues.js': [ '<%= pkg.sources %>hbs/issues/**/*.hbs' ] + '<%= pkg.assets %>js/templates/issues-old.js': [ + '<%= pkg.sources %>hbs/issues-old/**/*.hbs' + ] '<%= pkg.assets %>js/templates/api-documentation.js': [ '<%= pkg.sources %>hbs/api-documentation/**/*.hbs' ] diff --git a/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-facets-view.coffee b/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-facets-view.coffee index 55b3f0b5720..a83094f7084 100644 --- a/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-facets-view.coffee +++ b/server/sonar-web/src/main/coffee/coding-rules/views/coding-rules-facets-view.coffee @@ -12,7 +12,7 @@ define [ ui: facets: '.navigator-facets-list-item' - options: '.navigator-facets-list-item-option' + options: '.facet' events: @@ -50,4 +50,4 @@ define [ property = jQuery(@).data 'property' if !!params[property] _(params[property].split(',')).map (value) -> - jQuery('.navigator-facets-list-item[data-property="' + property + '"] .navigator-facets-list-item-option[data-key="' + value + '"]').addClass 'active' + jQuery('.navigator-facets-list-item[data-property="' + property + '"] .facet[data-key="' + value + '"]').addClass 'active' diff --git a/server/sonar-web/src/main/coffee/issue/collections/issues.coffee b/server/sonar-web/src/main/coffee/issue/collections/issues.coffee new file mode 100644 index 00000000000..7dc4da76734 --- /dev/null +++ b/server/sonar-web/src/main/coffee/issue/collections/issues.coffee @@ -0,0 +1,46 @@ +define [ + 'backbone' + 'issue/models/issue' +], ( + Backbone + 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 + + if component + _.extend issue, + componentLongName: component.longName + componentQualifier: component.qualifier + + if project + _.extend issue, + projectLongName: project.longName + + if rule + _.extend issue, + ruleName: rule.name + + issue diff --git a/server/sonar-web/src/main/coffee/issue/issue-view.coffee b/server/sonar-web/src/main/coffee/issue/issue-view.coffee index 2be7d3120e2..1618fe8d26a 100644 --- a/server/sonar-web/src/main/coffee/issue/issue-view.coffee +++ b/server/sonar-web/src/main/coffee/issue/issue-view.coffee @@ -38,7 +38,6 @@ define [ class IssueView extends Marionette.Layout - className: 'code-issues' template: Templates['issue'] diff --git a/server/sonar-web/src/main/coffee/issues/app.coffee b/server/sonar-web/src/main/coffee/issues/app.coffee new file mode 100644 index 00000000000..29bcf6fee9a --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/app.coffee @@ -0,0 +1,117 @@ +requirejs.config + baseUrl: "#{baseUrl}/js" + + paths: + 'backbone': 'third-party/backbone' + 'backbone.marionette': 'third-party/backbone.marionette' + 'handlebars': 'third-party/handlebars' + + shim: + 'backbone.marionette': + deps: ['backbone'] + exports: 'Marionette' + 'backbone': + exports: 'Backbone' + 'handlebars': + exports: 'Handlebars' + + +requirejs [ + 'backbone', 'backbone.marionette' + + 'issues/models/state' + 'issues/layout' + 'issues/models/issues' + 'issues/models/facets' + 'issues/models/filters' + + 'issues/controller' + 'issues/router' + + 'issues/workspace-list-view' + 'issues/workspace-header-view' + + 'issues/facets-view' + 'issues/filters-view' + + 'common/handlebars-extensions' +], ( + Backbone, Marionette + + State + Layout + Issues + Facets + Filters + + Controller + Router + + WorkspaceListView + WorkspaceHeaderView + + FacetsView + FiltersView +) -> + + $ = jQuery + App = new Marionette.Application + + + App.addInitializer -> + @layout = new Layout() + $('.issues').empty().append @layout.render().el + + + App.addInitializer -> + @state = new State() + @issues = new Issues() + @facets = new Facets() + @filters = new Filters() + + + App.addInitializer -> + @controller = new Controller app: @ + + + App.addInitializer -> + @controller.fetchFilters() + + + App.addInitializer -> + @issuesView = new WorkspaceListView + app: @ + collection: @issues + @layout.workspaceListRegion.show @issuesView + @issuesView.bindScrollEvents() + + + App.addInitializer -> + @workspaceHeaderView = new WorkspaceHeaderView + app: @ + collection: @issues + @layout.workspaceHeaderRegion.show @workspaceHeaderView + + + App.addInitializer -> + @facetsView = new FacetsView + app: @ + collection: @facets + @layout.facetsRegion.show @facetsView + + + App.addInitializer -> + @filtersView = new FiltersView + app: @ + collection: @filters + @layout.filtersRegion.show @filtersView + + + App.addInitializer -> + key.setScope 'list' + @router = new Router app: @ + Backbone.history.start() + + + l10nXHR = window.requestMessages() + jQuery.when(l10nXHR).done -> App.start() diff --git a/server/sonar-web/src/main/coffee/issues/component-viewer/main.coffee b/server/sonar-web/src/main/coffee/issues/component-viewer/main.coffee new file mode 100644 index 00000000000..a51aaf116c4 --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/component-viewer/main.coffee @@ -0,0 +1,191 @@ +define [ + 'backbone' + 'backbone.marionette' + 'templates/issues' + 'issues/models/issues' + 'issues/issue-view' +], ( + Backbone + Marionette + Templates + Issues + IssueView +) -> + + $ = jQuery + + API_SOURCES = "#{baseUrl}/api/sources/show" + LINES_AROUND = 500 + + + class extends Marionette.ItemView + template: Templates['issues-component-viewer'] + + + ui: + sourceBeforeSpinner: '.js-component-viewer-source-before' + sourceAfterSpinner: '.js-component-viewer-source-after' + + + events: + 'click .js-close-component-viewer': 'closeComponentViewer' + + + initialize: (options) -> + @source = new Backbone.Model + source: [] + formattedSource: [] + @component = new Backbone.Model() + @issues = new Issues() + @listenTo @issues, 'add', @addIssue + @listenTo options.app.state, 'change:selectedIndex', @select + @bindShortcuts() + @loadSourceBeforeThrottled = _.throttle @loadSourceBefore, 1000 + @loadSourceAfterThrottled = _.throttle @loadSourceAfter, 1000 + + + bindShortcuts: -> + key 'delete,backspace', 'componentViewer', => + @options.app.controller.closeComponentViewer() + false + + + bindScrollEvents: -> + $(window).on 'scroll.issues-component-viewer', (=> @onScroll()) + + + unbindScrollEvents: -> + $(window).off 'scroll.issues-component-viewer' + + + onScroll: -> + if @model.get('hasSourceBefore') && $(window).scrollTop() <= @ui.sourceBeforeSpinner.offset().top + @loadSourceBeforeThrottled() + + if @model.get('hasSourceAfter') && $(window).scrollTop() + $(window).height() >= @ui.sourceAfterSpinner.offset().top + @loadSourceAfterThrottled() + + + loadSourceBefore: -> + @unbindScrollEvents() + formattedSource = @source.get 'formattedSource' + firstLine = _.first(formattedSource).lineNumber + @requestSources(firstLine - LINES_AROUND, firstLine - 1).done (data) => + newFormattedSource = _.map data.sources, (item) => lineNumber: item[0], code: item[1] + formattedSource = newFormattedSource.concat formattedSource + @source.set formattedSource: formattedSource + @model.set hasSourceBefore: newFormattedSource.length == LINES_AROUND + @render() + @scrollToLine firstLine + @bindScrollEvents() if @model.get('hasSourceBefore') || @model.get('hasSourceAfter') + + + loadSourceAfter: -> + @unbindScrollEvents() + formattedSource = @source.get 'formattedSource' + lastLine = _.last(formattedSource).lineNumber + @requestSources(lastLine + 1, lastLine + LINES_AROUND) + .done (data) => + newFormattedSource = _.map data.sources, (item) => lineNumber: item[0], code: item[1] + formattedSource = formattedSource.concat newFormattedSource + @source.set formattedSource: formattedSource + @model.set hasSourceAfter: newFormattedSource.length == LINES_AROUND + @render() + @bindScrollEvents() if @model.get('hasSourceBefore') || @model.get('hasSourceAfter') + .fail => + @source.set formattedSource: [] + @model.set hasSourceAfter: false + @render() + + + onRender: -> + @renderIssues() + + + renderIssues: -> + @issues.forEach (issue) => + @renderIssue issue + + + addIssue: (issue) -> + @renderIssue issue + + + renderIssue: (issue) -> + line = issue.get 'line' || 0 + row = @$("[data-line-number=#{line}]") + issueView = new IssueView model: issue + if row.length == 0 + issueView.render().$el.insertBefore @$('.issues-workspace-component-viewer-code') + else + row.find('.line').addClass 'issue' + barRow = $('<tr></tr>').insertAfter row + barCell = $('<td colspan="2"></td>').appendTo barRow + issueView.render().$el.appendTo barCell + + + select: -> + selected = @options.app.state.get 'selectedIndex' + selectedKey = @options.app.issues.at(selected).get 'key' + @highlightIssue selectedKey + + + highlightIssue: (key) -> + @$("[data-issue-key]").removeClass 'highlighted' + @$("[data-issue-key='#{key}']").addClass 'highlighted' + + + openFileByIssue: (issue) -> + componentKey = issue.get 'component' + @issues.reset @options.app.issues.filter (issue) => issue.get('component') == componentKey + + line = issue.get('line') || 0 + @model.set key: componentKey, issueLine: line + + @requestSources(line - LINES_AROUND, line + LINES_AROUND).done (data) => + formattedSource = _.map data.sources, (item) => lineNumber: item[0], code: item[1] + @source.set + source: data.sources + formattedSource: formattedSource + @model.set hasSourceBefore: line > LINES_AROUND + @render() + @highlightIssue issue.get 'key' + @scrollToLine issue.get 'line' + @bindScrollEvents() + @requestIssues() + + + requestSources: (lineFrom, lineTo) -> + lineFrom = Math.max 0, lineFrom + $.get API_SOURCES, key: @model.get('key'), from: lineFrom, to: lineTo + + + requestIssues: -> + lastIssue = @options.app.issues.at @options.app.issues.length - 1 + lastLine = _.last(@source.get('formattedSource')).lineNumber + needMore = lastIssue.get('component') == @model.get 'key' + needMore = needMore && (lastIssue.get('line') <= lastLine) + if needMore + @options.app.controller.fetchNextPage().done => + @addNextIssuesPage() + @requestIssues() + + + addNextIssuesPage: -> + componentKey = @model.get 'key' + @issues.add @options.app.issues.filter (issue) => issue.get('component') == componentKey + + + scrollToLine: (line) -> + row = @$("[data-line-number=#{line}]") + goal = if row.length > 0 then row.offset().top - 40 else 0 + $(window).scrollTop goal + + + closeComponentViewer: -> + @options.app.controller.closeComponentViewer() + + + serializeData: -> + _.extend super, + source: @source.get 'formattedSource' diff --git a/server/sonar-web/src/main/coffee/issues/component-viewer/state.coffee b/server/sonar-web/src/main/coffee/issues/component-viewer/state.coffee new file mode 100644 index 00000000000..45e46c2b32a --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/component-viewer/state.coffee @@ -0,0 +1,12 @@ +define [ + 'backbone' +], ( + Backbone +) -> + + class extends Backbone.Model + + defaults: + hasSourceBefore: true + hasSourceAfter: true + diff --git a/server/sonar-web/src/main/coffee/issues/controller.coffee b/server/sonar-web/src/main/coffee/issues/controller.coffee new file mode 100644 index 00000000000..a1a05844c57 --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/controller.coffee @@ -0,0 +1,163 @@ +define [ + 'backbone.marionette' + + 'issues/component-viewer/main' + 'issues/component-viewer/state' +], ( + Marionette + + ComponentViewer + ComponentViewerState +) -> + + $ = jQuery + EXTRA_FIELDS = 'actions,transitions,assigneeName,reporterName,actionPlanName' + PAGE_SIZE = 50 + + + class extends Marionette.Controller + + initialize: (options) -> + @listenTo options.app.state, 'change:query', @fetchIssues + + + _issuesParameters: -> + p: @options.app.state.get 'page' + ps: PAGE_SIZE + s: 'FILE_LINE' + asc: true + extra_fields: EXTRA_FIELDS + facets: @options.app.state.get('facets').join() + + + _allFacets: -> + @options.app.state.get('allFacets').map (facet) -> { property: facet } + + + fetchIssues: (firstPage = true) -> + if firstPage + @options.app.state.set { selectedIndex: 0, page: 1 }, { silent: true } + + data = @_issuesParameters() + _.extend data, @options.app.state.get 'query' + + fetchIssuesProcess = window.process.addBackgroundProcess() + $.get "#{baseUrl}/api/issues/search", data, (r) => + issues = @options.app.issues.parseIssues r + if firstPage + @options.app.issues.reset issues + else + @options.app.issues.add issues + + _.extend @options.app.facets, + components: r.components + projects: r.projects + rules: r.rules + users: r.users + actionPlans: r.actionPlans + @options.app.facets.reset @_allFacets() + @options.app.facets.add r.facets, merge: true + @enableFacets @options.app.state.get 'facets' + + @options.app.state.set + page: r.p + pageSize: r.ps + total: r.total + maxResultsReached: r.p * r.ps >= r.total + + window.process.finishBackgroundProcess fetchIssuesProcess + + + fetchNextPage: -> + @options.app.state.nextPage() + @fetchIssues false + + + fetchFilters: -> + $.get "#{baseUrl}/api/issue_filters/app", (r) => + @options.app.state.set + canBulkChange: r.canBulkChange + canManageFilters: r.canManageFilters + @options.app.filters.reset r.favorites + + + enableFacet: (facet) -> + @options.app.facets.get(facet).set enabled: true + + + enableFacets: (facets) -> + facets.forEach @enableFacet, @ + + + newSearch: -> + @options.app.state.unset 'filter' + @options.app.state.setQuery resolved: 'false' + + + applyFilter: (filter) -> + query = @parseQuery filter.get 'query' + @options.app.state.setQuery query + @options.app.state.set filter: filter, changed: false + + + parseQuery: (query, separator = '|') -> + q = {} + (query || '').split(separator).forEach (t) -> + tokens = t.split('=') + if tokens[0] && tokens[1]? + q[tokens[0]] = decodeURIComponent tokens[1] + q + + + getQuery: (separator = '|') -> + filter = @options.app.state.get 'query' + route = [] + _.map filter, (value, property) -> + route.push "#{property}=#{decodeURIComponent value}" + route.join separator + + + _prepareComponent: (issue) -> + key: issue.get 'component' + name: issue.get 'componentLongName' + qualifier: issue.get 'componentQualifier' + project: issue.get 'project' + projectName: issue.get 'projectLongName' + + + showComponentViewer: (issue) -> + key.setScope 'componentViewer' + @options.app.issuesView.unbindScrollEvents() + @options.app.state.set 'component', @_prepareComponent(issue) + @options.app.componentViewer = new ComponentViewer + app: @options.app + model: new ComponentViewerState() + @options.app.layout.workspaceComponentViewerRegion.show @options.app.componentViewer + @options.app.layout.showComponentViewer() + @options.app.componentViewer.openFileByIssue issue + + + closeComponentViewer: -> + key.setScope 'list' + @options.app.state.unset 'component' + @options.app.componentViewer.unbindScrollEvents() + @options.app.layout.workspaceComponentViewerRegion.reset() + @options.app.layout.hideComponentViewer() + @options.app.issuesView.bindScrollEvents() + @options.app.issuesView.scrollToIssue() + + + selectNextIssue: -> + index = @options.app.state.get('selectedIndex') + 1 + if index < @options.app.issues.length + @options.app.state.set selectedIndex: index + else + unless @options.app.state.get('maxResultsReached') + @fetchNextPage().done => + @options.app.state.set selectedIndex: index + + + selectPreviousIssue: -> + index = @options.app.state.get('selectedIndex') - 1 + if index >= 0 + @options.app.state.set selectedIndex: index diff --git a/server/sonar-web/src/main/coffee/issues/facets-view.coffee b/server/sonar-web/src/main/coffee/issues/facets-view.coffee new file mode 100644 index 00000000000..e1e6e121a78 --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/facets-view.coffee @@ -0,0 +1,43 @@ +define [ + 'backbone.marionette' + + 'issues/facets/base-facet' + 'issues/facets/severity-facet' + 'issues/facets/status-facet' + 'issues/facets/project-facet' + 'issues/facets/assignee-facet' + 'issues/facets/rule-facet' + 'issues/facets/resolution-facet' + 'issues/facets/creation-date-facet' +], ( + Marionette + BaseFacet + SeverityFacet + StatusFacet + ProjectFacet + AssigneeFacet + RuleFacet + ResolutionFacet + CreationDateFacet +) -> + + class extends Marionette.CollectionView + className: 'issues-facets-list' + + + getItemView: (model) -> + switch model.get 'property' + when 'severities' then SeverityFacet + when 'statuses' then StatusFacet + when 'assignees' then AssigneeFacet + when 'resolutions' then ResolutionFacet + when 'created' then CreationDateFacet + when 'componentRootUuids' then ProjectFacet + when 'rules' then RuleFacet + when 'creationDate' then CreationDateFacet + else BaseFacet + + + itemViewOptions: -> + app: @options.app + diff --git a/server/sonar-web/src/main/coffee/issues/facets/assignee-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/assignee-facet.coffee new file mode 100644 index 00000000000..0c922e9ba5e --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/facets/assignee-facet.coffee @@ -0,0 +1,52 @@ +define [ + 'issues/facets/base-facet' + 'templates/issues' +], ( + BaseFacet + Templates +) -> + + $ = jQuery + + + class extends BaseFacet + template: Templates['issues-assignee-facet'] + + + onRender: -> + super + + value = @options.app.state.get('query')['assigned'] + if value? && (!value || value == 'false') + @$('.js-issues-facet').filter("[data-unassigned]").addClass 'active' + + + toggleFacet: (e) -> + unassigned = $(e.currentTarget).is "[data-unassigned]" + if unassigned + $(e.currentTarget).toggleClass 'active' + checked = $(e.currentTarget).is '.active' + if checked + @options.app.state.updateFilter assigned: 'false', assignees: null + else + @options.app.state.updateFilter assigned: null, assignees: null + else + super + + + getValuesWithLabels: -> + values = @model.getValues() + users = @options.app.facets.users + values.forEach (v) => + login = v.val + name = '' + if login + user = _.findWhere users, login: login + name = user.name if user? + v.label = name + values + + + serializeData: -> + _.extend super, + values: @getValuesWithLabels() diff --git a/server/sonar-web/src/main/coffee/issues/facets/base-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/base-facet.coffee new file mode 100644 index 00000000000..190028b9326 --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/facets/base-facet.coffee @@ -0,0 +1,52 @@ +define [ + 'backbone.marionette' + 'templates/issues' +], ( + Marionette + Templates +) -> + + $ = jQuery + + + class extends Marionette.ItemView + className: 'issues-facet-box' + template: Templates['issues-base-facet'] + + + modelEvents: -> + 'change': 'render' + + + events: -> + 'click .js-issues-facet-toggle': 'toggle' + 'click .js-issues-facet': 'toggleFacet' + + + onRender: -> + console.log @model.id, @model.get 'enabled' + @$el.toggleClass 'issues-facet-box-collapsed', !@model.get('enabled') + + property = @model.get 'property' + value = @options.app.state.get('query')[property] + if typeof value == 'string' + value.split(',').forEach (s) => + @$('.js-issues-facet').filter("[data-value='#{s}']").addClass 'active' + + + toggle: -> + @model.toggle() + + + getValue: -> + @$('.js-issues-facet.active').map(-> $(@).data 'value').get().join() + + + toggleFacet: (e) -> + $(e.currentTarget).toggleClass 'active' + property = @model.get 'property' + value = @getValue() + obj = {} + obj[property] = value + @options.app.state.updateFilter obj + diff --git a/server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee new file mode 100644 index 00000000000..5979ba1f977 --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/facets/creation-date-facet.coffee @@ -0,0 +1,42 @@ +define [ + 'issues/facets/base-facet' + 'templates/issues' +], ( + BaseFacet + Templates +) -> + + $ = jQuery + + + class extends BaseFacet + template: Templates['issues-creation-date-facet'] + + + events: -> + _.extend super, + 'change input': 'applyFacet' + + + onRender: -> + @$el.toggleClass 'issues-facet-box-collapsed', !@model.get('enabled') + + @$('input').datepicker + dateFormat: 'yy-mm-dd' + changeMonth: true + changeYear: true + + props = ['createdAfter', 'createdBefore', 'createdAt'] + query = @options.app.state.get 'query' + props.forEach (prop) => + value = query[prop] + @$("input[name=#{prop}]").val value if value? + + + applyFacet: -> + obj = {} + @$('input').each -> + property = $(@).prop 'name' + value = $(@).val() + obj[property] = value + @options.app.state.updateFilter obj diff --git a/server/sonar-web/src/main/coffee/issues/facets/project-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/project-facet.coffee new file mode 100644 index 00000000000..4b4495000ed --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/facets/project-facet.coffee @@ -0,0 +1,25 @@ +define [ + 'issues/facets/base-facet' +], ( + BaseFacet +) -> + + + class extends BaseFacet + + getValuesWithLabels: -> + values = @model.getValues() + projects = @options.app.facets.projects + values.forEach (v) => + uuid = v.val + label = '' + if uuid + project = _.findWhere projects, uuid: uuid + label = project.longName if project? + v.label = label + values + + + serializeData: -> + _.extend super, + values: @getValuesWithLabels() diff --git a/server/sonar-web/src/main/coffee/issues/facets/resolution-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/resolution-facet.coffee new file mode 100644 index 00000000000..a51aa4ab796 --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/facets/resolution-facet.coffee @@ -0,0 +1,43 @@ +define [ + 'issues/facets/base-facet' + 'templates/issues' +], ( + BaseFacet + Templates +) -> + + $ = jQuery + + + class extends BaseFacet + template: Templates['issues-resolution-facet'] + + + onRender: -> + super + + value = @options.app.state.get('query')['resolved'] + if value? && (!value || value == 'false') + @$('.js-issues-facet').filter("[data-unresolved]").addClass 'active' + + + toggleFacet: (e) -> + unresolved = $(e.currentTarget).is "[data-unresolved]" + $(e.currentTarget).toggleClass 'active' + if unresolved + checked = $(e.currentTarget).is '.active' + value = if checked then 'false' else null + @options.app.state.updateFilter resolved: value, resolutions: null + else + @options.app.state.updateFilter resolved: null, resolutions: @getValue() + + + sortValues: (values) -> + order = ['FIXED', 'FALSE-POSITIVE', 'CLOSED'] + _.sortBy values, (v) -> order.indexOf v.val + + + serializeData: -> + _.extend super, + values: @sortValues @model.getValues() + diff --git a/server/sonar-web/src/main/coffee/issues/facets/rule-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/rule-facet.coffee new file mode 100644 index 00000000000..df5b544bb9a --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/facets/rule-facet.coffee @@ -0,0 +1,25 @@ +define [ + 'issues/facets/base-facet' +], ( + BaseFacet +) -> + + + class extends BaseFacet + + getValuesWithLabels: -> + values = @model.getValues() + rules = @options.app.facets.rules + values.forEach (v) => + key = v.val + label = '' + if key + rule = _.findWhere rules, key: key + label = rule.name if rule? + v.label = label + values + + + serializeData: -> + _.extend super, + values: @getValuesWithLabels() diff --git a/server/sonar-web/src/main/coffee/issues/facets/severity-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/severity-facet.coffee new file mode 100644 index 00000000000..2f7dac47911 --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/facets/severity-facet.coffee @@ -0,0 +1,22 @@ +define [ + 'issues/facets/base-facet' + 'templates/issues' +], ( + BaseFacet + Templates +) -> + + + class extends BaseFacet + template: Templates['issues-severity-facet'] + + + sortValues: (values) -> + order = ['BLOCKER', 'MINOR', 'CRITICAL', 'INFO', 'MAJOR'] + _.sortBy values, (v) -> order.indexOf v.val + + + serializeData: -> + _.extend super, + values: @sortValues @model.getValues() + diff --git a/server/sonar-web/src/main/coffee/issues/facets/status-facet.coffee b/server/sonar-web/src/main/coffee/issues/facets/status-facet.coffee new file mode 100644 index 00000000000..ced0a9dedfb --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/facets/status-facet.coffee @@ -0,0 +1,21 @@ +define [ + 'issues/facets/base-facet' + 'templates/issues' +], ( + BaseFacet + Templates +) -> + + + class extends BaseFacet + template: Templates['issues-status-facet'] + + + sortValues: (values) -> + order = ['OPEN', 'RESOLVED', 'REOPENED', 'CLOSED', 'CONFIRMED'] + _.sortBy values, (v) -> order.indexOf v.val + + + serializeData: -> + _.extend super, + values: @sortValues @model.getValues() diff --git a/server/sonar-web/src/main/coffee/issues/filters-view.coffee b/server/sonar-web/src/main/coffee/issues/filters-view.coffee new file mode 100644 index 00000000000..1d9f9e9eaed --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/filters-view.coffee @@ -0,0 +1,77 @@ +define [ + 'backbone.marionette' + 'templates/issues' +], ( + Marionette + Templates +) -> + + $ = jQuery + + + class extends Marionette.ItemView + template: Templates['issues-filters'] + + + events: + 'click .js-issues-toggle-filters': 'toggleFilters' + 'click .js-issues-filter': 'applyFilter' + 'click .js-issues-new-search': 'newSearch' + 'click .js-issues-save-as': 'saveAs' + 'click .js-issues-save': 'save' + 'click .js-issues-copy': 'copy' + 'click .js-issues-edit': 'edit' + + + initialize: (options) -> + @listenTo options.app.state, 'change:filter', @render + @listenTo options.app.state, 'change:changed', @render + @listenTo options.app.filters, 'all', @render + window.onSaveAs = window.onCopy = window.onEdit = (id) => + $('#modal').dialog 'close' + @options.app.controller.fetchFilters().done => + filter = @collection.get id + filter.fetch().done => @options.app.controller.applyFilter filter + + + toggleFilters: -> + @$('.issues-filters-list').toggle() + + + applyFilter: (e) -> + id = $(e.currentTarget).data 'id' + filter = @collection.get id + filter.fetch().done => @options.app.controller.applyFilter filter + + + newSearch: -> + @options.app.controller.newSearch() + + + saveAs: -> + query = @options.app.controller.getQuery '&' + url = "#{baseUrl}/issues/save_as_form?#{query}" + openModalWindow url, {} + + + save: -> + query = @options.app.controller.getQuery '&' + url = "#{baseUrl}/issues/save/#{@options.app.state.get('filter').id}?#{query}" + $.post(url).done => + @options.app.state.set changed: false + + + copy: -> + url = "#{baseUrl}/issues/copy_form/#{@options.app.state.get('filter').id}" + openModalWindow url, {} + + + edit: -> + url = "#{baseUrl}/issues/edit_form/#{@options.app.state.get('filter').id}" + openModalWindow url, {} + + + serializeData: -> + _.extend super, + state: @options.app.state.toJSON() + filter: @options.app.state.get('filter')?.toJSON() diff --git a/server/sonar-web/src/main/coffee/issues/issue-view.coffee b/server/sonar-web/src/main/coffee/issues/issue-view.coffee new file mode 100644 index 00000000000..624327d2672 --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/issue-view.coffee @@ -0,0 +1,11 @@ +define [ + 'issue/issue-view' + 'templates/issues' +], ( + IssueView + Templates +) -> + + + class extends IssueView + template: Templates['issues-issue'] diff --git a/server/sonar-web/src/main/coffee/issues/layout.coffee b/server/sonar-web/src/main/coffee/issues/layout.coffee new file mode 100644 index 00000000000..c0649f25b4c --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/layout.coffee @@ -0,0 +1,63 @@ +define [ + 'backbone.marionette' + 'templates/issues' +], ( + Marionette + Templates +) -> + + $ = jQuery + + # http://stackoverflow.com/questions/7600454/how-to-prevent-page-scrolling-when-scrolling-a-div-element + $.fn.isolatedScroll = -> + @on 'mousewheel DOMMouseScroll', (e) -> + delta = e.wheelDelta || (e.originalEvent && e.originalEvent.wheelDelta) || -e.detail + bottomOverflow = @scrollTop + $(@).outerHeight() - @scrollHeight >= 0 + topOverflow = @scrollTop <= 0 + e.preventDefault() if (delta < 0 && bottomOverflow) || (delta > 0 && topOverflow) + @ + + + class extends Marionette.Layout + template: Templates['issues-layout'] + + + regions: + filtersRegion: '.issues-filters' + facetsRegion: '.issues-facets' + workspaceHeaderRegion: '.issues-workspace-header' + workspaceListRegion: '.issues-workspace-list' + workspaceComponentViewerRegion: '.issues-workspace-component-viewer' + + + initialize: -> + $(window).on 'scroll.issues-layout', (=> @onScroll()) + + + onClose: -> + $(window).off 'scroll.issues-layout' + + + onRender: -> + @$('.issues-side').isolatedScroll() + + + onScroll: -> + scrollTop = $(window).scrollTop() + $('.issues').toggleClass 'sticky', scrollTop >= 30 + @$('.issues-side').css top: Math.max(0, Math.min(30 - scrollTop, 30)) + + + showSpinner: (region) -> + @[region].show new Marionette.ItemView + template: _.template('<i class="spinner"></i>') + + + showComponentViewer: -> + @scroll = $(window).scrollTop() + $('.issues').addClass 'issues-extended-view' + + + hideComponentViewer: -> + $('.issues').removeClass 'issues-extended-view' + $(window).scrollTop @scroll if @scroll? diff --git a/server/sonar-web/src/main/coffee/issues/models/facet.coffee b/server/sonar-web/src/main/coffee/issues/models/facet.coffee new file mode 100644 index 00000000000..fa7cb817441 --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/models/facet.coffee @@ -0,0 +1,22 @@ +define [ + 'backbone' +], ( + Backbone +) -> + + class extends Backbone.Model + idAttribute: 'property' + + + defaults: + enabled: false + + + getValues: -> + console.log @toJSON() + @get('values') || [] + + + toggle: -> + enabled = @get 'enabled' + @set enabled: !enabled diff --git a/server/sonar-web/src/main/coffee/issues/models/facets.coffee b/server/sonar-web/src/main/coffee/issues/models/facets.coffee new file mode 100644 index 00000000000..b0be4ad3c3c --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/models/facets.coffee @@ -0,0 +1,10 @@ +define [ + 'backbone' + 'issues/models/facet' +], ( + Backbone + Facet +) -> + + class extends Backbone.Collection + model: Facet diff --git a/server/sonar-web/src/main/coffee/issues/models/filter.coffee b/server/sonar-web/src/main/coffee/issues/models/filter.coffee new file mode 100644 index 00000000000..bbb881f924e --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/models/filter.coffee @@ -0,0 +1,14 @@ +define [ + 'backbone' +], ( + Backbone +) -> + + class extends Backbone.Model + + url: -> + "/api/issue_filters/show/#{@id}" + + + parse: (r) -> + if r.filter? then r.filter else r diff --git a/server/sonar-web/src/main/coffee/issues/models/filters.coffee b/server/sonar-web/src/main/coffee/issues/models/filters.coffee new file mode 100644 index 00000000000..b6c8f9dc59f --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/models/filters.coffee @@ -0,0 +1,10 @@ +define [ + 'backbone' + 'issues/models/filter' +], ( + Backbone + Filter +) -> + + class extends Backbone.Collection + model: Filter diff --git a/server/sonar-web/src/main/coffee/issues/models/issues.coffee b/server/sonar-web/src/main/coffee/issues/models/issues.coffee new file mode 100644 index 00000000000..7174163359e --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/models/issues.coffee @@ -0,0 +1,61 @@ +define [ + 'backbone' + 'issue/models/issue' +], ( + Backbone + Issue +) -> + + class extends Backbone.Collection + model: Issue + + url: -> + "#{baseUrl}/api/issues/search" + + + # Used to parse /api/issues/search response + parseIssues: (r) -> + find = (source, key, keyField) -> + searchDict = {} + searchDict[keyField || 'key'] = key + _.findWhere(source, searchDict) || key + + r.issues.map (issue) -> + component = find r.components, issue.component + project = find r.projects, issue.project + rule = find r.rules, issue.rule + + if component + _.extend issue, + componentLongName: component.longName + componentQualifier: component.qualifier + + if project + _.extend issue, + projectLongName: project.longName + + if rule + _.extend issue, + ruleName: rule.name + + if _.isArray(issue.sources) && issue.sources.length > 0 + source = '' + issue.sources.forEach (line) -> + source = line[1] if line[0] == issue.line + _.extend issue, source: source + + + if _.isArray(issue.scm) && issue.scm.length > 0 + scmAuthor = '' + scmDate = '' + + issue.scm.forEach (line) -> + if line[0] == issue.line + scmAuthor = line[1] + scmDate = line[2] + + _.extend issue, + scmAuthor: scmAuthor + scmDate: scmDate + + issue diff --git a/server/sonar-web/src/main/coffee/issues/models/state.coffee b/server/sonar-web/src/main/coffee/issues/models/state.coffee new file mode 100644 index 00000000000..6ef3f104983 --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/models/state.coffee @@ -0,0 +1,42 @@ +define [ + 'backbone' +], ( + Backbone +) -> + + class extends Backbone.Model + + defaults: + page: 1 + maxResultsReached: false + + query: {} + + facets: ['severities', 'statuses', 'resolutions', 'componentRootUuids'] + allFacets: ['severities', 'statuses', 'resolutions', 'componentRootUuids', 'assignees', 'reporters', 'rule', + 'languages', 'actionPlan', 'creationDate'] + + + nextPage: -> + page = @get 'page' + @set page: page + 1 + + + cleanQuery: (query) -> + q = {} + Object.keys(query).forEach (key) -> + q[key] = query[key] if query[key] + q + + + updateFilter: (obj) -> + filter = @get 'query' + _.extend filter, obj + @setQuery @cleanQuery filter + + + setQuery: (query) -> + @set { query: query }, { silent: true } + @trigger 'change:query' + @set changed: true + diff --git a/server/sonar-web/src/main/coffee/issues/router.coffee b/server/sonar-web/src/main/coffee/issues/router.coffee new file mode 100644 index 00000000000..b89b9e5ace3 --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/router.coffee @@ -0,0 +1,32 @@ +define [ + 'backbone' +], ( + Backbone +) -> + + class extends Backbone.Router + routeSeparator: '|' + + routes: + '': 'emptyQuery' + ':query': 'index' + + + initialize: (options) -> + @options = options + @listenTo @options.app.state, 'change:query', @updateRoute + + + emptyQuery: -> + @navigate 'resolved=false', { trigger: true, replace: true } + + + index: (query) -> + filter = @options.app.controller.parseQuery query + @options.app.state.setQuery filter + + + updateRoute: -> + route = @options.app.controller.getQuery() + @navigate route + diff --git a/server/sonar-web/src/main/coffee/issues/workspace-header-view.coffee b/server/sonar-web/src/main/coffee/issues/workspace-header-view.coffee new file mode 100644 index 00000000000..fd2e92e439a --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/workspace-header-view.coffee @@ -0,0 +1,70 @@ +define [ + 'backbone.marionette' + 'templates/issues' +], ( + Marionette + Templates +) -> + + $ = jQuery + + + class extends Marionette.ItemView + template: Templates['issues-workspace-header'] + + + collectionEvents: + 'all': 'render' + + + events: + 'click .js-back': 'returnToList' + 'click .js-issues-bulk-change': 'bulkChange' + 'click .js-issues-next': 'selectNextIssue' + 'click .js-issues-prev': 'selectPrevIssue' + + + initialize: (options) -> + @listenTo options.app.state, 'change', @render + @_onBulkIssues = window.onBulkIssues + window.onBulkIssues = => + $('#modal').dialog 'close' + @options.app.controller.fetchIssues() + @bindShortcuts() + + + onClose: -> + window.onBulkIssues = @_onBulkIssues + + + returnToList: -> + @options.app.controller.closeComponentViewer() + + + bulkChange: -> + query = @options.app.controller.getQuery '&' + url = "#{baseUrl}/issues/bulk_change_form?#{query}" + openModalWindow url, {} + + + selectNextIssue: -> + @options.app.controller.selectNextIssue() + + + selectPrevIssue: -> + @options.app.controller.selectPreviousIssue() + + + bindShortcuts: -> + key 'j,up', => + @options.app.controller.selectPreviousIssue() + false + key 'k,down', => + @options.app.controller.selectNextIssue() + false + + + serializeData: -> + _.extend super, + state: @options.app.state.toJSON() + diff --git a/server/sonar-web/src/main/coffee/issues/workspace-list-item-view.coffee b/server/sonar-web/src/main/coffee/issues/workspace-list-item-view.coffee new file mode 100644 index 00000000000..1d5f67c4df1 --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/workspace-list-item-view.coffee @@ -0,0 +1,42 @@ +define [ + 'backbone.marionette' + 'templates/issues' + 'issues/issue-view' +], ( + Marionette + Templates + IssueBoxView +) -> + + class extends Marionette.ItemView + tagName: 'li' + className: 'issue-box' + template: Templates['issues-workspace-list-item'] + + + events: + 'click .js-issues-snippet': 'openComponentViewer' + + + initialize: (options) -> + @listenTo options.app.state, 'change:selectedIndex', @select + + + onRender: -> + @issueBoxView = new IssueBoxView model: @model + @$('.issue-box-details').append @issueBoxView.render().el + @select() + + + select: -> + selected = @options.index == @options.app.state.get 'selectedIndex' + @$el.toggleClass 'selected', selected + + + onClose: -> + @issueBoxView?.close() + + + openComponentViewer: -> + @options.app.state.set selectedIndex: @options.index + @options.app.controller.showComponentViewer @model diff --git a/server/sonar-web/src/main/coffee/issues/workspace-list-view.coffee b/server/sonar-web/src/main/coffee/issues/workspace-list-view.coffee new file mode 100644 index 00000000000..18413b1dff2 --- /dev/null +++ b/server/sonar-web/src/main/coffee/issues/workspace-list-view.coffee @@ -0,0 +1,93 @@ +define [ + 'backbone.marionette' + 'templates/issues' + 'issues/workspace-list-item-view' +], ( + Marionette + Templates + IssueView +) -> + + $ = jQuery + + TOP_OFFSET = 38 + BOTTOM_OFFSET = 10 + + + class extends Marionette.CompositeView + template: Templates['issues-workspace-list'] + itemView: IssueView + itemViewContainer: 'ul' + + + ui: + loadMore: '.js-issues-more' + + + itemViewOptions: (_, index) -> + app: @options.app + index: index + + + collectionEvents: + 'reset': 'scrollToTop' + + + initialize: -> + @loadMoreThrottled = _.throttle @loadMore, 1000 + @listenTo @options.app.state, 'change:maxResultsReached', @toggleLoadMore + @listenTo @options.app.state, 'change:selectedIndex', @scrollToIssue + @bindShortcuts() + + + onClose: -> + @unbindScrollEvents() + + + toggleLoadMore: -> + @ui.loadMore.toggle !@options.app.state.get 'maxResultsReached' + + + bindScrollEvents: -> + $(window).on 'scroll.issues-workspace-list', (=> @onScroll()) + + + unbindScrollEvents: -> + $(window).off 'scroll.issues-workspace-list' + + + bindShortcuts: -> + key 'return', 'list', => + selectedIssue = @collection.at @options.app.state.get 'selectedIndex' + @options.app.controller.showComponentViewer selectedIssue + return false + + + loadMore: -> + unless @options.app.state.get 'maxResultsReached' + @unbindScrollEvents() + @options.app.controller.fetchNextPage().done => @bindScrollEvents() + + + onScroll: -> + if $(window).scrollTop() + $(window).height() >= @ui.loadMore.offset().top + @loadMoreThrottled() + + + scrollToTop: -> + @$el.scrollParent().scrollTop 0 + + + scrollToIssue: -> + selectedIssue = @collection.at @options.app.state.get 'selectedIndex' + selectedIssueView = @children.findByModel selectedIssue + viewTop = selectedIssueView.$el.offset().top + viewBottom = viewTop + selectedIssueView.$el.outerHeight() + windowTop = $(window).scrollTop() + windowBottom = windowTop + $(window).height() + if viewTop < windowTop + $(window).scrollTop viewTop - TOP_OFFSET + if viewBottom > windowBottom + $(window).scrollTop $(window).scrollTop() - windowBottom + viewBottom + BOTTOM_OFFSET + + diff --git a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-facets.hbs b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-facets.hbs index d2ea9d4fd56..c274ffff513 100644 --- a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-facets.hbs +++ b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-facets.hbs @@ -14,9 +14,9 @@ <div class="navigator-facets-list-item-options"> {{#each values}} {{#if count}} - <a class="navigator-facets-list-item-option" data-key="{{val}}" data-property="{{../property}}"> - <span class="navigator-facets-list-item-option-name">{{text}}</span> - <span class="navigator-facets-list-item-option-stat">{{count}}</span> + <a class="facet" data-key="{{val}}" data-property="{{../property}}"> + <span class="facet-name">{{text}}</span> + <span class="facet-stat">{{count}}</span> </a> {{/if}} {{/each}} diff --git a/server/sonar-web/src/main/hbs/component-viewer/cw-layout.hbs b/server/sonar-web/src/main/hbs/component-viewer/cw-layout.hbs index a010e4c4803..088eadcf56b 100644 --- a/server/sonar-web/src/main/hbs/component-viewer/cw-layout.hbs +++ b/server/sonar-web/src/main/hbs/component-viewer/cw-layout.hbs @@ -1,3 +1,3 @@ <div class="component-viewer-header"></div> <div class="component-viewer-workspace"></div> -<div class="component-viewer-source"></div>
\ No newline at end of file +<div class="component-viewer-source code-source"></div> diff --git a/server/sonar-web/src/main/hbs/issues/filter-bar.hbs b/server/sonar-web/src/main/hbs/issues-old/filter-bar.hbs index 4f1907964c7..4f1907964c7 100644 --- a/server/sonar-web/src/main/hbs/issues/filter-bar.hbs +++ b/server/sonar-web/src/main/hbs/issues-old/filter-bar.hbs diff --git a/server/sonar-web/src/main/hbs/issues/issue-detail.hbs b/server/sonar-web/src/main/hbs/issues-old/issue-detail.hbs index c746f72148c..c746f72148c 100644 --- a/server/sonar-web/src/main/hbs/issues/issue-detail.hbs +++ b/server/sonar-web/src/main/hbs/issues-old/issue-detail.hbs diff --git a/server/sonar-web/src/main/hbs/issues/issues-actions.hbs b/server/sonar-web/src/main/hbs/issues-old/issues-actions.hbs index 351d6d4d25c..351d6d4d25c 100644 --- a/server/sonar-web/src/main/hbs/issues/issues-actions.hbs +++ b/server/sonar-web/src/main/hbs/issues-old/issues-actions.hbs diff --git a/server/sonar-web/src/main/hbs/issues/issues-details-favorite-filter.hbs b/server/sonar-web/src/main/hbs/issues-old/issues-details-favorite-filter.hbs index 84c265857f0..84c265857f0 100644 --- a/server/sonar-web/src/main/hbs/issues/issues-details-favorite-filter.hbs +++ b/server/sonar-web/src/main/hbs/issues-old/issues-details-favorite-filter.hbs diff --git a/server/sonar-web/src/main/hbs/issues/issues-header.hbs b/server/sonar-web/src/main/hbs/issues-old/issues-header.hbs index bb44852c45e..bb44852c45e 100644 --- a/server/sonar-web/src/main/hbs/issues/issues-header.hbs +++ b/server/sonar-web/src/main/hbs/issues-old/issues-header.hbs diff --git a/server/sonar-web/src/main/hbs/issues/issues.hbs b/server/sonar-web/src/main/hbs/issues-old/issues.hbs index 50dba468b50..50dba468b50 100644 --- a/server/sonar-web/src/main/hbs/issues/issues.hbs +++ b/server/sonar-web/src/main/hbs/issues-old/issues.hbs diff --git a/server/sonar-web/src/main/hbs/issues/no-issues.hbs b/server/sonar-web/src/main/hbs/issues-old/no-issues.hbs index 33ec1914d28..33ec1914d28 100644 --- a/server/sonar-web/src/main/hbs/issues/no-issues.hbs +++ b/server/sonar-web/src/main/hbs/issues-old/no-issues.hbs diff --git a/server/sonar-web/src/main/hbs/issues/facets/_issues-facet-header.hbs b/server/sonar-web/src/main/hbs/issues/facets/_issues-facet-header.hbs new file mode 100644 index 00000000000..9b59a846155 --- /dev/null +++ b/server/sonar-web/src/main/hbs/issues/facets/_issues-facet-header.hbs @@ -0,0 +1,4 @@ +<a class="issues-facet-header js-issues-facet-toggle"> + <i class="icon-checkbox {{#if enabled}}icon-checkbox-checked{{/if}}"></i> + {{t 'issues.facet' property}} +</a> diff --git a/server/sonar-web/src/main/hbs/issues/facets/issues-assignee-facet.hbs b/server/sonar-web/src/main/hbs/issues/facets/issues-assignee-facet.hbs new file mode 100644 index 00000000000..835b6a5b4a4 --- /dev/null +++ b/server/sonar-web/src/main/hbs/issues/facets/issues-assignee-facet.hbs @@ -0,0 +1,18 @@ +{{> '_issues-facet-header'}} + +<div class="issues-facet-list"> + {{#each values}} + {{#eq val ''}} + {{! unassigned }} + <a class="facet issues-facet js-issues-facet" data-unassigned> + <span class="facet-name">Unassigned</span> + <span class="facet-stat">{{count}}</span> + </a> + {{else}} + <a class="facet issues-facet js-issues-facet" data-value="{{val}}"> + <span class="facet-name">{{label}}</span> + <span class="facet-stat">{{count}}</span> + </a> + {{/eq}} + {{/each}} +</div> diff --git a/server/sonar-web/src/main/hbs/issues/facets/issues-base-facet.hbs b/server/sonar-web/src/main/hbs/issues/facets/issues-base-facet.hbs new file mode 100644 index 00000000000..0c2bf496a7a --- /dev/null +++ b/server/sonar-web/src/main/hbs/issues/facets/issues-base-facet.hbs @@ -0,0 +1,10 @@ +{{> '_issues-facet-header'}} + +<div class="issues-facet-list"> + {{#each values}} + <a class="facet issues-facet js-issues-facet" data-value="{{val}}"> + <span class="facet-name">{{default label val}}</span> + <span class="facet-stat">{{count}}</span> + </a> + {{/each}} +</div> diff --git a/server/sonar-web/src/main/hbs/issues/facets/issues-creation-date-facet.hbs b/server/sonar-web/src/main/hbs/issues/facets/issues-creation-date-facet.hbs new file mode 100644 index 00000000000..1d90cb2d139 --- /dev/null +++ b/server/sonar-web/src/main/hbs/issues/facets/issues-creation-date-facet.hbs @@ -0,0 +1,9 @@ +{{> '_issues-facet-header'}} + +<div class="issues-facet-container justify"> + <input type="text" class="issues-facet-input" name="createdAfter" placeholder="From"> + to + <input type="text" class="issues-facet-input" name="createdBefore" placeholder="To"> +</div> + +<input type="hidden" name="createdAt"> diff --git a/server/sonar-web/src/main/hbs/issues/facets/issues-resolution-facet.hbs b/server/sonar-web/src/main/hbs/issues/facets/issues-resolution-facet.hbs new file mode 100644 index 00000000000..2268710a51a --- /dev/null +++ b/server/sonar-web/src/main/hbs/issues/facets/issues-resolution-facet.hbs @@ -0,0 +1,18 @@ +{{> '_issues-facet-header'}} + +<div class="issues-facet-list"> + {{#each values}} + {{#eq val ''}} + {{! unresolved }} + <a class="facet issues-facet issues-facet-half js-issues-facet" data-unresolved> + <span class="facet-name">Unresolved</span> + <span class="facet-stat">{{count}}</span> + </a> + {{else}} + <a class="facet issues-facet issues-facet-half js-issues-facet" data-value="{{val}}"> + <span class="facet-name">{{t 'issue.resolution' val}}</span> + <span class="facet-stat">{{count}}</span> + </a> + {{/eq}} + {{/each}} +</div> diff --git a/server/sonar-web/src/main/hbs/issues/facets/issues-severity-facet.hbs b/server/sonar-web/src/main/hbs/issues/facets/issues-severity-facet.hbs new file mode 100644 index 00000000000..66deef1086e --- /dev/null +++ b/server/sonar-web/src/main/hbs/issues/facets/issues-severity-facet.hbs @@ -0,0 +1,10 @@ +{{> '_issues-facet-header'}} + +<div class="issues-facet-list"> + {{#each values}} + <a class="facet issues-facet issues-facet-half js-issues-facet" data-value="{{val}}"> + <span class="facet-name">{{severityIcon val}} {{t 'severity' val}}</span> + <span class="facet-stat">{{count}}</span> + </a> + {{/each}} +</div> diff --git a/server/sonar-web/src/main/hbs/issues/facets/issues-status-facet.hbs b/server/sonar-web/src/main/hbs/issues/facets/issues-status-facet.hbs new file mode 100644 index 00000000000..800b7cdb679 --- /dev/null +++ b/server/sonar-web/src/main/hbs/issues/facets/issues-status-facet.hbs @@ -0,0 +1,10 @@ +{{> '_issues-facet-header'}} + +<div class="issues-facet-list"> + {{#each values}} + <a class="facet issues-facet issues-facet-half js-issues-facet" data-value="{{val}}"> + <span class="facet-name">{{statusIcon val}} {{t 'issue.status' val}}</span> + <span class="facet-stat">{{count}}</span> + </a> + {{/each}} +</div> diff --git a/server/sonar-web/src/main/hbs/issues/issues-component-viewer.hbs b/server/sonar-web/src/main/hbs/issues/issues-component-viewer.hbs new file mode 100644 index 00000000000..1fe6686a28d --- /dev/null +++ b/server/sonar-web/src/main/hbs/issues/issues-component-viewer.hbs @@ -0,0 +1,18 @@ +<div class="issues-workspace-component-viewer-code code-source"> + {{#if hasSourceBefore}} + <i class="spinner js-component-viewer-source-before"></i> + {{/if}} + + <table class="code"> + {{#each source}} + <tr class="row" data-line-number="{{lineNumber}}"> + <td class="stat ">{{lineNumber}}</td> + <td class="line"><pre>{{{code}}}</pre></td> + </tr> + {{/each}} + </table> + + {{#if hasSourceAfter}} + <i class="spinner js-component-viewer-source-after"></i> + {{/if}} +</div> diff --git a/server/sonar-web/src/main/hbs/issues/issues-filters.hbs b/server/sonar-web/src/main/hbs/issues/issues-filters.hbs new file mode 100644 index 00000000000..7009e8b38cc --- /dev/null +++ b/server/sonar-web/src/main/hbs/issues/issues-filters.hbs @@ -0,0 +1,34 @@ +<div class="issues-filters-list"> + {{#each items}} + <a class="issues-filters-button issues-filters-filter js-issues-filter" data-id="{{id}}">{{name}}</a> + <br> + {{/each}} +</div> + +<div class="issues-filters-header"> + {{#notEmpty items}} + <a class="issues-filters-button issues-filters-header-filters js-issues-toggle-filters"> + {{default filter.name "Saved Filters"}} + </a> + {{/notEmpty}} + + <span class="issues-filters-header-actions"> + <a class="issues-filters-button js-issues-new-search">New Search</a> + + {{#if state.canManageFilters}} + {{#if filter.canModify}} + {{#if state.changed}} + <a class="issues-filters-button js-issues-save">{{t 'save'}}</a> + {{/if}} + {{/if}} + + {{#unless filter.id}}<a class="issues-filters-button js-issues-save-as">{{t 'save_as'}}</a>{{/unless}} + + {{#if filter.id}}<a class="issues-filters-button js-issues-copy">{{t 'copy'}}</a>{{/if}} + + {{#if filter.canModify}} + {{#if filter.id}}<a class="issues-filters-button js-issues-edit">{{t 'edit'}}</a>{{/if}} + {{/if}} + {{/if}} + </span> +</div> diff --git a/server/sonar-web/src/main/hbs/issues/issues-issue.hbs b/server/sonar-web/src/main/hbs/issues/issues-issue.hbs new file mode 100644 index 00000000000..7027e07ce79 --- /dev/null +++ b/server/sonar-web/src/main/hbs/issues/issues-issue.hbs @@ -0,0 +1,142 @@ +<div class="code-issue code-issue-modern code-issue-collapsed" data-issue-key="{{key}}" data-issue-component="{{component}}" data-issue-rule="{{rule}}"> + <div class="code-issue-name code-issue-toggle"> + <div class="code-issue-name-rule"> + {{severityIcon severity}} <span class="rulename">{{message}}</span> + </div> + + <div class="code-issue-permalink"> + <a target="_blank" href="{{permalink}}"> + <i class="icon-link"></i> + </a> + </div> + </div> + + + <ul class="code-issue-actions code-issue-list"> + {{#inArray actions "comment"}} + <li> + <a id="issue-comment" class="link-action">{{t 'issue.comment.formlink' }}</a> + </li> + {{/inArray}} + + + <li> + {{statusIcon status}}{{t 'issue.status' status}} + {{#if resolution}}({{t 'issue.resolution' resolution}}){{/if}} + + {{#ifNotEmpty transitions}} + {{#each transitions}} + <a class="link-action issue-transition spacer-left" data-transition="{{this}}">{{t 'issue.transition' this}}</a> + {{/each}} + {{/ifNotEmpty}} + </li> + + + {{#inArray actions "assign"}} + <li> + {{#if assigneeName}} + <a id="issue-assign" class="link-action">{{t 'assigned_to'}}</a> {{assigneeName}}</li> + {{else}} + <a id="issue-assign" class="link-action">{{t 'issue.assign.formlink' }}</a> + {{#inArray actions "assign_to_me"}} + [<a id="issue-assign-to-me" class="link-action">{{t 'issue.assign.to_me' }}</a>] + {{/inArray}} + {{/if}} + </li> + {{else}} + {{#if assigneeName}} + <li>{{t 'assigned_to'}} <strong>{{assigneeName}}</strong></li> + {{/if}} + {{/inArray}} + + + {{#inArray actions "plan"}} + <li> + {{#if actionPlanName}} + <a id="issue-plan" class="link-action">{{t 'issue.planned_for'}}</a> {{actionPlanName}} + {{else}} + <a id="issue-plan" class="link-action">{{t 'issue.do_plan'}}</a> + {{/if}} + </li> + {{else}} + {{#if actionPlanName}} + <li>{{t 'issue.planned_for'}} <strong>{{actionPlanName}}</strong></li> + {{/if}} + {{/inArray}} + + + {{#ifHasExtraActions actions}} + <li> + <div class="dropdown"> + <a class="link-action link-more" onclick="showDropdownMenuOnElement($j(this).next('.dropdown-menu')); return false;">{{t 'more_actions'}}</a> + <ul style="display: none" class="dropdown-menu"> + {{#inArray actions "set_severity"}} + <li> + <a id="issue-set-severity" class="link-action spacer-right">{{t "issue.set_severity"}}</a> + </li> + {{/inArray}} + {{#pluginActions actions}} + <li> + <a class="link-action spacer-right issue-action" data-action="{{this}}">{{t "issue.action" this "formlink"}}</a> + </li> + {{/pluginActions}} + </ul> + </div> + </li> + {{else}} + {{#inArray actions "set_severity"}} + <li> + <a id="issue-set-severity" class="link-action">{{t "issue.set_severity"}}</a> + </li> + {{/inArray}} + {{/ifHasExtraActions}} + + {{#if debt}} + <li>{{t 'issue.technical_debt_short'}}: {{debt}}</li> + {{/if}} + + {{#if reporterName}}<li>{{t 'reporter'}}: {{reporterName}}</li>{{/if}} + {{#if author}}<li>{{t 'author'}}: {{author}}</li>{{/if}} + </ul> + + <div class="code-issue-form" style="display: none;"></div> + + + <div class="code-issue-details"> + <ul class="code-issue-tabs"> + <li> + <a class="js-tab-link" href="#tab-issue-rule">{{t 'rule'}}</a> + </li> + <li> + <a class="js-tab-link" href="#tab-issue-changelog">{{t 'changelog'}}</a> + </li> + </ul> + + <div id="tab-issue-rule" class="js-tab"> + <div class="rule-desc"></div> + </div> + + <div id="tab-issue-changelog" class="js-tab"></div> + </div> + + + <div class="code-issue-comments"> + {{#each comments}} + <div class="code-issue-comment" data-comment-key="{{key}}"> + <h4> + <i class="icon-comment"></i> + <b>{{userName}}</b> + ({{fromNow createdAt}}) + + {{#if updatable}} + + <a class="link-action issue-comment-edit">{{t 'edit'}}</a> + <a class="link-action link-red spacer-right issue-comment-delete" + data-confirm-msg="{{t 'issue.comment.delete_confirm_message'}}">{{t 'delete'}}</a> + {{/if}} + </h4> + <div class="markdown">{{{show html htmlText}}}</div> + </div> + {{/each}} + </div> +</div> diff --git a/server/sonar-web/src/main/hbs/issues/issues-layout.hbs b/server/sonar-web/src/main/hbs/issues/issues-layout.hbs new file mode 100644 index 00000000000..dfa704a6fb8 --- /dev/null +++ b/server/sonar-web/src/main/hbs/issues/issues-layout.hbs @@ -0,0 +1,10 @@ +<div class="issues-side"> + <div class="issues-filters"></div> + <div class="issues-facets"></div> +</div> + +<div class="issues-workspace"> + <div class="issues-workspace-header issues-header"></div> + <div class="issues-workspace-list"></div> + <div class="issues-workspace-component-viewer"></div> +</div> diff --git a/server/sonar-web/src/main/hbs/issues/issues-workspace-header.hbs b/server/sonar-web/src/main/hbs/issues/issues-workspace-header.hbs new file mode 100644 index 00000000000..e2c874348eb --- /dev/null +++ b/server/sonar-web/src/main/hbs/issues/issues-workspace-header.hbs @@ -0,0 +1,21 @@ +{{#if state.component}} + <span class="issues-header-item"> + <a class="js-back">Return to List</a> + </span> + + <span class="issues-header-item"> + {{state.component.projectName}} {{state.component.name}} + </span> +{{/if}} + +<span class="issues-header-item issues-header-nav"> + <a class="icon-bulk-change js-issues-bulk-change"></a> +</span> + +{{#if state.total}} + <span class="issues-header-item issues-header-nav"> + <a class="js-issues-prev icon-prev"></a> + <span class="current">{{sum state.selectedIndex 1}} / {{state.total}}</span> + <a class="js-issues-next icon-next"></a> + </span> +{{/if}} diff --git a/server/sonar-web/src/main/hbs/issues/issues-workspace-list-item.hbs b/server/sonar-web/src/main/hbs/issues/issues-workspace-list-item.hbs new file mode 100644 index 00000000000..35fe12446aa --- /dev/null +++ b/server/sonar-web/src/main/hbs/issues/issues-workspace-list-item.hbs @@ -0,0 +1,24 @@ +<div class="issue-box-component subtitle"> + <a href="{{dashboardUrl project}}">{{projectLongName}}</a> + + <a href="{{dashboardUrl component}}">{{componentLongName}}</a> +</div> + +{{#if source}} + <div class="issue-box-snippet code-source js-issues-snippet"> + <table class="code"> + <tr class="row"> + {{#if scmAuthor}} + <td class="stat scm"> + <span class="scm-date">{{scmDate}}</span> + <span class="scm-author" title="{{scmAuthor}}">{{scmAuthor}}</span> + </td> + {{/if}} + <td class="stat lid">{{line}}</td> + <td class="line issue"><pre>{{{source}}}</pre></td> + </tr> + </table> + </div> +{{/if}} + +<div class="issue-box-details"></div> diff --git a/server/sonar-web/src/main/hbs/issues/issues-workspace-list.hbs b/server/sonar-web/src/main/hbs/issues/issues-workspace-list.hbs new file mode 100644 index 00000000000..74f2f7acff8 --- /dev/null +++ b/server/sonar-web/src/main/hbs/issues/issues-workspace-list.hbs @@ -0,0 +1,5 @@ +<ul></ul> + +<div class="issues-workspace-list-more js-issues-more"> + <i class="spinner"></i> +</div> diff --git a/server/sonar-web/src/main/js/common/handlebars-extensions.js b/server/sonar-web/src/main/js/common/handlebars-extensions.js index 65a211bfc74..e927143b7c1 100644 --- a/server/sonar-web/src/main/js/common/handlebars-extensions.js +++ b/server/sonar-web/src/main/js/common/handlebars-extensions.js @@ -206,9 +206,9 @@ define(['handlebars'], function (Handlebars) { }); Handlebars.registerHelper('dashboardUrl', function(componentKey, componentQualifier) { - var url = baseUrl + '/dashboard/index/' + decodeURIComponent(componentKey); + var url = baseUrl + '/dashboard/index?id=' + encodeURIComponent(componentKey); if (componentQualifier === 'FIL' || componentQualifier === 'CLA') { - url += '?metric=sqale_index'; + url += '&metric=sqale_index'; } return url; }); @@ -268,6 +268,14 @@ define(['handlebars'], function (Handlebars) { } }); + Handlebars.registerHelper('withLast', function(list, options) { + if (list && list.length > 0) { + return options.fn(list[list.length - 1]); + } else { + return ''; + } + }); + Handlebars.registerHelper('withoutFirst', function(list, options) { if (list && list.length > 1) { return list.slice(1).reduce(function(prev, current) { diff --git a/server/sonar-web/src/main/js/issues/app.js b/server/sonar-web/src/main/js/issues-old/app.js index 63b329ead76..2d45ffdce75 100644 --- a/server/sonar-web/src/main/js/issues/app.js +++ b/server/sonar-web/src/main/js/issues-old/app.js @@ -25,7 +25,7 @@ requirejs.config({ requirejs( [ 'backbone', 'backbone.marionette', 'handlebars', - 'issues/extra', + 'issues-old/extra', 'navigator/filters/filter-bar', 'navigator/filters/base-filters', 'navigator/filters/checkbox-filters', diff --git a/server/sonar-web/src/main/js/issues/extra.js b/server/sonar-web/src/main/js/issues-old/extra.js index 5aa0662a43e..ba1c35a7642 100644 --- a/server/sonar-web/src/main/js/issues/extra.js +++ b/server/sonar-web/src/main/js/issues-old/extra.js @@ -7,7 +7,7 @@ define( 'navigator/filters/favorite-filters', 'navigator/filters/read-only-filters', 'component-viewer/main', - 'templates/issues' + 'templates/issues-old' ], function (Backbone, Marionette, FilterBarView, BaseFilters, FavoriteFiltersModule, ReadOnlyFilterView, ComponentViewer, Templates) { diff --git a/server/sonar-web/src/main/less/component-viewer-source-colorizer.less b/server/sonar-web/src/main/less/component-viewer-source-colorizer.less index e11e0e1c402..c53557a89c5 100644 --- a/server/sonar-web/src/main/less/component-viewer-source-colorizer.less +++ b/server/sonar-web/src/main/less/component-viewer-source-colorizer.less @@ -1,49 +1,36 @@ @import (reference) 'variables'; @import (reference) 'mixins'; -.component-viewer-source { - - .code pre { - padding: 0; - font-family: 'Source Code Pro', monospace; - font-size: 12px; - line-height: 16px; - } - - /* constants */ - .code .c { - font-style: normal; - } - - /* javadoc */ - .code .j { - font-style: normal; - } - - /* keyword */ - .code .k { - color: saturate(@darkBlue, 50%); - font-weight: 600; - } +.code pre { + padding: 0; + font-family: 'Source Code Pro', monospace; + font-size: 12px; + line-height: 16px; +} - /* string */ - .code .s { - color: saturate(@red, 0%); - font-weight: normal; - } +/* constants */ +.code .c { + font-style: normal; +} - /* preprocessing directive */ - .code .p { - font-weight: normal; - } +/* javadoc */ +.code .j { + font-style: normal; +} - .sym { - @symColor: darken(saturate(@green, 10%), 15%); - border-bottom: 1px dotted @symColor; - .trans(color); +/* keyword */ +.code .k { + color: saturate(@darkBlue, 50%); + font-weight: 600; +} - &:hover, - &.highlighted { color: @symColor; } - } +/* string */ +.code .s { + color: saturate(@red, 0%); + font-weight: normal; +} +/* preprocessing directive */ +.code .p { + font-weight: normal; } diff --git a/server/sonar-web/src/main/less/component-viewer.less b/server/sonar-web/src/main/less/component-viewer.less index a24fcd828ab..2533fc45871 100644 --- a/server/sonar-web/src/main/less/component-viewer.less +++ b/server/sonar-web/src/main/less/component-viewer.less @@ -107,7 +107,6 @@ &:hover { background-color: @barBorderColor; } } - .component-viewer-source { position: relative; float: left; @@ -117,27 +116,7 @@ overflow: auto; } - .code { - width: 100%; - border: 1px solid @barBorderColor; - } - .code th { - height: 30px; - .box-sizing(border-box); - background-color: @barBackgroundColor; - - &.stat { - padding-top: 4px; - padding-bottom: 4px; - border-left: none; - border-right: none; - border-bottom: 1px solid @barBorderColor; - } - - &.lid { - border-right: 1px solid @barBorderColor; - } &.settings { line-height: 22px; @@ -160,42 +139,6 @@ } } - .code .row:hover { - td.stat { background-color: @barBorderColor; } - td.line { background-color: @barBackgroundColor; } - } - - .code .row-highlighted, - .code .row-highlighted:hover { - td.stat { background-color: #fdf190; } - td.line, .code-issues, .code-issue { background-color: #fff8c2; } - } - - .code td.line { - width: 100%; - padding: 1px 5px; - } - - .code .stat { - vertical-align: top; - min-width: 12px; - padding: 1px 5px; - background-color: @barBackgroundColor; - color: #888; - font-size: 11px; - line-height: 16px; - text-align: right; - cursor: default; - white-space: nowrap; - } - - .code .lid { - min-width: 18px; - padding-left: 10px; - padding-right: 10px; - cursor: pointer; - } - .code .coverage-tests { cursor: pointer; } @@ -231,41 +174,6 @@ background-color: @barBackgroundColor; } - .code .issue > pre { - display: inline-block; - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAGCAYAAAAPDoR2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1M0M2Rjk4M0M3QUYxMUUzODkzRUREMUM5OTNDMjY4QSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1M0M2Rjk4NEM3QUYxMUUzODkzRUREMUM5OTNDMjY4QSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjUzQzZGOTgxQzdBRjExRTM4OTNFREQxQzk5M0MyNjhBIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjUzQzZGOTgyQzdBRjExRTM4OTNFREQxQzk5M0MyNjhBIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+bcqJtQAAAEhJREFUeNpi+G+swwDGDAwgbAWlwZiJAQFCgfgwEIfDRaC67ID4NRDnQ2kQnwFZwgFqnANMAQOUYY9sF0wBiCGH5CBkrAgQYACuWi4sSGW8yAAAAABJRU5ErkJggg==); - background-repeat: repeat-x; - background-size: 4px; - background-position: bottom; - } - - .code .scm { - line-height: 16px; - padding-top: 0; - padding-bottom: 0; - text-align: left; - - .scm-date { - display: inline-block; - vertical-align: middle; - padding: 2px 4px; - line-height: 1; - background-color: @barBorderColor; - } - - .scm-author { - display: inline-block; - vertical-align: middle; - max-width: 120px; - padding: 2px 0; - line-height: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - } - .code .stat { &.coverage-green { diff --git a/server/sonar-web/src/main/less/components.less b/server/sonar-web/src/main/less/components.less new file mode 100644 index 00000000000..68d0e96a244 --- /dev/null +++ b/server/sonar-web/src/main/less/components.less @@ -0,0 +1,3 @@ +@import "components/component-issues"; +@import "components/code-source"; +@import "components/facets"; diff --git a/server/sonar-web/src/main/less/components/code-source.less b/server/sonar-web/src/main/less/components/code-source.less new file mode 100644 index 00000000000..1e4fd508693 --- /dev/null +++ b/server/sonar-web/src/main/less/components/code-source.less @@ -0,0 +1,92 @@ +@import (reference) "../mixins"; +@import (reference) "../variables"; + +.code-source { + + .code { + width: 100%; + border: 1px solid @barBorderColor; + } + + .code th { + height: 30px; + .box-sizing(border-box); + background-color: @barBackgroundColor; + + &.lid { + border-right: 1px solid @barBorderColor; + } + + &.stat { + padding-top: 4px; + padding-bottom: 4px; + border-left: none; + border-right: none; + border-bottom: 1px solid @barBorderColor; + } + } + + .code .stat { + vertical-align: top; + min-width: 12px; + padding: 1px 5px; + background-color: @barBackgroundColor; + color: #888; + font-size: 11px; + line-height: 16px; + text-align: right; + cursor: default; + white-space: nowrap; + } + + .code .lid { + min-width: 18px; + padding-left: 10px; + padding-right: 10px; + cursor: pointer; + } + + .code .scm { + line-height: 16px; + padding-top: 0; + padding-bottom: 0; + text-align: left; + + .scm-date { + display: inline-block; + vertical-align: middle; + padding: 2px 4px; + line-height: 1; + background-color: @barBorderColor; + } + + .scm-author { + display: inline-block; + vertical-align: middle; + max-width: 120px; + padding: 2px 0; + line-height: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + } + + .code .row:hover { + td.stat { background-color: @barBorderColor; } + td.line { background-color: @barBackgroundColor; } + } + + .code .row-highlighted, + .code .row-highlighted:hover { + td.stat { background-color: #fdf190; } + td.line, .code-issues, .code-issue { background-color: #fff8c2; } + } + + .code td.line { + width: 100%; + padding: 1px 5px; + } + +} diff --git a/server/sonar-web/src/main/less/components/component-issues.less b/server/sonar-web/src/main/less/components/component-issues.less new file mode 100644 index 00000000000..9b07304a6e5 --- /dev/null +++ b/server/sonar-web/src/main/less/components/component-issues.less @@ -0,0 +1,24 @@ +@import (reference) "../variables"; +@import (reference) "../mixins"; + +.issues-distribution-bar { + display: inline-block; + vertical-align: middle; + width: 45px; +} + +.issues-distribution-bar-item { + display: block; + min-width: 1px; + height: 3px; + + &.s-blocker { background-color: @severityBlockerColor; } + &.s-critical { background-color: @severityCriticalColor; } + &.s-major { background-color: @severityMajorColor; } + &.s-minor { background-color: @severityMinorColor; } + &.s-info { background-color: @severityInfoColor; } +} + +.issues-distribution-bar-item + .issues-distribution-bar-item { + margin-top: 1px; +} diff --git a/server/sonar-web/src/main/less/components/facets.less b/server/sonar-web/src/main/less/components/facets.less new file mode 100644 index 00000000000..ec0a11412a0 --- /dev/null +++ b/server/sonar-web/src/main/less/components/facets.less @@ -0,0 +1,52 @@ +@import (reference) "../mixins"; +@import (reference) "../variables"; + +.facet { + display: inline-block; + vertical-align: middle; + line-height: 1; + margin: 0 6px 6px 6px; + padding: 4px 5px; + border: 1px solid @barBorderColor; + border-radius: 2px; + background-color: lighten(@barBackgroundColor, 2%); + font-size: 0; + font-weight: 300; + cursor: pointer; + white-space: nowrap; + .trans(none); + + &:hover { + border: 1px solid @blue; + text-decoration: none; + } + + &.active { + border: 1px solid @blue; + background-color: @lightBlue; + text-decoration: none; + + .facet-name { + background-color: @lightBlue; + } + + .facet-stat { + border-color: @blue; + background-color: @lightBlue; + } + } +} + +.facet-name { + background-color: lighten(@barBackgroundColor, 2%); + font-size: @smallFontSize; +} + +.facet-stat { + margin-left: 5px; + padding-left: 5px; + border-left: 1px solid @barBorderColor; + background-color: lighten(@barBackgroundColor, 2%); + color: @secondFontColor; + font-size: @smallFontSize; +} diff --git a/server/sonar-web/src/main/less/icons.less b/server/sonar-web/src/main/less/icons.less index fa174f8b19b..6b60e2ea4a2 100644 --- a/server/sonar-web/src/main/less/icons.less +++ b/server/sonar-web/src/main/less/icons.less @@ -298,7 +298,7 @@ a[class^="icon-"], a[class*=" icon-"] { } .icon-bulk-change:before { content: "\f085"; - font-size: @iconFontSize; + font-size: @iconSmallFontSize; } .icon-arrow-down:before { content: "\f0d7"; @@ -412,6 +412,10 @@ a[class^="icon-"], a[class*=" icon-"] { color: @red; font-size: @iconFontSize; } +.icon-close:before { + content: "\f00d"; + font-size: @iconFontSize; +} .icon-compare:before { content: "\f0c5"; font-size: @iconSmallFontSize; @@ -521,6 +525,18 @@ a[class^="icon-"], a[class*=" icon-"] { color: #999; font-size: @iconSmallFontSize; } +.icon-filters:before { + content: "\f039"; + font-size: @iconSmallFontSize; +} +.icon-next:before { + content: "\f0d7"; + font-size: @iconFontSize; +} +.icon-prev:before { + content: "\f0d8"; + font-size: @iconFontSize; +} /* diff --git a/server/sonar-web/src/main/less/issues.less b/server/sonar-web/src/main/less/issues.less new file mode 100644 index 00000000000..4a35c249d81 --- /dev/null +++ b/server/sonar-web/src/main/less/issues.less @@ -0,0 +1,302 @@ +@import (reference) "variables"; +@import (reference) "mixins"; +@import (reference) "ui"; + +@sideWidth: 300px; + + +.issues { + + &.sticky { + + .issues-workspace-header { + position: fixed; + z-index: 4; + top: 0; + left: @sideWidth; + right: 0px; + } + + .issues-workspace-list, + .issues-workspace-component-viewer { + padding-top: 37px; + } + + .issues-side { + position: fixed; + z-index: 4; + top: 0; + bottom: 0; + overflow-y: scroll; + } + + } +} + +.issues-side { + position: fixed; + width: @sideWidth; + top: 30px; left: 0; bottom: 0; + .box-sizing(border-box); + border-right: 1px solid @barBorderColor; + background-color: @barBorderColor; + color: #fff; + overflow-x: hidden; +} + +.issues-facet-box { + border-top: 1px solid @barBorderColor; + background-color: @barBackgroundColor; + font-size: @baseFontSize; +} + +.issues-facet-box-collapsed { + background-color: transparent; + + .issues-facet-list, + .issues-facet-container { + display: none; + } + + .issues-facet-header { + color: @secondFontColor; + font-weight: 300; + } +} + +.issues-facet { + position: relative; + width: @sideWidth - 22px; + margin: 0 22px 1px 0; + border-color: transparent; + .box-sizing(border-box); + background-color: @barBackgroundColor; + white-space: normal; + overflow-x: hidden; + + .facet-name { + white-space: nowrap; + background-color: @barBackgroundColor; + color: @secondFontColor; + } + + .facet-stat { + position: absolute; + top: 0; right: 0; + padding: 4px 5px; + border: none; + background-color: @barBackgroundColor; + + &:before { + content: " "; + position: absolute; + top: 0; bottom: 0; right: 100%; + width: 10px; + background-image: linear-gradient(to right, rgba(255, 255, 255, 0), @barBackgroundColor 75%); + } + } + + &.active .facet-stat:before { + background-image: linear-gradient(to right, rgba(255, 255, 255, 0), @lightBlue 75%); + } +} + +.issues-facet-half { + width: @sideWidth / 2 - 22px; +} + +.issues-facet-header { + display: block; + padding: 6px 10px; + border-bottom: none; + color: @baseFontColor; + font-weight: 400; +} + +.issues-facet-list { + margin: 0 -22px 0 0; + padding: 0 10px 10px; + font-size: 0; +} + +.issues-facet-container { + margin-top: 6px; + padding: 0 10px 16px; +} + +.issues-facet-input { + width: @sideWidth * 0.4; +// .box-sizing(border-box); +} + +.issues-filters { + .clearfix; + padding: 8px 10px; + background-color: @barBackgroundColor; + color: @secondFontColor; + font-size: @smallFontSize; + font-weight: 300; +} + +.issues-filters-list { + display: none; + margin-bottom: 8px; + line-height: 1.5; +} + +.issues-filters-filter { + +} + +.issues-filters-header { + font-size: @smallFontSize; + font-weight: 300; +} + +.issues-filters-header-filters { + display: inline-block; + max-width: 50%; + .text-ellipsis; +} + +.issues-filters-header-actions { + float: right; +} + +.issues-filters-button { + border-bottom-color: @secondFontColor; + color: @secondFontColor; + font-size: @smallFontSize; + font-weight: 300; + .trans(color); + + &:hover, &:active { + color: #fff; + } + + &:focus {} +} + +.issues-workspace { + padding-left: @sideWidth; +} + +.issues-header { + .clearfix; + margin-bottom: 10px; + padding: 5px 0; + border: 1px solid @barBorderColor; + border-left: none; + background: @barBackgroundColor; + font-size: @smallFontSize; +} + +.issues-header-item { + display: inline-block; + margin: 0 10px; +} + +.issues-header-nav { + float: right; +} + +.issues-workspace-list { + padding: 0 10px; +} + +.issues-workspace-list-more { + margin-top: 10px; + padding: 5px 10px; + border: 1px solid @barBorderColor; + background: @barBackgroundColor; + text-align: center; +} + +.issues-workspace-component-viewer { + padding: 0 10px; + overflow-x: auto; +} + +.issues-workspace-component-viewer-header { + position: relative; +} + +.issues-workspace-component-viewer-close { + position: absolute; + top: 50%; + right: 10px; + margin-top: -8px; +} + +.issues-extended-view { + + .issues-workspace-list { + display: none; + } +} + +.issue-box { + .clearfix; + border: 1px solid @barBorderColor; + border-left: none; + overflow: hidden; + + &.selected { + background-color: #e4ecf3; + } +} + +.issue-box + .issue-box { + margin-top: 12px; +} + +.issue-box-component, +.issue-box-snippet { + border-left: 2px solid @red; +} + +.issue-box-details { + +} + +.code-issue-modern { + border-left: 2px solid @red; + + .code-issue-name, + .code-issue-actions, + .code-issue-details, + .code-issue-comment { + border: none; + background: transparent; + } + + &.highlighted { + background: #E4ECF3; + } +} + +.code-source .code-issue-modern { + margin: 5px 0; +} + +.issue-box-snippet { + padding: 3px 0; + overflow: hidden; + cursor: pointer; + + .code { + border: none; + } +} + +.issue-box-component { + padding: 5px; +} + +.code .issue > pre { + display: inline-block; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAGCAYAAAAPDoR2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1M0M2Rjk4M0M3QUYxMUUzODkzRUREMUM5OTNDMjY4QSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1M0M2Rjk4NEM3QUYxMUUzODkzRUREMUM5OTNDMjY4QSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjUzQzZGOTgxQzdBRjExRTM4OTNFREQxQzk5M0MyNjhBIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjUzQzZGOTgyQzdBRjExRTM4OTNFREQxQzk5M0MyNjhBIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+bcqJtQAAAEhJREFUeNpi+G+swwDGDAwgbAWlwZiJAQFCgfgwEIfDRaC67ID4NRDnQ2kQnwFZwgFqnANMAQOUYY9sF0wBiCGH5CBkrAgQYACuWi4sSGW8yAAAAABJRU5ErkJggg==); + background-repeat: repeat-x; + background-size: 4px; + background-position: bottom; +} diff --git a/server/sonar-web/src/main/less/navigator/base.less b/server/sonar-web/src/main/less/navigator/base.less index 1613a496c5e..200ab94be5a 100644 --- a/server/sonar-web/src/main/less/navigator/base.less +++ b/server/sonar-web/src/main/less/navigator/base.less @@ -255,49 +255,6 @@ overflow: hidden; } -.navigator-facets-list-item-option { - display: inline-block; - vertical-align: middle; - line-height: 1; - margin: 0 6px 6px 6px; - padding: 4px 5px; - border: 1px solid @barBorderColor; - border-radius: 2px; - background-color: lighten(@barBackgroundColor, 2%); - font-size: 0; - font-weight: 300; - cursor: pointer; - .trans; - - &:hover { - border: 1px solid @blue; - text-decoration: none; - } - - &.active { - border: 1px solid @blue; - background-color: @lightBlue; - text-decoration: none; - - .navigator-facets-list-item-option-stat { - border-color: @blue; - } - } -} - -.navigator-facets-list-item-option-name { - font-size: @smallFontSize; -} - -.navigator-facets-list-item-option-stat { - margin-left: 5px; - padding-left: 5px; - border-left: 1px solid @barBorderColor; - color: @navigatorFacetStatColor; - font-size: @smallFontSize; - .trans; -} - // Results diff --git a/server/sonar-web/src/main/less/style.less b/server/sonar-web/src/main/less/style.less index a0c6ec7f7ba..62af5c47cea 100644 --- a/server/sonar-web/src/main/less/style.less +++ b/server/sonar-web/src/main/less/style.less @@ -708,7 +708,7 @@ th.operations, td.operations { .rulename, .rulename a { color: #444; - font-weight: bold; + font-weight: 500; } .rulename a:hover { @@ -806,7 +806,6 @@ th.operations, td.operations { .code-issue { margin: 0; font-size: 12px; - padding: 0 0 10px 0; .trans(border-color); .error { diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issues2_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issues2_controller.rb new file mode 100644 index 00000000000..9cc713e267c --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/issues2_controller.rb @@ -0,0 +1,75 @@ +# +# 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. +# +require 'set' + +class Issues2Controller < ApplicationController + + SECTION=Navigation::SECTION_ISSUES2 + + before_filter :init_options + before_filter :load_fav_filters, :only => [:index, :search, :search2, :filter, :manage, :favourites, :toggle_fav] + + PAGE_SIZE = 100 + + # GET /issues/index + def index + redirect_to :action => 'search' + end + + # GET /issues/search + def search + + end + + private + + def init_options + @options_for_statuses = Internal.issues.listStatus().map { |s| [message('issue.status.' + s), s] } + @options_for_resolutions = Internal.issues.listResolutions().map { |s| [message('issue.resolution.' + s), s] } + end + + def load_fav_filters + @favourite_filters = Internal.issues.findFavouriteIssueFiltersForCurrentUser() if logged_in? + end + + def find_filter(id) + Internal.issues.findIssueFilter(id) + end + + def criteria_params + new_params = params.clone + new_params.delete('controller') + new_params.delete('action') + new_params + end + + def init_params + params['pageSize'] = PAGE_SIZE unless request.xhr? + end + + def issues_query_params_sanitized + Internal.issues.sanitizeFilterQuery(params).to_hash + end + + def issues_query_params_from_filter(filter) + Internal.issues.deserializeFilterQuery(filter).to_hash + end + +end diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/models/navigation.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/models/navigation.rb index 4b690d90178..7c709252329 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/models/navigation.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/models/navigation.rb @@ -32,6 +32,7 @@ class Navigation SECTION_RESOURCE_CONFIGURATION = Navigation.new('resource_configuration', true) SECTION_SESSION = Navigation.new('session', true) SECTION_ISSUES = Navigation.new('issues', false) + SECTION_ISSUES2 = Navigation.new('issues2', false) SECTION_MEASURES = Navigation.new('measures', false) SECTION_QUALITY_PROFILES = Navigation.new('quality_profiles', false) SECTION_QUALITY_GATES = Navigation.new('quality_gates', false) diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/search.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/search.html.erb index fac32ece75d..70dcece4ae5 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/search.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues/search.html.erb @@ -2,256 +2,6 @@ <script data-main="<%= ApplicationController.root_context -%>/js/issues/app" src="<%= ApplicationController.root_context -%>/js/require.js"></script> <% end %> -<div class="navigator"> - <div class="navigator-header"></div> - <div class="navigator-filters"></div> - - <div class="navigator-content"> - <div class="navigator-side"> - <div style="position:relative; overflow: visible; height: 100%;"> - <div class="navigator-actions"></div> - <div class="navigator-results"></div> - <a class="navigator-resizer"><i class="icon-resizer"></i></a> - </div> - </div> - <div class="navigator-main"> - <div class="navigator-notes"><%= message('issue_filter.max_results_reached', :params => 10000) -%></div> - <div class="navigator-details"></div> - </div> - </div> +<div class="issues"> + <i class="spinner"></i> </div> - - -<script> - window.SS = {}; - - _.extend(window.SS, { - currentUser: '<%= current_user.login if current_user -%>', - currentUserName: '<%= current_user.name if current_user -%>', - severities: <%= RulesConfigurationController::RULE_PRIORITIES.to_json.html_safe -%>, - statuses: <%= @options_for_statuses.to_json.html_safe -%>, - resolutions: <%= @options_for_resolutions.to_json.html_safe -%>, - favorites: <%= render :partial => 'issues/filter_favourites' -%>, - languages: {<% controller.java_facade.getLanguages().to_a.sort {|a,b| a.getName() <=> b.getName()}.each do |language| -%> - '<%= language.getKey().html_safe -%>': '<%= language.getName().html_safe -%>', - <% end %>}, - - phrases: { - 'actionPlan': '<%= escape_javascript message('issue_filter.criteria.actionPlan') -%>', - 'actionPlanNotAvailable': '<%= escape_javascript message('issue_filter.criteria.actionPlanNotAvailable') -%>', - 'any': '<%= escape_javascript message('any') -%>', - 'anytime': '<%= escape_javascript message('anytime') -%>', - 'all': '<%= escape_javascript message('all') -%>', - 'edit': '<%= escape_javascript message('edit') -%>', - 'delete': '<%= escape_javascript message('delete') -%>', - 'to': '<%= escape_javascript message('to.downcase') -%>', - 'project': '<%= escape_javascript message('issue_filter.criteria.project') -%>', - 'language': '<%= escape_javascript message('language') -%>', - 'severity': '<%= escape_javascript message('issue_filter.criteria.severity') -%>', - 'severities': { - BLOCKER: '<%= escape_javascript message('severity.BLOCKER') -%>', - CRITICAL: '<%= escape_javascript message('severity.CRITICAL') -%>', - MAJOR: '<%= escape_javascript message('severity.MAJOR') -%>', - MINOR: '<%= escape_javascript message('severity.MINOR') -%>', - INFO: '<%= escape_javascript message('severity.INFO') -%>' - }, - 'status': '<%= escape_javascript message('issue_filter.criteria.status') -%>', - 'statuses': { - OPEN: '<%= escape_javascript message('issue.status.OPEN') -%>', - CONFIRMED: '<%= escape_javascript message('issue.status.CONFIRMED') -%>', - REOPENED: '<%= escape_javascript message('issue.status.REOPENED') -%>', - RESOLVED: '<%= escape_javascript message('issue.status.RESOLVED') -%>', - CLOSED: '<%= escape_javascript message('issue.status.CLOSED') -%>' - }, - 'actions': { - comment: '<%= escape_javascript message('issue.comment.formlink') -%>', - assign: '<%= escape_javascript message('issue.assign.formlink') -%>', - assign_to_me: '<%= escape_javascript message('issue.assign.to_me') -%>', - assigned_to: '<%= escape_javascript message('assigned_to') -%>', - plan: '<%= escape_javascript message('issue.do_plan') -%>', - planned_for: '<%= escape_javascript message('issue.planned_for') -%>', - set_severity: '<%= escape_javascript message('issue.set_severity') -%>', - <% Internal.issues.listPluginActions().each do |action| -%> - '<%= escape_javascript action -%>': '<%= escape_javascript message("issue.action.#{action}.formlink") -%>', - <% end -%> - }, - 'transitions': { - confirm: '<%= escape_javascript message('issue.transition.confirm') -%>', - unconfirm: '<%= escape_javascript message('issue.transition.unconfirm') -%>', - resolve: '<%= escape_javascript message('issue.transition.resolve') -%>', - falsepositive: '<%= escape_javascript message('issue.transition.falsepositive') -%>', - reopen: '<%= escape_javascript message('issue.transition.reopen') -%>', - close: '<%= escape_javascript message('issue.transition.close') -%>' - }, - assignee: '<%= escape_javascript message('issue_filter.criteria.assignee') -%>', - resolution: '<%= escape_javascript message('issue_filter.criteria.resolution') -%>', - resolutions: { - 'RESOLVED': '<%= escape_javascript message('issue.status.RESOLVED') -%>', - 'UNRESOLVED': '<%= escape_javascript message('unresolved') -%>', - 'FALSE-POSITIVE': '<%= escape_javascript message('issue.resolution.FALSE-POSITIVE') -%>', - 'FIXED': '<%= escape_javascript message('issue.resolution.FIXED') -%>', - 'REMOVED': '<%= escape_javascript message('issue.resolution.REMOVED') -%>' - }, - reporter: '<%= escape_javascript message('issue_filter.criteria.reporter') -%>', - rule: '<%= escape_javascript message('issue_filter.criteria.rule') -%>', - created: '<%= escape_javascript message('issue_filter.criteria.created') -%>', - createdAt: '<%= escape_javascript message('issue_filter.criteria.created_at') -%>', - - moreCriteria: '<%= escape_javascript message('issue_filter.more_criteria') -%>', - unassigned: '<%= escape_javascript message('unassigned') -%>', - unplanned: '<%= escape_javascript message('issue.unplanned') -%>', - assignedToMe: '<%= escape_javascript message('assigned_to_me') -%>', - filtersList: '<%= escape_javascript message('issue_filter.filter_list') -%>', - commentConfirmDelete: '<%= escape_javascript message('issue.comment.delete_confirm_message') -%>', - requirementRemoved: '<%= escape_javascript message('issue.technical_debt_deleted') -%>', - - select2: { - noMatches: '<%= escape_javascript message('select2.noMatches') -%>', - searching: '<%= escape_javascript message('select2.searching') -%>', - tooShort: '<%= escape_javascript message('select2.tooShort', :params => [2]) -%>' - }, - 'Done': '<%= escape_javascript message("Done") -%>', - 'Prev': '<%= escape_javascript message("Prev") -%>', - 'Next': '<%= escape_javascript message("Next") -%>', - 'Today': '<%= escape_javascript message("Today") -%>', - 'January': '<%= escape_javascript message("January") -%>', - 'February': '<%= escape_javascript message("February") -%>', - 'March': '<%= escape_javascript message("March") -%>', - 'April': '<%= escape_javascript message("April") -%>', - 'May': '<%= escape_javascript message("May") -%>', - 'June': '<%= escape_javascript message("June") -%>', - 'July': '<%= escape_javascript message("July") -%>', - 'August': '<%= escape_javascript message("August") -%>', - 'September': '<%= escape_javascript message("September") -%>', - 'October': '<%= escape_javascript message("October") -%>', - 'November': '<%= escape_javascript message("November") -%>', - 'December': '<%= escape_javascript message("December") -%>', - 'Jan': '<%= escape_javascript message("Jan") -%>', - 'Feb': '<%= escape_javascript message("Feb") -%>', - 'Mar': '<%= escape_javascript message("Mar") -%>', - 'Apr': '<%= escape_javascript message("Apr") -%>', - 'Jun': '<%= escape_javascript message("Jun") -%>', - 'Jul': '<%= escape_javascript message("Jul") -%>', - 'Aug': '<%= escape_javascript message("Aug") -%>', - 'Sep': '<%= escape_javascript message("Sep") -%>', - 'Oct': '<%= escape_javascript message("Oct") -%>', - 'Nov': '<%= escape_javascript message("Nov") -%>', - 'Dec': '<%= escape_javascript message("Dec") -%>', - 'Sunday': '<%= escape_javascript message("Sunday") -%>', - 'Monday': '<%= escape_javascript message("Monday") -%>', - 'Tuesday': '<%= escape_javascript message("Tuesday") -%>', - 'Wednesday': '<%= escape_javascript message("Wednesday") -%>', - 'Thursday': '<%= escape_javascript message("Thursday") -%>', - 'Friday': '<%= escape_javascript message("Friday") -%>', - 'Saturday': '<%= escape_javascript message("Saturday") -%>', - 'Sun': '<%= escape_javascript message("Sun") -%>', - 'Mon': '<%= escape_javascript message("Mon") -%>', - 'Tue': '<%= escape_javascript message("Tue") -%>', - 'Wed': '<%= escape_javascript message("Wed") -%>', - 'Thu': '<%= escape_javascript message("Thu") -%>', - 'Fri': '<%= escape_javascript message("Fri") -%>', - 'Sat': '<%= escape_javascript message("Sat") -%>', - 'Su': '<%= escape_javascript message("Su") -%>', - 'Mo': '<%= escape_javascript message("Mo") -%>', - 'Tu': '<%= escape_javascript message("Tu") -%>', - 'We': '<%= escape_javascript message("We") -%>', - 'Th': '<%= escape_javascript message("Th") -%>', - 'Fr': '<%= escape_javascript message("Fr") -%>', - 'Sa': '<%= escape_javascript message("Sa") -%>' - } - }); - - window.messages = { - 'all': '<%= escape_javascript message('all') -%>', - 'assigned_to': '<%= escape_javascript message('assigned_to') -%>', - 'bulk_change': '<%= escape_javascript message('bulk_change') -%>', - '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') -%>', - - 'component_viewer.measure_section.size': '<%= escape_javascript message('component_viewer.measure_section.size') -%>', - 'component_viewer.measure_section.complexity': '<%= escape_javascript message('component_viewer.measure_section.complexity') -%>', - 'component_viewer.measure_section.structure': '<%= escape_javascript message('component_viewer.measure_section.structure') -%>', - 'component_viewer.measure_section.documentation': '<%= escape_javascript message('component_viewer.measure_section.documentation') -%>', - 'component_viewer.measure_section.severities': '<%= escape_javascript message('component_viewer.measure_section.severities') -%>', - 'component_viewer.measure_section.rules': '<%= escape_javascript message('component_viewer.measure_section.rules') -%>', - 'component_viewer.measure_section.issues': '<%= escape_javascript message('component_viewer.measure_section.issues') -%>', - 'component_viewer.measure_section.sqale': '<%= escape_javascript message('component_viewer.measure_section.sqale') -%>', - 'component_viewer.measure_section.unit_tests': '<%= escape_javascript message('component_viewer.measure_section.unit_tests') -%>', - 'component_viewer.measure_section.integration_tests': '<%= escape_javascript message('component_viewer.measure_section.integration_tests') -%>', - - 'component_viewer.issues.current_issue': '<%= escape_javascript message('component_viewer.issues.current_issue') -%>', - 'component_viewer.issues.unresolved_issues': '<%= escape_javascript message('component_viewer.issues.unresolved_issues') -%>', - 'component_viewer.issues.false_positive_issues': '<%= escape_javascript message('component_viewer.issues.false_positive_issues') -%>', - - 'component_viewer.header.debt': '<%= escape_javascript message('component_viewer.header.debt') -%>', - 'component_viewer.header.toggle_issues': '<%= escape_javascript message('component_viewer.header.toggle_issues') -%>', - 'component_viewer.header.toggle_coverage': '<%= escape_javascript message('component_viewer.header.toggle_coverage') -%>', - 'component_viewer.header.toggle_duplications': '<%= escape_javascript message('component_viewer.header.toggle_duplications') -%>', - - 'metric.lines.name': '<%= escape_javascript message('metric.lines.name') -%>', - 'metric.ncloc.name': '<%= escape_javascript message('metric.ncloc.name') -%>', - 'metric.complexity.name': '<%= escape_javascript message('metric.complexity.name') -%>', - 'metric.function_complexity.name': '<%= escape_javascript message('metric.function_complexity.name') -%>', - 'metric.classes.name': '<%= escape_javascript message('metric.classes.name') -%>', - 'metric.functions.name': '<%= escape_javascript message('metric.functions.name') -%>', - 'metric.accessors.name': '<%= escape_javascript message('metric.accessors.name') -%>', - 'metric.statements.name': '<%= escape_javascript message('metric.statements.name') -%>', - 'metric.comment_lines.name': '<%= escape_javascript message('metric.comment_lines.name') -%>', - 'metric.comment_lines_density.name': '<%= escape_javascript message('metric.comment_lines_density.name') -%>', - 'metric.public_api.name': '<%= escape_javascript message('metric.public_api.name') -%>', - 'metric.public_undocumented_api.name': '<%= escape_javascript message('metric.public_undocumented_api.name') -%>', - 'metric.public_documented_api_density.name': '<%= escape_javascript message('metric.public_documented_api_density.name') -%>', - - 'metric.coverage.name': '<%= escape_javascript message('metric.coverage.name') -%>', - 'metric.line_coverage.name': '<%= escape_javascript message('metric.line_coverage.name') -%>', - 'metric.lines_to_cover.name': '<%= escape_javascript message('metric.lines_to_cover.name') -%>', - 'metric.covered_lines.name': '<%= escape_javascript message('metric.covered_lines.name') -%>', - 'metric.uncovered_lines.name': '<%= escape_javascript message('metric.uncovered_lines.name') -%>', - 'metric.branch_coverage.name': '<%= escape_javascript message('metric.branch_coverage.name') -%>', - 'metric.conditions_to_cover.name': '<%= escape_javascript message('metric.conditions_to_cover.name') -%>', - 'metric.covered_conditions.name': '<%= escape_javascript message('metric.covered_conditions.name') -%>', - 'metric.uncovered_conditions.name': '<%= escape_javascript message('metric.uncovered_conditions.name') -%>', - - 'metric.duplicated_blocks.name': '<%= escape_javascript message('metric.duplicated_blocks.name') -%>', - 'metric.duplicated_files.name': '<%= escape_javascript message('metric.duplicated_files.name') -%>', - 'metric.duplicated_lines.name': '<%= escape_javascript message('metric.duplicated_lines.name') -%>', - 'metric.duplicated_lines_density.name': '<%= escape_javascript message('metric.duplicated_lines_density.name') -%>', - - 'metric.violations.name': '<%= escape_javascript message('metric.violations.name') -%>', - 'metric.sqale_index.name': '<%= escape_javascript message('metric.sqale_index.name') -%>', - 'metric.blocker_violations.name': '<%= escape_javascript message('metric.blocker_violations.name') -%>', - 'metric.critical_violations.name': '<%= escape_javascript message('metric.critical_violations.name') -%>', - 'metric.major_violations.name': '<%= escape_javascript message('metric.major_violations.name') -%>', - 'metric.minor_violations.name': '<%= escape_javascript message('metric.minor_violations.name') -%>', - 'metric.info_violations.name': '<%= escape_javascript message('metric.info_violations.name') -%>' - }; -</script> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_bulk_change_form.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_bulk_change_form.html.erb new file mode 100644 index 00000000000..3802a05e99d --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_bulk_change_form.html.erb @@ -0,0 +1,161 @@ +<% + project_uuid = @projectUuids.to_a.first if !@projectUuids.empty? && @projectUuids.to_a.size == 1 + project_key = Internal.component_api.findByUuid(project_uuid).key() if project_uuid + max_page_size_reached = @issues.size >= Internal.issues.maxPageSize() + + transitions_by_issues = {} + unresolved_issues = 0 + unresolved_issues_user_can_admin = 0 + at_least_one_issue_is_planned = false + at_least_one_issue_is_assigned = false + all_issues_are_assigned_to_current_user = true + @issues.each do |issue| + transitions = Internal.issues.listTransitions(issue) + transitions.each do |transition| + issues_for_transition = transitions_by_issues[transition.key] || 0 + issues_for_transition += 1 + transitions_by_issues[transition.key] = issues_for_transition + end + unresolved_issues += 1 unless issue.resolution() + if Java::OrgSonarServerUser::UserSession.get().hasProjectPermissionByUuid('issueadmin', issue.projectUuid) + unresolved_issues_user_can_admin += 1 unless issue.resolution() + end + at_least_one_issue_is_planned ||= issue.actionPlanKey() + at_least_one_issue_is_assigned ||= issue.assignee() + all_issues_are_assigned_to_current_user &&= issue.assignee() == current_user.login + end +%> +<form id="bulk-change-form" method="post" action="<%= ApplicationController.root_context -%>/issues/bulk_change"> + <input type="hidden" name="issues" value="<%= @issues.map { |issue| issue.key() }.join(',') -%>"> + <input type="hidden" name="actions[]" id="bulk-change-transition-action"> + <fieldset> + <div class="modal-head"> + <h2><%= message('issue_bulk_change.form.title', {:params => @issues.size.to_s}) -%></h2> + </div> + <div class="modal-body"> + <div> + <% if max_page_size_reached %> + <p class="notes"><%= message('issue_bulk_change.max_issues_reached', :params => @issues.size) -%></p> + <% end %> + </div> + <div class="modal-error"/> + + <% if unresolved_issues > 0 %> + <div class="modal-field"> + <label for="assignee"> + <%= message('issue.assign.formlink') -%> + </label> + <input id="assign-action" name="actions[]" type="checkbox" value="assign"/> + <%= assign_added_choices = {} + # SONAR-4728 Display 'Not assigned' option only if at least one issue is assigned + assign_added_choices[''] = escape_javascript(message('unassigned')) if at_least_one_issue_is_assigned + # SONAR-4728 Display 'Assign to me' option only if not not all issues are asigned to current user + assign_added_choices[current_user.login] = escape_javascript(message('assigned_to_me')) unless all_issues_are_assigned_to_current_user + user_select_tag('assign.assignee', :html_id => 'assignee', :open => false, :selected_user => current_user, + :include_choices => assign_added_choices) + -%> + <span style="float:right" class="note">(<%= message('issue_bulk_change.x_issues', :params => unresolved_issues.to_s) -%>)</span> + </div> + <% + if project_key + plans = Internal.issues.findOpenActionPlans(project_key) + # Display action plan combo box if there are some action plans defined on the project + unless plans.empty? + first_plan = plans[0] + options = plans.map { |plan| + label = plan.deadLine ? "#{h plan.name} (#{format_date(plan.deadLine)})" : h(plan.name) + [label, plan.key] + } + # SONAR-4728 Display 'Unplan' option only if at least one issue is planned + options.unshift([escape_javascript(message('issue.unplan.submit')), '']) if at_least_one_issue_is_planned + plan_options = options_for_select(options, first_plan.key) + %> + <div class="modal-field"> + <label for="plan"> + <%= message('issue.do_plan') -%> + </label> + <input id="plan-action" name="actions[]" type="checkbox" value="plan"/> + <%= dropdown_tag('plan.plan', plan_options, {:show_search_box => false}, {:id => 'plan'}) -%> + <span style="float:right" class="note">(<%= message('issue_bulk_change.x_issues', :params => unresolved_issues.to_s) -%>)</span> + </div> + <% end %> + <% end %> + <div class="modal-field"> + <label for="severity"> + <%= message('issue.set_severity') -%> + </label> + <input id="set-severity-action" name="actions[]" type="checkbox" value="set_severity"/> + <%= severity_dropdown_tag('set_severity.severity', severitiy_select_option_tags, {:show_search_box => false}, + {:id => 'severity'}) -%> + <span style="float:right" class="note">(<%= message('issue_bulk_change.x_issues', :params => unresolved_issues_user_can_admin.to_s) -%>)</span> + </div> + <% end %> + + <% if transitions_by_issues.size > 0 %> + <div class="modal-field"> + <label> + <%= message('issue.transition') -%> + </label> + <% transitions_by_issues.keys.sort.each do |transition| %> + <input type="radio" id="transition-<%= transition -%>" name="do_transition.transition" value="<%= transition -%>" + onClick="addTransitionAction();"> <label for="transition-<%= transition -%>" style="float: none; display: inline; left: 0; cursor: pointer;"><%= message("issue.transition.#{transition}") -%></label> + <span style="float:right" class="note">(<%= message('issue_bulk_change.x_issues', :params => transitions_by_issues[transition].to_s) %>)</span><br/> + <% end %> + </div> + <% end %> + + <div class="modal-field"> + <label> + <%= message('issue.comment.formlink') -%> + <span style="cursor: help;"><%= image_tag 'help.png', :title => h(message('issue_bulk_change.comment.help')) -%></span> + </label> + <div style="padding: 0 10px 10px 0;"> + <div> + <textarea rows="4" name="comment" id="comment" style="width: 100%"></textarea> + </div> + <div style="float:right"> + <%= render :partial => 'markdown/tips' -%> + </div> + </div> + </div> + + <div class="modal-field"> + <label for="send-notifications"> + <%= message('issue.send_notifications') -%> + </label> + <input id="send-notifications" name="sendNotifications" type="checkbox" value="true" /> + </div> + </div> + <div class="modal-foot"> + <span id="bulk-change-loading-image" class="loading-image hidden"><%= image_tag 'loading.gif' %></span> + <input type="submit" value="<%= message('apply') -%>" id="bulk-change-submit" class="bulk-change" onclick="return displayLoadingImage()" /> + <a href="#" onclick="return closeModalWindow()" id="bulk-change-cancel"><%= message('cancel') -%></a> + </div> + </fieldset> +</form> +<script> + $j("#bulk-change-form").modalForm({ + success: function () { + onBulkIssues(<%= json_escape(params.to_json) -%>); + } + }); + + function addTransitionAction() { + $j('#bulk-change-transition-action').val("do_transition"); + } + + function displayLoadingImage() { + $j('#bulk-change-loading-image').removeClass("hidden"); + } + + function check(id){ + $j('#'+ id).prop('checked', true); + } + + /** + * Attach some events on select boxes to automatically select associate check box when selecting an option + */ + $j('#assignee').click(function() { check('assign-action'); }); + $j('#plan').click(function() { check('plan-action'); }); + $j('#severity').click(function() { check('set-severity-action'); }); +</script> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_copy_form.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_copy_form.html.erb new file mode 100644 index 00000000000..ad34992b288 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_copy_form.html.erb @@ -0,0 +1,20 @@ +<form id="copy-filter-form" method="post" action="<%= ApplicationController.root_context -%>/issues/copy"> + <input type="hidden" name="id" value="<%= @filter.id -%>"> + <fieldset> + <div class="modal-head"> + <h2><%= message('issue_filter.copy_filter') -%></h2> + </div> + <%= render :partial => 'filter_shared_form' %> + <div class="modal-foot"> + <input type="submit" value="<%= message('copy') -%>" id="copy-submit" class="issue-filter"/> + <a href="#" onclick="return closeModalWindow()" id="copy-cancel"><%= h message('cancel') -%></a> + </div> + </fieldset> +</form> +<script> + $j("#copy-filter-form").modalForm({ + success: function (data) { + return onCopy(data); + } + }); +</script> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_edit_form.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_edit_form.html.erb new file mode 100644 index 00000000000..650e7e19993 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_edit_form.html.erb @@ -0,0 +1,20 @@ +<form id="edit-filter-form" method="post" action="<%= ApplicationController.root_context -%>/issues/edit"> + <input type="hidden" name="id" value="<%= @filter.id -%>"> + <fieldset> + <div class="modal-head"> + <h2><%= message('issue_filter.edit_filter') -%></h2> + </div> + <%= render :partial => 'filter_shared_form', :locals => {:display_owner => true} %> + <div class="modal-foot"> + <input type="submit" value="<%= message('save') -%>" id="save-submit" class="issue-filter"/> + <a href="#" onclick="return closeModalWindow()" id="save-cancel"><%= message('cancel') -%></a> + </div> + </fieldset> +</form> +<script> + $j("#edit-filter-form").modalForm({ + success: function (data) { + return onEdit(data); + } + }); +</script> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_favourites.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_favourites.html.erb new file mode 100644 index 00000000000..4c1f60180b8 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_favourites.html.erb @@ -0,0 +1,9 @@ +<% if logged_in? %> + { + <% @favourite_filters.each do |filter| %> + "<%= h filter.id -%>": "<%= escape_javascript filter.name -%>", + <% end %> + } +<% else %> + null +<% end %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_save_as_form.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_save_as_form.html.erb new file mode 100644 index 00000000000..a771f24bd01 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_save_as_form.html.erb @@ -0,0 +1,20 @@ +<form id="save-as-filter-form" method="post" action="<%= ApplicationController.root_context -%>/issues/save_as"> + <input type="hidden" name="data" value="<%= u(@filter_query_serialized) -%>"> + <fieldset> + <div class="modal-head"> + <h2><%= message('issue_filter.save_filter') -%></h2> + </div> + <%= render :partial => 'filter_shared_form' %> + <div class="modal-foot"> + <input type="submit" value="<%= message('save') -%>" id="save-as-submit" class="issue-filter"/> + <a href="#" onclick="return closeModalWindow()" id="save-as-cancel"><%= message('cancel') -%></a> + </div> + </fieldset> +</form> +<script> + $j("#save-as-filter-form").modalForm({ + success: function (data) { + return onSaveAs(data); + } + }); +</script> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_shared_form.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_shared_form.html.erb new file mode 100644 index 00000000000..24c3891792a --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_filter_shared_form.html.erb @@ -0,0 +1,33 @@ +<% if !local_assigns.has_key? :display_owner + display_owner = false + end %> +<div class="modal-body"> + <div class="modal-error"/> + <div class="modal-field"> + <label for="name"><%= message('issue_filter.form.name') -%> <em class="mandatory">*</em></label> + <input id="name" name="name" type="text" size="50" maxlength="100" value="<%= h(@filter.name) if @filter -%>" autofocus="autofocus"/> + </div> + <div class="modal-field"> + <label for="description"><%= message('issue_filter.form.description') -%></label> + <input id="description" name="description" type="text" size="50" maxlength="4000" value="<%= h(@filter.description) if @filter -%>"/> + </div> + <% if display_owner && can_be_reassigned_by(current_user, @filter) %> + <% filter_owner = Api.users.findByLogin(@filter.user) %> + <div class="modal-field"> + <label for="user"><%= h message('issue_filter.form.owner') -%></label> + <%= user_select_tag('user', :html_id => 'select-filter-owner', :selected_user => filter_owner) -%> + </div> + <% else %> + <input id="user" name="user" type="hidden" value="<%= h(@filter.user) if @filter -%>"/> + <% end %> + <% if Internal.issues.canUserShareIssueFilter() %> + <div class="modal-field"> + <% if !@filter || @filter.user.nil? || @filter.user == current_user.login %> + <label for="shared"><%= message('issue_filter.form.share') -%></label> + <input id="shared" name="shared" type="checkbox" value="true" <%= 'checked' if (@filter && @filter.shared) -%>/> + <% else %> + <input id="shared" name="shared" type="hidden" value="<%= @filter.shared if @filter -%>"/> + <% end %> + </div> + <% end %> +</div> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_list.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_list.html.erb new file mode 100644 index 00000000000..7e07f5f74b6 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_list.html.erb @@ -0,0 +1,126 @@ +<% + if @issues_result.issues && !@issues_result.issues.empty? + colspan = 6 +%> + <div id="issues-list"> + <table class="data width100"> + <thead> + <tr> + <th width="1%" nowrap class="column-severity"> + <%= column_html(@issues_query, @issues_result, message('severity_abbreviated'), message('severity'), 'SEVERITY') %> + </th> + <th width="1%" nowrap class="column-status"> + <%= column_html(@issues_query, @issues_result, message('status'), message('status'), 'STATUS') %> + </th> + <th> + <%= message('description') -%> + </th> + <th nowrap> + <%= message('component') -%> + </th> + <th class="column-assignee"> + <%= column_html(@issues_query, @issues_result, message('issue_filter.header.assignee'), message('issue_filter.header.assignee'), 'ASSIGNEE') %> + </th> + <th width="1%" nowrap> + <%= message('issue_filter.header.action_plan') -%> + </th> + <th width="1%" nowrap class="column-update-date"> + <%= column_html(@issues_query, @issues_result, message('issue_filter.header.update_date'), message('issue_filter.header.update_date'), 'UPDATE_DATE') %> + </th> + </tr> + </thead> + <tbody> + <% + @issues_result.issues.each do |issue| + %> + <tr class="<%= cycle('even', 'odd') -%>"> + <td width="1%" nowrap> + <i class="icon-severity-<%= issue.severity.downcase -%>"></i> + </td> + <td> + <%= message("issue.status.#{issue.status}") -%> + <% if issue.resolution %> + <span class="note" style="white-space: nowrap">[<%= message("issue.resolution.#{issue.resolution}") -%>]</span> + <% end %> + </td> + <td> + <a class='open-modal rule-modal issue-detail-link' modal-width='900' href='<%= url_for :controller => 'issue', :action => 'show', :id => issue.key, :modal => true -%>'> + <%= h truncate(issue.message, :length => 100) -%></a> + </td> + <td> + <% project = @issues_result.project(issue) + component = @issues_result.component(issue) -%> + <div class="subtitle"><%= h (truncate(project.name, :length => 100)) -%></div> + <% if component %> + <!-- Do not display component name when issue is on module --> + <% if component.key != project.key %> + <%= h component.longName() -%> + <% end %> + <% else %> + <del><%= h issue.componentKey() %></del> + <% end %> + </td> + <td> + <%= h @issues_result.user(issue.assignee).name if issue.assignee -%> + </td> + <td> + <%= h @issues_result.actionPlan(issue).name if issue.actionPlanKey() -%> + </td> + <td width="1%" nowrap> + <%= human_short_date(Api::Utils.java_to_ruby_datetime(issue.updateDate())) -%> + </td> + </tr> + <% + end + %> + </tbody> + <%= if @ajax_mode + paginate_java(@issues_result.paging, :colspan => colspan, :id => 'issue-filter-foot', :include_loading_icon => true, + :url_results => url_for({:controller => 'issues', :action => 'search'}.merge(params))) { |label, page_id| + link_to_function label, "refreshList('#{@issues_query.sort}', #{@issues_query.asc}, #{page_id})" + } + else + paginate_java(@issues_result.paging, :colspan => colspan, :id => 'issue-filter-foot', :include_loading_icon => true) { |label, page_id| + link_to(label, params.merge({:pageIndex => page_id})) + } + end + %> + </table> + </div> +<% + end +%> + +<script type="text/javascript"> + var filterCriteria = <%= json_escape(@issues_query_params.to_json) -%>; + + function refreshList(sort, asc, page) { + $j('#issue-filter-foot_pages').hide(); + $j('#issue-filter-foot_loading').show(); + + filterCriteria['sort']=sort; + filterCriteria['asc']=asc; + filterCriteria['pageIndex']=page; + var url = baseUrl + '/issues/search?' + $j.param(filterCriteria); + + <% if @ajax_mode %> + $j('.issues-content').load(url, function () { + // As issues will be loaded after open-modal has been processed by jQuery, we have to process manually modal classes + processModal(); + }); + <% else %> + window.location = url; + <% end %> + return false; + } + + function processModal(){ + $j('.issues-content .open-modal').modal(); + } + + <% if @ajax_mode %> + $j(document).ready(function() { + processModal(); + }); + <% end %> +</script> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations.html.erb new file mode 100644 index 00000000000..389856f6de1 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations.html.erb @@ -0,0 +1,53 @@ +<% if @issues_result && @issues_result.maxResultsReached() %> + <p class="notes"><%= message('issue_filter.max_results_reached', :params => @issues_result.paging.total()) -%></p> +<% end %> + +<% unless @issues_result.issues && !@issues_result.issues.empty? %> + <p class="notes" style="padding: 5px;"><%= message('issue_filter.no_result') -%></p> +<% end %> + +<% if logged_in? && !@first_search %> + <div id="issue-filters-operations" class="line-block marginbottom10"> + <ul class="operations"> + <% if @filter && @filter.id %> + <li><a id="copy" href="<%= url_for :action => 'copy_form', :id => @filter.id -%>" class="link-action open-modal"><%= message('copy') -%></a></li> + <% end %> + <% if !@unchanged && @filter && @filter.id && @filter.user == current_user.login %> + <li> + <%= link_to message('save'), params.merge({:action => 'save', :id => @filter.id}), :class => 'link-action', :id => 'save', :method => :post -%> + </li> + <% end %> + <% unless @filter %> + <li> + <a id="save-as" href="<%= url_for params.merge({:action => 'save_as_form'}) -%>" class="link-action open-modal"><%= message('save_as') -%></a> + </li> + <% end %> + <% if @issues_result.issues && !@issues_result.issues.empty? %> + <li class="last"> + <a id="bulk-change" href="<%= url_for params.merge({:action => 'bulk_change_form'}) -%>" + class="link-action open-modal"><%= message('bulk_change') -%></a> + </li> + <% end %> + </ul> + + <% if @filter && @filter.id && @filter.name.present? %> + <div class="page_title" id="filter-title"> + <p> + <span class="h3"><%= h @filter.name -%></span> + <span class="note"> + <% if !@filter.shared %> + [<%= message 'issue_filter.private' -%>] + <% elsif @filter.user==current_user.login %> + [<%= message 'issue_filter.shared_with_all_users' -%>] + <% elsif @filter.user %> + [<%= message 'shared_by' -%> <%= Api.users.findByLogin(@filter.user).name -%>] + <% end %> + </span> + <% if @filter.user == current_user.login %> + <a href="<%= url_for :action => 'edit_form', :id => @filter.id -%>" class="open-modal" id="edit-filter"><%= image_tag 'pencil-small.png', :alt => message('edit') -%></a> + <% end %> + </p> + </div> + <% end %> + </div> +<% end %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations_ajax.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations_ajax.html.erb new file mode 100644 index 00000000000..36e6c6d4573 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_operations_ajax.html.erb @@ -0,0 +1,12 @@ +<% if logged_in? && @issues_result.issues && !@issues_result.issues.empty? %> + <div class="line-block marginbottom10"> + <div id="issue-filters-operations" style="padding-right: 5px; padding-top: 5px;"> + <ul class="operations"> + <li class="last"> + <a id="bulk-change" href="<%= url_for params.merge({:controller => 'issues', :action => 'bulk_change_form'}) -%>" + class="link-action open-modal"><%= message('bulk_change') -%></a> + </li> + </ul> + </div> + </div> +<% end %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search.html.erb new file mode 100644 index 00000000000..f9d6b02a759 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search.html.erb @@ -0,0 +1,4 @@ +<div class="issues-content"> + <%= render :partial => 'operations' -%> + <%= render :partial => 'list' -%> +</div> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search_ajax.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search_ajax.html.erb new file mode 100644 index 00000000000..eb1c6bf3e58 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/_search_ajax.html.erb @@ -0,0 +1,10 @@ +<div class="issues-content"> + <% + if @issues_result.issues && !@issues_result.issues.empty? + %> + <%= render :partial => 'operations_ajax' -%> + <%= render :partial => 'list' -%> + <% else %> + <%= render :partial => 'shared/no_issues' -%> + <% end %> +</div> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/manage.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/manage.html.erb new file mode 100644 index 00000000000..619b9f78068 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/manage.html.erb @@ -0,0 +1,149 @@ +<% content_for :script do %> + <script> + $j(document).ready(function () { + $j(".issue-filter-star").click(function () { + var filterId = $j(this).attr('filter-id'); + var star = $j(this); + $j.ajax({ + type: 'POST', + url: baseUrl + "/issues/toggle_fav", + data: {id: filterId}, + success: function (data) { + if (data == 'true') { + star.removeClass('icon-not-favorite').addClass('icon-favorite'); + star.attr('title', '<%= escape_javascript message('click_to_remove_from_favorites') -%>'); + } else { + star.removeClass('icon-favorite').addClass('icon-not-favorite'); + star.attr('title', '<%= escape_javascript message('click_to_add_to_favorites') -%>'); + } + $j('#sidebar-favourites').load(baseUrl + '/issues/favourites'); + } + }); + }); + }); + + function onSaveAs(data) { + window.location = baseUrl + '/issues/search#id=' + data; + } + + function onCopy(data) { + window.location = baseUrl + '/issues/search#id=' + data; + } + + function onEdit(data) { + window.location = baseUrl + '/issues/search#id=' + data; + } + </script> +<% end %> +<div> + <div class="page"> + <h1><%= message 'issue_filter.manage.my_filters' -%></h1> + <table class="data marginbottom10" id="my-issue-filters"> + <thead> + <tr> + <th class="thin"></th> + <th><%= message('issue_filter.form.name') -%></th> + <th><%= message('issue_filter.sharing') -%></th> + <th class="right"><%= message('operations') -%></th> + </tr> + </thead> + <tbody> + <% if @filters.empty? %> + <tr class="even"> + <td colspan="4"><%= message('issue_filter.no_filters') -%></td> + </tr> + <% else %> + <% @filters.each do |filter| %> + <tr id="my-issue-filter-<%= filter.name.parameterize -%>" class="<%= cycle('even', 'odd', :name => 'my-filters') -%>"> + <td> + <%= issue_filter_star(filter, @favourite_filter_ids.include?(filter.id)) -%> + </td> + <td> + <a href="<%= ApplicationController.root_context -%>/issues/search#id=<%= h filter.id -%>|<%= h filter.data -%>"><%= h filter.name -%></a> + <% if filter.description %> + <div class="note"><%= h filter.description -%></div> + <% end %> + </td> + <td> + <% if filter.shared %> + <%= message 'issue_filter.shared_with_all_users' -%> + <% else %> + <%= message 'issue_filter.private' -%> + <% end %> + </td> + <td class="thin nowrap right"> + <a id="copy-<%= filter.name.parameterize -%>" href="<%= ApplicationController.root_context -%>/issues/copy_form/<%= filter.id -%>" + class="link-action open-modal"><%= message('copy') -%></a> + + <a id="edit_<%= filter.name.parameterize -%>" href="<%= ApplicationController.root_context -%>/issues/edit_form/<%= filter.id -%>" + class="link-action open-modal"><%= message('edit') -%></a> + + <%= link_to_action message('delete'), "#{ApplicationController.root_context}/issues/delete/#{filter.id}", + :class => 'link-action link-red', + :id => "delete_#{filter.name.parameterize}", + :confirm_button => message('delete'), + :confirm_title => 'issue_filter.delete_confirm_title', + :confirm_msg => 'issue_filter.are_you_sure_want_delete_filter_x', + :confirm_msg_params => [filter.name] -%> + </td> + </tr> + <% end %> + <% end %> + </tbody> + </table> + + <br/> + + <h1><%= message 'issue_filter.manage.shared_filters' -%></h1> + <table class="data" id="shared-filters"> + <thead> + <tr> + <th class="thin"></th> + <th><%= message('issue_filter.form.name') -%></th> + <th><%= message('shared_by') -%></th> + <th class="right"><%= message('operations') -%></th> + </tr> + </thead> + <tbody> + <% if @shared_filters.empty? %> + <tr class="even"> + <td colspan="4"><%= message('issue_filter.no_filters') -%></td> + </tr> + <% else %> + <% @shared_filters.each do |filter| %> + <tr id="shared-<%= filter.name.parameterize -%>" class="<%= cycle('even', 'odd', :name => 'shared-filters') -%>"> + <td> + <%= issue_filter_star(filter, @favourite_filter_ids.include?(filter.id)) -%> + </td> + <td> + <a href="<%= ApplicationController.root_context -%>/issues/search#id=<%= h filter.id -%>|<%= h filter.data -%>"><%= h filter.name -%></a> + <% if filter.description %> + <div class="note"><%= h filter.description -%></div> + <% end %> + </td> + <td> + <%= h Api.users.findByLogin(filter.user).name -%> + </td> + <td class="thin nowrap right"> + <a id="copy-<%= filter.name.parameterize -%>" href="<%= ApplicationController.root_context -%>/issues/copy_form/<%= filter.id -%>" class="link-action open-modal"><%= message('copy') -%></a> + <% if has_role?(:admin) %> + + <a id="edit_shared_<%= filter.name.parameterize -%>" href="<%= ApplicationController.root_context -%>/issues/edit_form/<%= filter.id -%>" class="link-action open-modal"><%= message('edit') -%></a> + + <%= link_to_action message('delete'), "#{ApplicationController.root_context}/issues/delete/#{filter.id}", + :class => 'link-action link-red', + :id => "delete_system_#{filter.name.parameterize}", + :confirm_button => message('delete'), + :confirm_title => 'issue_filter.delete_confirm_title', + :confirm_msg => 'issue_filter.are_you_sure_want_delete_filter_x', + :confirm_msg_params => [filter.name] -%> + <% end %> + </td> + </tr> + <% end %> + <% end %> + </tbody> + </table> + + </div> +</div> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/search.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/search.html.erb new file mode 100644 index 00000000000..9cd9535b53e --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/issues2/search.html.erb @@ -0,0 +1,257 @@ +<% content_for :script do %> + <script data-main="<%= ApplicationController.root_context -%>/js/issues-old/app" src="<%= ApplicationController.root_context -%>/js/require.js"></script> +<% end %> + +<div class="navigator"> + <div class="navigator-header"></div> + <div class="navigator-filters"></div> + + <div class="navigator-content"> + <div class="navigator-side"> + <div style="position:relative; overflow: visible; height: 100%;"> + <div class="navigator-actions"></div> + <div class="navigator-results"></div> + <a class="navigator-resizer"><i class="icon-resizer"></i></a> + </div> + </div> + <div class="navigator-main"> + <div class="navigator-notes"><%= message('issue_filter.max_results_reached', :params => 10000) -%></div> + <div class="navigator-details"></div> + </div> + </div> +</div> + + +<script> + window.SS = {}; + + _.extend(window.SS, { + currentUser: '<%= current_user.login if current_user -%>', + currentUserName: '<%= current_user.name if current_user -%>', + severities: <%= RulesConfigurationController::RULE_PRIORITIES.to_json.html_safe -%>, + statuses: <%= @options_for_statuses.to_json.html_safe -%>, + resolutions: <%= @options_for_resolutions.to_json.html_safe -%>, + favorites: <%= render :partial => 'issues/filter_favourites' -%>, + languages: {<% controller.java_facade.getLanguages().to_a.sort {|a,b| a.getName() <=> b.getName()}.each do |language| -%> + '<%= language.getKey().html_safe -%>': '<%= language.getName().html_safe -%>', + <% end %>}, + + phrases: { + 'actionPlan': '<%= escape_javascript message('issue_filter.criteria.actionPlan') -%>', + 'actionPlanNotAvailable': '<%= escape_javascript message('issue_filter.criteria.actionPlanNotAvailable') -%>', + 'any': '<%= escape_javascript message('any') -%>', + 'anytime': '<%= escape_javascript message('anytime') -%>', + 'all': '<%= escape_javascript message('all') -%>', + 'edit': '<%= escape_javascript message('edit') -%>', + 'delete': '<%= escape_javascript message('delete') -%>', + 'to': '<%= escape_javascript message('to.downcase') -%>', + 'project': '<%= escape_javascript message('issue_filter.criteria.project') -%>', + 'language': '<%= escape_javascript message('language') -%>', + 'severity': '<%= escape_javascript message('issue_filter.criteria.severity') -%>', + 'severities': { + BLOCKER: '<%= escape_javascript message('severity.BLOCKER') -%>', + CRITICAL: '<%= escape_javascript message('severity.CRITICAL') -%>', + MAJOR: '<%= escape_javascript message('severity.MAJOR') -%>', + MINOR: '<%= escape_javascript message('severity.MINOR') -%>', + INFO: '<%= escape_javascript message('severity.INFO') -%>' + }, + 'status': '<%= escape_javascript message('issue_filter.criteria.status') -%>', + 'statuses': { + OPEN: '<%= escape_javascript message('issue.status.OPEN') -%>', + CONFIRMED: '<%= escape_javascript message('issue.status.CONFIRMED') -%>', + REOPENED: '<%= escape_javascript message('issue.status.REOPENED') -%>', + RESOLVED: '<%= escape_javascript message('issue.status.RESOLVED') -%>', + CLOSED: '<%= escape_javascript message('issue.status.CLOSED') -%>' + }, + 'actions': { + comment: '<%= escape_javascript message('issue.comment.formlink') -%>', + assign: '<%= escape_javascript message('issue.assign.formlink') -%>', + assign_to_me: '<%= escape_javascript message('issue.assign.to_me') -%>', + assigned_to: '<%= escape_javascript message('assigned_to') -%>', + plan: '<%= escape_javascript message('issue.do_plan') -%>', + planned_for: '<%= escape_javascript message('issue.planned_for') -%>', + set_severity: '<%= escape_javascript message('issue.set_severity') -%>', + <% Internal.issues.listPluginActions().each do |action| -%> + '<%= escape_javascript action -%>': '<%= escape_javascript message("issue.action.#{action}.formlink") -%>', + <% end -%> + }, + 'transitions': { + confirm: '<%= escape_javascript message('issue.transition.confirm') -%>', + unconfirm: '<%= escape_javascript message('issue.transition.unconfirm') -%>', + resolve: '<%= escape_javascript message('issue.transition.resolve') -%>', + falsepositive: '<%= escape_javascript message('issue.transition.falsepositive') -%>', + reopen: '<%= escape_javascript message('issue.transition.reopen') -%>', + close: '<%= escape_javascript message('issue.transition.close') -%>' + }, + assignee: '<%= escape_javascript message('issue_filter.criteria.assignee') -%>', + resolution: '<%= escape_javascript message('issue_filter.criteria.resolution') -%>', + resolutions: { + 'RESOLVED': '<%= escape_javascript message('issue.status.RESOLVED') -%>', + 'UNRESOLVED': '<%= escape_javascript message('unresolved') -%>', + 'FALSE-POSITIVE': '<%= escape_javascript message('issue.resolution.FALSE-POSITIVE') -%>', + 'FIXED': '<%= escape_javascript message('issue.resolution.FIXED') -%>', + 'REMOVED': '<%= escape_javascript message('issue.resolution.REMOVED') -%>' + }, + reporter: '<%= escape_javascript message('issue_filter.criteria.reporter') -%>', + rule: '<%= escape_javascript message('issue_filter.criteria.rule') -%>', + created: '<%= escape_javascript message('issue_filter.criteria.created') -%>', + createdAt: '<%= escape_javascript message('issue_filter.criteria.created_at') -%>', + + moreCriteria: '<%= escape_javascript message('issue_filter.more_criteria') -%>', + unassigned: '<%= escape_javascript message('unassigned') -%>', + unplanned: '<%= escape_javascript message('issue.unplanned') -%>', + assignedToMe: '<%= escape_javascript message('assigned_to_me') -%>', + filtersList: '<%= escape_javascript message('issue_filter.filter_list') -%>', + commentConfirmDelete: '<%= escape_javascript message('issue.comment.delete_confirm_message') -%>', + requirementRemoved: '<%= escape_javascript message('issue.technical_debt_deleted') -%>', + + select2: { + noMatches: '<%= escape_javascript message('select2.noMatches') -%>', + searching: '<%= escape_javascript message('select2.searching') -%>', + tooShort: '<%= escape_javascript message('select2.tooShort', :params => [2]) -%>' + }, + 'Done': '<%= escape_javascript message("Done") -%>', + 'Prev': '<%= escape_javascript message("Prev") -%>', + 'Next': '<%= escape_javascript message("Next") -%>', + 'Today': '<%= escape_javascript message("Today") -%>', + 'January': '<%= escape_javascript message("January") -%>', + 'February': '<%= escape_javascript message("February") -%>', + 'March': '<%= escape_javascript message("March") -%>', + 'April': '<%= escape_javascript message("April") -%>', + 'May': '<%= escape_javascript message("May") -%>', + 'June': '<%= escape_javascript message("June") -%>', + 'July': '<%= escape_javascript message("July") -%>', + 'August': '<%= escape_javascript message("August") -%>', + 'September': '<%= escape_javascript message("September") -%>', + 'October': '<%= escape_javascript message("October") -%>', + 'November': '<%= escape_javascript message("November") -%>', + 'December': '<%= escape_javascript message("December") -%>', + 'Jan': '<%= escape_javascript message("Jan") -%>', + 'Feb': '<%= escape_javascript message("Feb") -%>', + 'Mar': '<%= escape_javascript message("Mar") -%>', + 'Apr': '<%= escape_javascript message("Apr") -%>', + 'Jun': '<%= escape_javascript message("Jun") -%>', + 'Jul': '<%= escape_javascript message("Jul") -%>', + 'Aug': '<%= escape_javascript message("Aug") -%>', + 'Sep': '<%= escape_javascript message("Sep") -%>', + 'Oct': '<%= escape_javascript message("Oct") -%>', + 'Nov': '<%= escape_javascript message("Nov") -%>', + 'Dec': '<%= escape_javascript message("Dec") -%>', + 'Sunday': '<%= escape_javascript message("Sunday") -%>', + 'Monday': '<%= escape_javascript message("Monday") -%>', + 'Tuesday': '<%= escape_javascript message("Tuesday") -%>', + 'Wednesday': '<%= escape_javascript message("Wednesday") -%>', + 'Thursday': '<%= escape_javascript message("Thursday") -%>', + 'Friday': '<%= escape_javascript message("Friday") -%>', + 'Saturday': '<%= escape_javascript message("Saturday") -%>', + 'Sun': '<%= escape_javascript message("Sun") -%>', + 'Mon': '<%= escape_javascript message("Mon") -%>', + 'Tue': '<%= escape_javascript message("Tue") -%>', + 'Wed': '<%= escape_javascript message("Wed") -%>', + 'Thu': '<%= escape_javascript message("Thu") -%>', + 'Fri': '<%= escape_javascript message("Fri") -%>', + 'Sat': '<%= escape_javascript message("Sat") -%>', + 'Su': '<%= escape_javascript message("Su") -%>', + 'Mo': '<%= escape_javascript message("Mo") -%>', + 'Tu': '<%= escape_javascript message("Tu") -%>', + 'We': '<%= escape_javascript message("We") -%>', + 'Th': '<%= escape_javascript message("Th") -%>', + 'Fr': '<%= escape_javascript message("Fr") -%>', + 'Sa': '<%= escape_javascript message("Sa") -%>' + } + }); + + window.messages = { + 'all': '<%= escape_javascript message('all') -%>', + 'assigned_to': '<%= escape_javascript message('assigned_to') -%>', + 'bulk_change': '<%= escape_javascript message('bulk_change') -%>', + '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') -%>', + + 'component_viewer.measure_section.size': '<%= escape_javascript message('component_viewer.measure_section.size') -%>', + 'component_viewer.measure_section.complexity': '<%= escape_javascript message('component_viewer.measure_section.complexity') -%>', + 'component_viewer.measure_section.structure': '<%= escape_javascript message('component_viewer.measure_section.structure') -%>', + 'component_viewer.measure_section.documentation': '<%= escape_javascript message('component_viewer.measure_section.documentation') -%>', + 'component_viewer.measure_section.severities': '<%= escape_javascript message('component_viewer.measure_section.severities') -%>', + 'component_viewer.measure_section.rules': '<%= escape_javascript message('component_viewer.measure_section.rules') -%>', + 'component_viewer.measure_section.issues': '<%= escape_javascript message('component_viewer.measure_section.issues') -%>', + 'component_viewer.measure_section.sqale': '<%= escape_javascript message('component_viewer.measure_section.sqale') -%>', + 'component_viewer.measure_section.unit_tests': '<%= escape_javascript message('component_viewer.measure_section.unit_tests') -%>', + 'component_viewer.measure_section.integration_tests': '<%= escape_javascript message('component_viewer.measure_section.integration_tests') -%>', + + 'component_viewer.issues.current_issue': '<%= escape_javascript message('component_viewer.issues.current_issue') -%>', + 'component_viewer.issues.unresolved_issues': '<%= escape_javascript message('component_viewer.issues.unresolved_issues') -%>', + 'component_viewer.issues.false_positive_issues': '<%= escape_javascript message('component_viewer.issues.false_positive_issues') -%>', + + 'component_viewer.header.debt': '<%= escape_javascript message('component_viewer.header.debt') -%>', + 'component_viewer.header.toggle_issues': '<%= escape_javascript message('component_viewer.header.toggle_issues') -%>', + 'component_viewer.header.toggle_coverage': '<%= escape_javascript message('component_viewer.header.toggle_coverage') -%>', + 'component_viewer.header.toggle_duplications': '<%= escape_javascript message('component_viewer.header.toggle_duplications') -%>', + + 'metric.lines.name': '<%= escape_javascript message('metric.lines.name') -%>', + 'metric.ncloc.name': '<%= escape_javascript message('metric.ncloc.name') -%>', + 'metric.complexity.name': '<%= escape_javascript message('metric.complexity.name') -%>', + 'metric.function_complexity.name': '<%= escape_javascript message('metric.function_complexity.name') -%>', + 'metric.classes.name': '<%= escape_javascript message('metric.classes.name') -%>', + 'metric.functions.name': '<%= escape_javascript message('metric.functions.name') -%>', + 'metric.accessors.name': '<%= escape_javascript message('metric.accessors.name') -%>', + 'metric.statements.name': '<%= escape_javascript message('metric.statements.name') -%>', + 'metric.comment_lines.name': '<%= escape_javascript message('metric.comment_lines.name') -%>', + 'metric.comment_lines_density.name': '<%= escape_javascript message('metric.comment_lines_density.name') -%>', + 'metric.public_api.name': '<%= escape_javascript message('metric.public_api.name') -%>', + 'metric.public_undocumented_api.name': '<%= escape_javascript message('metric.public_undocumented_api.name') -%>', + 'metric.public_documented_api_density.name': '<%= escape_javascript message('metric.public_documented_api_density.name') -%>', + + 'metric.coverage.name': '<%= escape_javascript message('metric.coverage.name') -%>', + 'metric.line_coverage.name': '<%= escape_javascript message('metric.line_coverage.name') -%>', + 'metric.lines_to_cover.name': '<%= escape_javascript message('metric.lines_to_cover.name') -%>', + 'metric.covered_lines.name': '<%= escape_javascript message('metric.covered_lines.name') -%>', + 'metric.uncovered_lines.name': '<%= escape_javascript message('metric.uncovered_lines.name') -%>', + 'metric.branch_coverage.name': '<%= escape_javascript message('metric.branch_coverage.name') -%>', + 'metric.conditions_to_cover.name': '<%= escape_javascript message('metric.conditions_to_cover.name') -%>', + 'metric.covered_conditions.name': '<%= escape_javascript message('metric.covered_conditions.name') -%>', + 'metric.uncovered_conditions.name': '<%= escape_javascript message('metric.uncovered_conditions.name') -%>', + + 'metric.duplicated_blocks.name': '<%= escape_javascript message('metric.duplicated_blocks.name') -%>', + 'metric.duplicated_files.name': '<%= escape_javascript message('metric.duplicated_files.name') -%>', + 'metric.duplicated_lines.name': '<%= escape_javascript message('metric.duplicated_lines.name') -%>', + 'metric.duplicated_lines_density.name': '<%= escape_javascript message('metric.duplicated_lines_density.name') -%>', + + 'metric.violations.name': '<%= escape_javascript message('metric.violations.name') -%>', + 'metric.sqale_index.name': '<%= escape_javascript message('metric.sqale_index.name') -%>', + 'metric.blocker_violations.name': '<%= escape_javascript message('metric.blocker_violations.name') -%>', + 'metric.critical_violations.name': '<%= escape_javascript message('metric.critical_violations.name') -%>', + 'metric.major_violations.name': '<%= escape_javascript message('metric.major_violations.name') -%>', + 'metric.minor_violations.name': '<%= escape_javascript message('metric.minor_violations.name') -%>', + 'metric.info_violations.name': '<%= escape_javascript message('metric.info_violations.name') -%>' + }; +</script> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb index fd4844050c6..ecf608e904f 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb @@ -22,6 +22,9 @@ <a href="<%= ApplicationController.root_context -%>/issues/index" class="<%= 'selected' if selected_section==Navigation::SECTION_ISSUES -%>"><%= message('issues.page') -%></a> </li> <li> + <a href="<%= ApplicationController.root_context -%>/issues2/index" class="<%= 'selected' if selected_section==Navigation::SECTION_ISSUES2 -%>">Old Issues</a> + </li> + <li> <a href="<%= ApplicationController.root_context -%>/coding_rules" class="<%= 'selected' if selected_section==Navigation::SECTION_CODING_RULES -%>"><%= message('coding_rules.page') -%></a> </li> <li> diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 1bb662b5f33..934006976be 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -757,6 +757,22 @@ issues.found=Found #------------------------------------------------------------------------------ # +# ISSUES FACETS +# +#------------------------------------------------------------------------------ +issues.facet.severities=Severity +issues.facet.componentRootUuids=Project +issues.facet.statuses=Status +issues.facet.actionPlans=Action Plan +issues.facet.assignees=Assignee +issues.facet.components=Component +issues.facet.rules=Rule +issues.facet.resolutions=Resolution +issues.facet.languages=Language + + +#------------------------------------------------------------------------------ +# # ISSUE BULK CHANGE # #------------------------------------------------------------------------------ |