diff options
author | Stas Vilchik <vilchiks@gmail.com> | 2015-06-01 13:53:12 +0200 |
---|---|---|
committer | Stas Vilchik <vilchiks@gmail.com> | 2015-06-01 16:13:15 +0200 |
commit | 8d3e45ed0c33cbf63b9c44be5b2c2f9e0dad9a50 (patch) | |
tree | 71fe97e3a545b81be12e6108561bd0ed9379224c /server/sonar-web/src/main/js | |
parent | 340fb8d06a718672c8f9b8166a68ed064cc56a73 (diff) | |
download | sonarqube-8d3e45ed0c33cbf63b9c44be5b2c2f9e0dad9a50.tar.gz sonarqube-8d3e45ed0c33cbf63b9c44be5b2c2f9e0dad9a50.zip |
move issues page from coffee to js
Diffstat (limited to 'server/sonar-web/src/main/js')
60 files changed, 2991 insertions, 0 deletions
diff --git a/server/sonar-web/src/main/js/apps/issues/app-context.js b/server/sonar-web/src/main/js/apps/issues/app-context.js new file mode 100644 index 00000000000..bcef0841779 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/app-context.js @@ -0,0 +1,105 @@ +define([ + './models/state', + './layout', + './models/issues', + 'components/navigator/models/facets', + './models/filters', + './controller', + './router', + './workspace-list-view', + './workspace-header-view', + './facets-view' +], function (State, Layout, Issues, Facets, Filters, Controller, Router, WorkspaceListView, WorkspaceHeaderView, + FacetsView) { + + var $ = jQuery, + App = new Marionette.Application(); + + App.getContextQuery = function () { + return { componentUuids: window.config.resource }; + }; + + App.getRestrictedFacets = function () { + return { + 'TRK': ['projectUuids'], + 'BRC': ['projectUuids'], + 'DIR': ['projectUuids', 'moduleUuids', 'directories'], + 'DEV': ['authors'], + 'DEV_PRJ': ['projectUuids', 'authors'] + }; + }; + + App.updateContextFacets = function () { + var facets = this.state.get('facets'), + allFacets = this.state.get('allFacets'), + facetsFromServer = this.state.get('facetsFromServer'); + return this.state.set({ + facets: facets, + allFacets: _.difference(allFacets, this.getRestrictedFacets()[window.config.resourceQualifier]), + facetsFromServer: _.difference(facetsFromServer, this.getRestrictedFacets()[window.config.resourceQualifier]) + }); + }; + + App.addInitializer(function () { + this.state = new State({ + isContext: true, + contextQuery: this.getContextQuery(), + contextComponentUuid: window.config.resource, + contextComponentName: window.config.resourceName, + contextComponentQualifier: window.config.resourceQualifier + }); + this.updateContextFacets(); + this.list = new Issues(); + this.facets = new Facets(); + this.filters = new Filters(); + }); + + App.addInitializer(function () { + this.layout = new Layout({ app: this }); + $('.issues').empty().append(this.layout.render().el); + $('#footer').addClass('search-navigator-footer'); + }); + + App.addInitializer(function () { + this.controller = new Controller({ app: this }); + }); + + App.addInitializer(function () { + this.issuesView = new WorkspaceListView({ + app: this, + collection: this.list + }); + this.layout.workspaceListRegion.show(this.issuesView); + this.issuesView.bindScrollEvents(); + }); + + App.addInitializer(function () { + this.workspaceHeaderView = new WorkspaceHeaderView({ + app: this, + collection: this.list + }); + this.layout.workspaceHeaderRegion.show(this.workspaceHeaderView); + }); + + App.addInitializer(function () { + this.facetsView = new FacetsView({ + app: this, + collection: this.facets + }); + this.layout.facetsRegion.show(this.facetsView); + }); + + App.addInitializer(function () { + return this.controller.fetchFilters().done(function () { + key.setScope('list'); + App.router = new Router({ app: App }); + Backbone.history.start(); + }); + }); + + var l10nXHR = window.requestMessages(); + return jQuery.when(l10nXHR).done(function () { + return App.start(); + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/app-new.js b/server/sonar-web/src/main/js/apps/issues/app-new.js new file mode 100644 index 00000000000..07b047bfcd8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/app-new.js @@ -0,0 +1,82 @@ +define([ + './models/state', + './layout', + './models/issues', + 'components/navigator/models/facets', + './models/filters', + './controller', + './router', + './workspace-list-view', + './workspace-header-view', + './facets-view', + './filters-view' +], function (State, Layout, Issues, Facets, Filters, Controller, Router, WorkspaceListView, WorkspaceHeaderView, + FacetsView, FiltersView) { + + var $ = jQuery, + App = new Marionette.Application(); + + App.addInitializer(function () { + this.state = new State(); + this.list = new Issues(); + this.facets = new Facets(); + this.filters = new Filters(); + }); + + App.addInitializer(function () { + this.layout = new Layout({ app: this }); + $('.issues').empty().append(this.layout.render().el); + $('#footer').addClass('search-navigator-footer'); + }); + + App.addInitializer(function () { + this.controller = new Controller({ app: this }); + }); + + App.addInitializer(function () { + this.issuesView = new WorkspaceListView({ + app: this, + collection: this.list + }); + this.layout.workspaceListRegion.show(this.issuesView); + this.issuesView.bindScrollEvents(); + }); + + App.addInitializer(function () { + this.workspaceHeaderView = new WorkspaceHeaderView({ + app: this, + collection: this.list + }); + this.layout.workspaceHeaderRegion.show(this.workspaceHeaderView); + }); + + App.addInitializer(function () { + this.facetsView = new FacetsView({ + app: this, + collection: this.facets + }); + this.layout.facetsRegion.show(this.facetsView); + }); + + App.addInitializer(function () { + this.filtersView = new FiltersView({ + app: this, + collection: this.filters + }); + this.layout.filtersRegion.show(this.filtersView); + }); + + App.addInitializer(function () { + this.controller.fetchFilters().done(function () { + key.setScope('list'); + App.router = new Router({ app: App }); + Backbone.history.start(); + }); + }); + + var l10nXHR = window.requestMessages(); + return jQuery.when(l10nXHR).done(function () { + return App.start(); + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/component-viewer/issue-view.js b/server/sonar-web/src/main/js/apps/issues/component-viewer/issue-view.js new file mode 100644 index 00000000000..6d8a6f73207 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/component-viewer/issue-view.js @@ -0,0 +1,18 @@ +define([ + '../workspace-list-item-view' +], function (IssueView) { + + return IssueView.extend({ + onRender: function () { + IssueView.prototype.onRender.apply(this, arguments); + this.$el.removeClass('issue-navigate-right'); + }, + + serializeData: function () { + return _.extend(IssueView.prototype.serializeData.apply(this, arguments), { + showComponent: false + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/component-viewer/main.js b/server/sonar-web/src/main/js/apps/issues/component-viewer/main.js new file mode 100644 index 00000000000..a702c4a4d7f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/component-viewer/main.js @@ -0,0 +1,204 @@ +define([ + 'components/source-viewer/main', + '../models/issues', + './issue-view', + '../templates' +], function (SourceViewer, Issues, IssueView) { + + var $ = jQuery; + + return SourceViewer.extend({ + events: function () { + return _.extend(SourceViewer.prototype.events.apply(this, arguments), { + 'click .js-close-component-viewer': 'closeComponentViewer', + 'click .code-issue': 'selectIssue' + }); + }, + + initialize: function (options) { + SourceViewer.prototype.initialize.apply(this, arguments); + return this.listenTo(options.app.state, 'change:selectedIndex', this.select); + }, + + onLoaded: function () { + SourceViewer.prototype.onLoaded.apply(this, arguments); + this.bindShortcuts(); + if (this.baseIssue != null) { + return this.scrollToLine(this.baseIssue.get('line')); + } + }, + + bindShortcuts: function () { + var that = this; + var doAction = function (action) { + var selectedIssueView = that.getSelectedIssueEl(); + if (!selectedIssueView) { + return; + } + return selectedIssueView.find('.js-issue-' + action).click(); + }; + key('up', 'componentViewer', function () { + that.options.app.controller.selectPrev(); + return false; + }); + key('down', 'componentViewer', function () { + that.options.app.controller.selectNext(); + return false; + }); + key('left,backspace', 'componentViewer', function () { + that.options.app.controller.closeComponentViewer(); + return false; + }); + key('f', 'componentViewer', function () { + return doAction('transition'); + }); + key('a', 'componentViewer', function () { + return doAction('assign'); + }); + key('m', 'componentViewer', function () { + return doAction('assign-to-me'); + }); + key('p', 'componentViewer', function () { + return doAction('plan'); + }); + key('i', 'componentViewer', function () { + return doAction('set-severity'); + }); + return key('c', 'componentViewer', function () { + return doAction('comment'); + }); + }, + + unbindShortcuts: function () { + return key.deleteScope('componentViewer'); + }, + + onClose: function () { + SourceViewer.prototype.onClose.apply(this, arguments); + this.unbindScrollEvents(); + return this.unbindShortcuts(); + }, + + select: function () { + var selected = this.options.app.state.get('selectedIndex'), + selectedIssue = this.options.app.list.at(selected); + if (selectedIssue.get('component') === this.model.get('key')) { + return this.scrollToIssue(selectedIssue.get('key')); + } else { + this.unbindShortcuts(); + return this.options.app.controller.showComponentViewer(selectedIssue); + } + }, + + getSelectedIssueEl: function () { + var selected = this.options.app.state.get('selectedIndex'); + if (selected == null) { + return null; + } + var selectedIssue = this.options.app.list.at(selected); + if (selectedIssue == null) { + return null; + } + var selectedIssueView = this.$('#issue-' + (selectedIssue.get('key'))); + if (selectedIssueView.length > 0) { + return selectedIssueView; + } else { + return null; + } + }, + + selectIssue: function (e) { + var key = $(e.currentTarget).data('issue-key'), + issue = this.issues.find(function (issue) { + return issue.get('key') === key; + }), + index = this.options.app.list.indexOf(issue); + return this.options.app.state.set({ selectedIndex: index }); + }, + + scrollToIssue: function (key) { + var el = this.$('#issue-' + key); + if (el.length > 0) { + var line = el.closest('[data-line-number]').data('line-number'); + return this.scrollToLine(line); + } else { + this.unbindShortcuts(); + var selected = this.options.app.state.get('selectedIndex'), + selectedIssue = this.options.app.list.at(selected); + return this.options.app.controller.showComponentViewer(selectedIssue); + } + }, + + openFileByIssue: function (issue) { + this.baseIssue = issue; + var componentKey = issue.get('component'), + componentUuid = issue.get('componentUuid'); + return this.open(componentUuid, componentKey); + }, + + linesLimit: function () { + var line = this.LINES_LIMIT / 2; + if ((this.baseIssue != null) && this.baseIssue.has('line')) { + line = Math.max(line, this.baseIssue.get('line')); + } + return { + from: line - this.LINES_LIMIT / 2 + 1, + to: line + this.LINES_LIMIT / 2 + }; + }, + + limitIssues: function (issues) { + var that = this; + var index = this.ISSUES_LIMIT / 2; + if ((this.baseIssue != null) && this.baseIssue.has('index')) { + index = Math.max(index, this.baseIssue.get('index')); + } + return issues.filter(function (issue) { + return Math.abs(issue.get('index') - index) <= that.ISSUES_LIMIT / 2; + }); + }, + + requestIssues: function () { + var that = this; + var r; + if (this.options.app.list.last().get('component') === this.model.get('key')) { + r = this.options.app.controller.fetchNextPage(); + } else { + r = $.Deferred().resolve().promise(); + } + return r.done(function () { + that.issues.reset(that.options.app.list.filter(function (issue) { + return issue.get('component') === that.model.key(); + })); + that.issues.reset(that.limitIssues(that.issues)); + return that.addIssuesPerLineMeta(that.issues); + }); + }, + + renderIssues: function () { + this.issues.forEach(this.renderIssue, this); + return this.$('.source-line-issues').addClass('hidden'); + }, + + renderIssue: function (issue) { + var issueView = new IssueView({ + el: '#issue-' + issue.get('key'), + model: issue, + app: this.options.app + }); + this.issueViews.push(issueView); + return issueView.render(); + }, + + scrollToLine: function (line) { + var row = this.$('[data-line-number=' + line + ']'), + goal = row.length > 0 ? row.offset().top - 200 : 0; + return $(window).scrollTop(goal); + }, + + closeComponentViewer: function () { + return this.options.app.controller.closeComponentViewer(); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/controller.js b/server/sonar-web/src/main/js/apps/issues/controller.js new file mode 100644 index 00000000000..15ccdda827f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/controller.js @@ -0,0 +1,249 @@ +define([ + 'components/navigator/controller', + './component-viewer/main', + './workspace-home-view' +], function (Controller, ComponentViewer, HomeView) { + + var $ = jQuery, + EXTRA_FIELDS = 'actions,transitions,assigneeName,reporterName,actionPlanName', + FACET_DATA_FIELDS = ['components', 'projects', 'users', 'rules', 'actionPlans', 'languages']; + + return Controller.extend({ + _facetsFromServer: function () { + var facets = Controller.prototype._facetsFromServer.apply(this, arguments) || []; + facets.push('assigned_to_me'); + return facets; + }, + + _issuesParameters: function () { + return { + p: this.options.app.state.get('page'), + ps: this.pageSize, + s: 'FILE_LINE', + asc: true, + extra_fields: EXTRA_FIELDS, + facets: this._facetsFromServer().join() + }; + }, + + _myIssuesFromResponse: function (r) { + var myIssuesData = _.findWhere(r.facets, { property: 'assigned_to_me' }); + if ((myIssuesData != null) && _.isArray(myIssuesData.values) && myIssuesData.values.length > 0) { + return this.options.app.state.set({ myIssues: myIssuesData.values[0].count }, { silent: true }); + } else { + return this.options.app.state.unset('myIssues', { silent: true }); + } + }, + + fetchList: function (firstPage) { + var that = this; + if (firstPage == null) { + firstPage = true; + } + if (firstPage) { + this.options.app.state.set({ selectedIndex: 0, page: 1 }, { silent: true }); + this.hideHomePage(); + this.closeComponentViewer(); + } + var data = this._issuesParameters(); + _.extend(data, this.options.app.state.get('query')); + if (this.options.app.state.get('isContext')) { + _.extend(data, this.options.app.state.get('contextQuery')); + } + return $.get(baseUrl + '/api/issues/search', data).done(function (r) { + var issues = that.options.app.list.parseIssues(r); + if (firstPage) { + that.options.app.list.reset(issues); + } else { + that.options.app.list.add(issues); + } + that.options.app.list.setIndex(); + FACET_DATA_FIELDS.forEach(function (field) { + that.options.app.facets[field] = r[field]; + }); + that.options.app.facets.reset(that._allFacets()); + that.options.app.facets.add(_.reject(r.facets, function (f) { + return f.property === 'assigned_to_me'; + }), { merge: true }); + that._myIssuesFromResponse(r); + that.enableFacets(that._enabledFacets()); + that.options.app.state.set({ + page: r.p, + pageSize: r.ps, + total: r.total, + maxResultsReached: r.p * r.ps >= r.total + }); + if (firstPage && that.isIssuePermalink()) { + return that.showComponentViewer(that.options.app.list.first()); + } + }); + }, + + isIssuePermalink: function () { + var query = this.options.app.state.get('query'); + return (query.issues != null) && this.options.app.list.length === 1; + }, + + fetchFilters: function () { + var that = this; + return $.get(baseUrl + '/api/issue_filters/app', function (r) { + that.options.app.state.set({ + canBulkChange: r.canBulkChange, + canManageFilters: r.canManageFilters + }); + return that.options.app.filters.reset(r.favorites); + }); + }, + + _mergeCollections: function (a, b) { + var collection = new Backbone.Collection(a); + collection.add(b, { merge: true }); + return collection.toJSON(); + }, + + requestFacet: function (id) { + var that = this; + if (id === 'assignees') { + return this.requestAssigneeFacet(); + } + var facet = this.options.app.facets.get(id), + data = _.extend({ facets: id, ps: 1 }, this.options.app.state.get('query')); + if (this.options.app.state.get('isContext')) { + _.extend(data, this.options.app.state.get('contextQuery')); + } + return $.get(baseUrl + '/api/issues/search', data, function (r) { + FACET_DATA_FIELDS.forEach(function (field) { + that.options.app.facets[field] = that._mergeCollections(that.options.app.facets[field], r[field]); + }); + var facetData = _.findWhere(r.facets, { property: id }); + if (facetData != null) { + return facet.set(facetData); + } + }); + }, + + requestAssigneeFacet: function () { + var that = this; + var facet = this.options.app.facets.get('assignees'), + data = _.extend({ facets: 'assignees,assigned_to_me', ps: 1 }, this.options.app.state.get('query')); + if (this.options.app.state.get('isContext')) { + _.extend(data, this.options.app.state.get('contextQuery')); + } + return $.get(baseUrl + '/api/issues/search', data, function (r) { + FACET_DATA_FIELDS.forEach(function (field) { + that.options.app.facets[field] = that._mergeCollections(that.options.app.facets[field], r[field]); + }); + var facetData = _.findWhere(r.facets, { property: 'assignees' }); + that._myIssuesFromResponse(r); + if (facetData != null) { + return facet.set(facetData); + } + }); + }, + + newSearch: function () { + this.options.app.state.unset('filter'); + return this.options.app.state.setQuery({ resolved: 'false' }); + }, + + applyFilter: function (filter, ignoreQuery) { + if (ignoreQuery == null) { + ignoreQuery = false; + } + if (!ignoreQuery) { + var filterQuery = this.parseQuery(filter.get('query')); + this.options.app.state.setQuery(filterQuery); + } + return this.options.app.state.set({ filter: filter, changed: false }); + }, + + parseQuery: function () { + var q = Controller.prototype.parseQuery.apply(this, arguments); + delete q.asc; + delete q.s; + return q; + }, + + getQuery: function (separator, addContext) { + if (separator == null) { + separator = '|'; + } + if (addContext == null) { + addContext = false; + } + var filter = this.options.app.state.get('query'); + if (addContext && this.options.app.state.get('isContext')) { + _.extend(filter, this.options.app.state.get('contextQuery')); + } + var route = []; + _.map(filter, function (value, property) { + return route.push('' + property + '=' + encodeURIComponent(value)); + }); + return route.join(separator); + }, + + getRoute: function () { + var filter = this.options.app.state.get('filter'), + query = Controller.prototype.getRoute.apply(this, arguments); + if (filter != null) { + if (this.options.app.state.get('changed') && query.length > 0) { + query = 'id=' + filter.id + '|' + query; + } else { + query = 'id=' + filter.id; + } + } + return query; + }, + + _prepareComponent: function (issue) { + return { + key: issue.get('component'), + name: issue.get('componentLongName'), + qualifier: issue.get('componentQualifier'), + project: issue.get('project'), + projectName: issue.get('projectLongName') + }; + }, + + showComponentViewer: function (issue) { + this.options.app.layout.workspaceComponentViewerRegion.reset(); + key.setScope('componentViewer'); + this.options.app.issuesView.unbindScrollEvents(); + this.options.app.state.set('component', this._prepareComponent(issue)); + this.options.app.componentViewer = new ComponentViewer({ app: this.options.app }); + this.options.app.layout.workspaceComponentViewerRegion.show(this.options.app.componentViewer); + this.options.app.layout.showComponentViewer(); + return this.options.app.componentViewer.openFileByIssue(issue); + }, + + closeComponentViewer: function () { + key.setScope('list'); + $('body').click(); + this.options.app.state.unset('component'); + this.options.app.layout.workspaceComponentViewerRegion.reset(); + this.options.app.layout.hideComponentViewer(); + this.options.app.issuesView.bindScrollEvents(); + return this.options.app.issuesView.scrollTo(); + }, + + showHomePage: function () { + this.fetchList(); + this.options.app.layout.workspaceComponentViewerRegion.reset(); + key.setScope('home'); + this.options.app.issuesView.unbindScrollEvents(); + this.options.app.homeView = new HomeView({ app: this.options.app }); + this.options.app.layout.workspaceHomeRegion.show(this.options.app.homeView); + return this.options.app.layout.showHomePage(); + }, + + hideHomePage: function () { + this.options.app.layout.workspaceComponentViewerRegion.reset(); + this.options.app.layout.workspaceHomeRegion.reset(); + key.setScope('list'); + this.options.app.layout.hideHomePage(); + this.options.app.issuesView.bindScrollEvents(); + return this.options.app.issuesView.scrollTo(); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets-view.js b/server/sonar-web/src/main/js/apps/issues/facets-view.js new file mode 100644 index 00000000000..2409c25b019 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets-view.js @@ -0,0 +1,65 @@ +define([ + 'components/navigator/facets-view', + './facets/base-facet', + './facets/severity-facet', + './facets/status-facet', + './facets/project-facet', + './facets/module-facet', + './facets/assignee-facet', + './facets/rule-facet', + './facets/tag-facet', + './facets/resolution-facet', + './facets/creation-date-facet', + './facets/action-plan-facet', + './facets/file-facet', + './facets/reporter-facet', + './facets/language-facet', + './facets/author-facet', + './facets/issue-key-facet', + './facets/context-facet' +], function (FacetsView, BaseFacet, SeverityFacet, StatusFacet, ProjectFacet, ModuleFacet, AssigneeFacet, RuleFacet, + TagFacet, ResolutionFacet, CreationDateFacet, ActionPlanFacet, FileFacet, ReporterFacet, LanguageFacet, + AuthorFacet, IssueKeyFacet, ContextFacet) { + + return FacetsView.extend({ + getItemView: function (model) { + switch (model.get('property')) { + case 'severities': + return SeverityFacet; + case 'statuses': + return StatusFacet; + case 'assignees': + return AssigneeFacet; + case 'resolutions': + return ResolutionFacet; + case 'createdAt': + return CreationDateFacet; + case 'projectUuids': + return ProjectFacet; + case 'moduleUuids': + return ModuleFacet; + case 'rules': + return RuleFacet; + case 'tags': + return TagFacet; + case 'actionPlans': + return ActionPlanFacet; + case 'fileUuids': + return FileFacet; + case 'reporters': + return ReporterFacet; + case 'languages': + return LanguageFacet; + case 'authors': + return AuthorFacet; + case 'issues': + return IssueKeyFacet; + case 'context': + return ContextFacet; + default: + return BaseFacet; + } + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/action-plan-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/action-plan-facet.js new file mode 100644 index 00000000000..d85fa9e5dfd --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/action-plan-facet.js @@ -0,0 +1,68 @@ +define([ + './base-facet', + '../templates' +], function (BaseFacet) { + + var $ = jQuery; + + return BaseFacet.extend({ + template: Templates['issues-action-plan-facet'], + + onRender: function () { + BaseFacet.prototype.onRender.apply(this, arguments); + var value = this.options.app.state.get('query').planned; + if ((value != null) && (!value || value === 'false')) { + return this.$('.js-facet').filter('[data-unplanned]').addClass('active'); + } + }, + + toggleFacet: function (e) { + var unplanned = $(e.currentTarget).is('[data-unplanned]'); + $(e.currentTarget).toggleClass('active'); + if (unplanned) { + var checked = $(e.currentTarget).is('.active'), + value = checked ? 'false' : null; + return this.options.app.state.updateFilter({ + planned: value, + actionPlans: null + }); + } else { + return this.options.app.state.updateFilter({ + planned: null, + actionPlans: this.getValue() + }); + } + }, + + getValuesWithLabels: function () { + var values = this.model.getValues(), + actionPlans = this.options.app.facets.actionPlans; + values.forEach(function (v) { + var key = v.val, + label = null; + if (key) { + var actionPlan = _.findWhere(actionPlans, { key: key }); + if (actionPlan != null) { + label = actionPlan.name; + } + } + v.label = label; + }); + return values; + }, + + disable: function () { + return this.options.app.state.updateFilter({ + planned: null, + actionPlans: null + }); + }, + + serializeData: function () { + return _.extend(BaseFacet.prototype.serializeData.apply(this, arguments), { + values: this.getValuesWithLabels() + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/assignee-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/assignee-facet.js new file mode 100644 index 00000000000..3645b7215d6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/assignee-facet.js @@ -0,0 +1,108 @@ +define([ + './custom-values-facet', + '../templates' +], function (CustomValuesFacet) { + + var $ = jQuery; + + return CustomValuesFacet.extend({ + template: Templates['issues-assignee-facet'], + + getUrl: function () { + return baseUrl + '/api/users/search'; + }, + + prepareAjaxSearch: function () { + return { + quietMillis: 300, + url: this.getUrl(), + data: function (term, page) { + return { q: term, p: page }; + }, + results: window.usersToSelect2 + }; + }, + + onRender: function () { + CustomValuesFacet.prototype.onRender.apply(this, arguments); + var value = this.options.app.state.get('query').assigned; + if ((value != null) && (!value || value === 'false')) { + return this.$('.js-facet').filter('[data-unassigned]').addClass('active'); + } + }, + + toggleFacet: function (e) { + var unassigned = $(e.currentTarget).is('[data-unassigned]'); + $(e.currentTarget).toggleClass('active'); + if (unassigned) { + var checked = $(e.currentTarget).is('.active'), + value = checked ? 'false' : null; + return this.options.app.state.updateFilter({ + assigned: value, + assignees: null + }); + } else { + return this.options.app.state.updateFilter({ + assigned: null, + assignees: this.getValue() + }); + } + }, + + getValuesWithLabels: function () { + var values = this.model.getValues(), + users = this.options.app.facets.users; + values.forEach(function (v) { + var login = v.val, + name = ''; + if (login) { + var user = _.findWhere(users, { login: login }); + if (user != null) { + name = user.name; + } + } + v.label = name; + }); + return values; + }, + + disable: function () { + return this.options.app.state.updateFilter({ + assigned: null, + assignees: null + }); + }, + + addCustomValue: function () { + var property = this.model.get('property'), + customValue = this.$('.js-custom-value').select2('val'), + value = this.getValue(); + if (value.length > 0) { + value += ','; + } + value += customValue; + var obj = {}; + obj[property] = value; + obj.assigned = null; + return this.options.app.state.updateFilter(obj); + }, + + sortValues: function (values) { + return _.sortBy(values, function (v) { + return v.val === '' ? -999999 : -v.count; + }); + }, + + getNumberOfMyIssues: function () { + return this.options.app.state.get('myIssues'); + }, + + serializeData: function () { + return _.extend(CustomValuesFacet.prototype.serializeData.apply(this, arguments), { + myIssues: this.getNumberOfMyIssues(), + values: this.sortValues(this.getValuesWithLabels()) + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/author-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/author-facet.js new file mode 100644 index 00000000000..ee987a3d067 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/author-facet.js @@ -0,0 +1,44 @@ +define([ + './custom-values-facet' +], function (CustomValuesFacet) { + + return CustomValuesFacet.extend({ + getUrl: function () { + return baseUrl + '/api/issues/authors'; + }, + + prepareSearch: function () { + return this.$('.js-custom-value').select2({ + placeholder: 'Search...', + minimumInputLength: 2, + allowClear: false, + formatNoMatches: function () { + return t('select2.noMatches'); + }, + formatSearching: function () { + return t('select2.searching'); + }, + formatInputTooShort: function () { + return tp('select2.tooShort', 2); + }, + width: '100%', + ajax: { + quietMillis: 300, + url: this.getUrl(), + data: function (term) { + return { q: term, ps: 25 }; + }, + results: function (data) { + return { + more: false, + results: data.authors.map(function (author) { + return { id: author, text: author }; + }) + }; + } + } + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/base-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/base-facet.js new file mode 100644 index 00000000000..1376e8b6611 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/base-facet.js @@ -0,0 +1,19 @@ +define([ + 'components/navigator/facets/base-facet', + '../templates' +], function (BaseFacet) { + + return BaseFacet.extend({ + template: Templates['issues-base-facet'], + + onRender: function () { + BaseFacet.prototype.onRender.apply(this, arguments); + return this.$('[data-toggle="tooltip"]').tooltip({ container: 'body' }); + }, + + onClose: function () { + return this.$('[data-toggle="tooltip"]').tooltip('destroy'); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/context-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/context-facet.js new file mode 100644 index 00000000000..48c88c8f1c0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/context-facet.js @@ -0,0 +1,16 @@ +define([ + './base-facet', + '../templates' +], function (BaseFacet) { + + return BaseFacet.extend({ + template: Templates['issues-context-facet'], + + serializeData: function () { + return _.extend(BaseFacet.prototype.serializeData.apply(this, arguments), { + state: this.options.app.state.toJSON() + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/creation-date-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/creation-date-facet.js new file mode 100644 index 00000000000..30305f317f1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/creation-date-facet.js @@ -0,0 +1,131 @@ +define([ + './base-facet', + '../templates' +], function (BaseFacet) { + + var $ = jQuery; + + return BaseFacet.extend({ + template: Templates['issues-creation-date-facet'], + + events: function () { + return _.extend(BaseFacet.prototype.events.apply(this, arguments), { + 'change input': 'applyFacet', + 'click .js-select-period-start': 'selectPeriodStart', + 'click .js-select-period-end': 'selectPeriodEnd', + 'click .sonar-d3 rect': 'selectBar', + 'click .js-all': 'onAllClick', + 'click .js-last-week': 'onLastWeekClick', + 'click .js-last-month': 'onLastMonthClick', + 'click .js-last-year': 'onLastYearClick' + }); + }, + + onRender: function () { + var that = this; + this.$el.toggleClass('search-navigator-facet-box-collapsed', !this.model.get('enabled')); + this.$('input').datepicker({ + dateFormat: 'yy-mm-dd', + changeMonth: true, + changeYear: true + }); + var props = ['createdAfter', 'createdBefore', 'createdAt'], + query = this.options.app.state.get('query'); + props.forEach(function (prop) { + var value = query[prop]; + if (value != null) { + return that.$('input[name=' + prop + ']').val(value); + } + }); + var values = this.model.getValues(); + if (!(_.isArray(values) && values.length > 0)) { + var date = moment(), + i, j; + values = []; + for (i = j = 0; j <= 10; i = ++j) { + values.push({ count: 0, val: date.toDate().toString() }); + date = date.subtract(1, 'days'); + } + values.reverse(); + } + return this.$('.js-barchart').barchart(values); + }, + + selectPeriodStart: function () { + return this.$('.js-period-start').datepicker('show'); + }, + + selectPeriodEnd: function () { + return this.$('.js-period-end').datepicker('show'); + }, + + applyFacet: function () { + var obj = { createdAt: null, createdInLast: null }; + this.$('input').each(function () { + var property, value; + property = $(this).prop('name'); + value = $(this).val(); + obj[property] = value; + }); + return this.options.app.state.updateFilter(obj); + }, + + disable: function () { + return this.options.app.state.updateFilter({ + createdAfter: null, + createdBefore: null, + createdAt: null, + createdInLast: null + }); + }, + + selectBar: function (e) { + var periodStart = $(e.currentTarget).data('period-start'), + periodEnd = $(e.currentTarget).data('period-end'); + return this.options.app.state.updateFilter({ + createdAfter: periodStart, + createdBefore: periodEnd, + createdAt: null, + createdInLast: null + }); + }, + + selectPeriod: function (period) { + return this.options.app.state.updateFilter({ + createdAfter: null, + createdBefore: null, + createdAt: null, + createdInLast: period + }); + }, + + onAllClick: function () { + return this.disable(); + }, + + onLastWeekClick: function (e) { + e.preventDefault(); + return this.selectPeriod('1w'); + }, + + onLastMonthClick: function (e) { + e.preventDefault(); + return this.selectPeriod('1m'); + }, + + onLastYearClick: function (e) { + e.preventDefault(); + return this.selectPeriod('1y'); + }, + + serializeData: function () { + return _.extend(BaseFacet.prototype.serializeData.apply(this, arguments), { + periodStart: this.options.app.state.get('query').createdAfter, + periodEnd: this.options.app.state.get('query').createdBefore, + createdAt: this.options.app.state.get('query').createdAt, + createdInLast: this.options.app.state.get('query').createdInLast + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/custom-values-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/custom-values-facet.js new file mode 100644 index 00000000000..11b3aebdd74 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/custom-values-facet.js @@ -0,0 +1,70 @@ +define([ + './base-facet', + '../templates' +], function (BaseFacet) { + + return BaseFacet.extend({ + template: Templates['issues-custom-values-facet'], + + events: function () { + return _.extend(BaseFacet.prototype.events.apply(this, arguments), { + 'change .js-custom-value': 'addCustomValue' + }); + }, + + getUrl: function () { + + }, + + onRender: function () { + BaseFacet.prototype.onRender.apply(this, arguments); + return this.prepareSearch(); + }, + + prepareSearch: function () { + return this.$('.js-custom-value').select2({ + placeholder: 'Search...', + minimumInputLength: 2, + allowClear: false, + formatNoMatches: function () { + return t('select2.noMatches'); + }, + formatSearching: function () { + return t('select2.searching'); + }, + formatInputTooShort: function () { + return tp('select2.tooShort', 2); + }, + width: '100%', + ajax: this.prepareAjaxSearch() + }); + }, + + prepareAjaxSearch: function () { + return { + quietMillis: 300, + url: this.getUrl(), + data: function (term, page) { + return { s: term, p: page }; + }, + results: function (data) { + return { more: data.more, results: data.results }; + } + }; + }, + + addCustomValue: function () { + var property = this.model.get('property'), + customValue = this.$('.js-custom-value').select2('val'), + value = this.getValue(); + if (value.length > 0) { + value += ','; + } + value += customValue; + var obj = {}; + obj[property] = value; + return this.options.app.state.updateFilter(obj); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/file-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/file-facet.js new file mode 100644 index 00000000000..d01340e6d3c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/file-facet.js @@ -0,0 +1,43 @@ +define([ + './base-facet', + '../templates' +], function (BaseFacet) { + + var $ = jQuery; + + return BaseFacet.extend({ + template: Templates['issues-file-facet'], + + onRender: function () { + BaseFacet.prototype.onRender.apply(this, arguments); + var maxValueWidth = _.max(this.$('.facet-stat').map(function () { + return $(this).outerWidth(); + }).get()); + return this.$('.facet-name').css('padding-right', maxValueWidth); + }, + + getValuesWithLabels: function () { + var values = this.model.getValues(), + source = this.options.app.facets.components; + values.forEach(function (v) { + var key = v.val, + label = null; + if (key) { + var item = _.findWhere(source, { uuid: key }); + if (item != null) { + label = item.longName; + } + } + v.label = label; + }); + return values; + }, + + serializeData: function () { + return _.extend(BaseFacet.prototype.serializeData.apply(this, arguments), { + values: this.sortValues(this.getValuesWithLabels()) + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/issue-key-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/issue-key-facet.js new file mode 100644 index 00000000000..2d309b85a3e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/issue-key-facet.js @@ -0,0 +1,24 @@ +define([ + './base-facet', + '../templates' +], function (BaseFacet) { + + return BaseFacet.extend({ + template: Templates['issues-issue-key-facet'], + + onRender: function () { + return this.$el.toggleClass('hidden', !this.options.app.state.get('query').issues); + }, + + disable: function () { + return this.options.app.state.updateFilter({ issues: null }); + }, + + serializeData: function () { + return _.extend(BaseFacet.prototype.serializeData.apply(this, arguments), { + issues: this.options.app.state.get('query').issues + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/language-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/language-facet.js new file mode 100644 index 00000000000..3418158fba4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/language-facet.js @@ -0,0 +1,67 @@ +define([ + './custom-values-facet' +], function (CustomValuesFacet) { + + return CustomValuesFacet.extend({ + getUrl: function () { + return baseUrl + '/api/languages/list'; + }, + + prepareSearch: function () { + return this.$('.js-custom-value').select2({ + placeholder: 'Search...', + minimumInputLength: 2, + allowClear: false, + formatNoMatches: function () { + return t('select2.noMatches'); + }, + formatSearching: function () { + return t('select2.searching'); + }, + formatInputTooShort: function () { + return tp('select2.tooShort', 2); + }, + width: '100%', + ajax: { + quietMillis: 300, + url: this.getUrl(), + data: function (term) { + return { q: term, ps: 0 }; + }, + results: function (data) { + return { + more: false, + results: data.languages.map(function (lang) { + return { id: lang.key, text: lang.name }; + }) + }; + } + } + }); + }, + + getValuesWithLabels: function () { + var values = this.model.getValues(), + source = this.options.app.facets.languages; + values.forEach(function (v) { + var key = v.val, + label = null; + if (key) { + var item = _.findWhere(source, { key: key }); + if (item != null) { + label = item.name; + } + } + v.label = label; + }); + return values; + }, + + serializeData: function () { + return _.extend(CustomValuesFacet.prototype.serializeData.apply(this, arguments), { + values: this.sortValues(this.getValuesWithLabels()) + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/module-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/module-facet.js new file mode 100644 index 00000000000..96c4a1de07f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/module-facet.js @@ -0,0 +1,30 @@ +define([ + './base-facet' +], function (BaseFacet) { + + return BaseFacet.extend({ + getValuesWithLabels: function () { + var values = this.model.getValues(), + components = this.options.app.facets.components; + values.forEach(function (v) { + var uuid = v.val, + label = uuid; + if (uuid) { + var component = _.findWhere(components, { uuid: uuid }); + if (component != null) { + label = component.longName; + } + } + v.label = label; + }); + return values; + }, + + serializeData: function () { + return _.extend(BaseFacet.prototype.serializeData.apply(this, arguments), { + values: this.sortValues(this.getValuesWithLabels()) + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/project-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/project-facet.js new file mode 100644 index 00000000000..ad7126b427d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/project-facet.js @@ -0,0 +1,83 @@ +define([ + './custom-values-facet' +], function (CustomValuesFacet) { + + return CustomValuesFacet.extend({ + + getUrl: function () { + var q = this.options.app.state.get('contextComponentQualifier'); + if (q === 'VW' || q === 'SVW') { + return baseUrl + '/api/components/search'; + } else { + return baseUrl + '/api/resources/search?f=s2&q=TRK&display_uuid=true'; + } + }, + + prepareSearch: function () { + var q = this.options.app.state.get('contextComponentQualifier'); + if (q === 'VW' || q === 'SVW') { + return this.prepareSearchForViews(); + } else { + return CustomValuesFacet.prototype.prepareSearch.apply(this, arguments); + } + }, + + prepareSearchForViews: function () { + var componentUuid = this.options.app.state.get('contextComponentUuid'); + return this.$('.js-custom-value').select2({ + placeholder: 'Search...', + minimumInputLength: 2, + allowClear: false, + formatNoMatches: function () { + return t('select2.noMatches'); + }, + formatSearching: function () { + return t('select2.searching'); + }, + formatInputTooShort: function () { + return tp('select2.tooShort', 2); + }, + width: '100%', + ajax: { + quietMillis: 300, + url: this.getUrl(), + data: function (term, page) { + return { q: term, componentUuid: componentUuid, p: page, ps: 25 }; + }, + results: function (data) { + return { + more: data.p * data.ps < data.total, + results: data.components.map(function (c) { + return { id: c.uuid, text: c.name }; + }) + }; + } + } + }); + }, + + getValuesWithLabels: function () { + var values = this.model.getValues(), + projects = this.options.app.facets.projects; + values.forEach(function (v) { + var uuid = v.val, + label = ''; + if (uuid) { + var project = _.findWhere(projects, { uuid: uuid }); + if (project != null) { + label = project.longName; + } + } + v.label = label; + }); + return values; + }, + + serializeData: function () { + return _.extend(CustomValuesFacet.prototype.serializeData.apply(this, arguments), { + values: this.sortValues(this.getValuesWithLabels()) + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/reporter-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/reporter-facet.js new file mode 100644 index 00000000000..6340aefe04f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/reporter-facet.js @@ -0,0 +1,46 @@ +define([ + './custom-values-facet' +], function (CustomValuesFacet) { + + return CustomValuesFacet.extend({ + getUrl: function () { + return baseUrl + '/api/users/search'; + }, + + prepareAjaxSearch: function () { + return { + quietMillis: 300, + url: this.getUrl(), + data: function (term, page) { + return { q: term, p: page }; + }, + results: window.usersToSelect2 + }; + }, + + getValuesWithLabels: function () { + var values = this.model.getValues(), + source = this.options.app.facets.users; + values.forEach(function (v) { + var item, key, label; + key = v.val; + label = null; + if (key) { + item = _.findWhere(source, { login: key }); + if (item != null) { + label = item.name; + } + } + v.label = label; + }); + return values; + }, + + serializeData: function () { + return _.extend(CustomValuesFacet.prototype.serializeData.apply(this, arguments), { + values: this.sortValues(this.getValuesWithLabels()) + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/resolution-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/resolution-facet.js new file mode 100644 index 00000000000..75dd3e78289 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/resolution-facet.js @@ -0,0 +1,52 @@ +define([ + './base-facet', + '../templates' +], function (BaseFacet) { + + var $ = jQuery; + + return BaseFacet.extend({ + template: Templates['issues-resolution-facet'], + + onRender: function () { + BaseFacet.prototype.onRender.apply(this, arguments); + var value = this.options.app.state.get('query').resolved; + if ((value != null) && (!value || value === 'false')) { + return this.$('.js-facet').filter('[data-unresolved]').addClass('active'); + } + }, + + toggleFacet: function (e) { + var unresolved = $(e.currentTarget).is('[data-unresolved]'); + $(e.currentTarget).toggleClass('active'); + if (unresolved) { + var checked = $(e.currentTarget).is('.active'), + value = checked ? 'false' : null; + return this.options.app.state.updateFilter({ + resolved: value, + resolutions: null + }); + } else { + return this.options.app.state.updateFilter({ + resolved: null, + resolutions: this.getValue() + }); + } + }, + + disable: function () { + return this.options.app.state.updateFilter({ + resolved: null, + resolutions: null + }); + }, + + sortValues: function (values) { + var order = ['', 'FIXED', 'FALSE-POSITIVE', 'WONTFIX', 'REMOVED']; + return _.sortBy(values, function (v) { + return order.indexOf(v.val); + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/rule-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/rule-facet.js new file mode 100644 index 00000000000..569fcf6c7de --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/rule-facet.js @@ -0,0 +1,78 @@ +define([ + './custom-values-facet' +], function (CustomValuesFacet) { + + return CustomValuesFacet.extend({ + prepareSearch: function () { + var url = baseUrl + '/api/rules/search?f=name,langName', + languages = this.options.app.state.get('query').languages; + if (languages != null) { + url += '&languages=' + languages; + } + return this.$('.js-custom-value').select2({ + placeholder: 'Search...', + minimumInputLength: 2, + allowClear: false, + formatNoMatches: function () { + return t('select2.noMatches'); + }, + formatSearching: function () { + return t('select2.searching'); + }, + formatInputTooShort: function () { + return tp('select2.tooShort', 2); + }, + width: '100%', + ajax: { + quietMillis: 300, + url: url, + data: function (term, page) { + return { q: term, p: page }; + }, + results: function (data) { + var results; + results = data.rules.map(function (rule) { + return { + id: rule.key, + text: '(' + rule.langName + ') ' + rule.name + }; + }); + return { + more: data.p * data.ps < data.total, + results: results + }; + } + } + }); + }, + + getValuesWithLabels: function () { + var values = this.model.getValues(), + rules = this.options.app.facets.rules; + values.forEach(function (v) { + var key = v.val, + label = '', + extra = ''; + if (key) { + var rule = _.findWhere(rules, { key: key }); + if (rule != null) { + label = rule.name; + } + if (rule != null) { + extra = rule.langName; + } + } + v.label = label; + v.extra = extra; + }); + return values; + }, + + serializeData: function () { + return _.extend(CustomValuesFacet.prototype.serializeData.apply(this, arguments), { + values: this.sortValues(this.getValuesWithLabels()) + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/severity-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/severity-facet.js new file mode 100644 index 00000000000..eccaf546684 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/severity-facet.js @@ -0,0 +1,17 @@ +define([ + './base-facet', + '../templates' +], function (BaseFacet) { + + return BaseFacet.extend({ + template: Templates['issues-severity-facet'], + + sortValues: function (values) { + var order = ['BLOCKER', 'MINOR', 'CRITICAL', 'INFO', 'MAJOR']; + return _.sortBy(values, function (v) { + return order.indexOf(v.val); + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/status-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/status-facet.js new file mode 100644 index 00000000000..08db6d508f8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/status-facet.js @@ -0,0 +1,17 @@ +define([ + './base-facet', + '../templates' +], function (BaseFacet) { + + return BaseFacet.extend({ + template: Templates['issues-status-facet'], + + sortValues: function (values) { + var order = ['OPEN', 'RESOLVED', 'REOPENED', 'CLOSED', 'CONFIRMED']; + return _.sortBy(values, function (v) { + return order.indexOf(v.val); + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/facets/tag-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/tag-facet.js new file mode 100644 index 00000000000..7752451272b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/facets/tag-facet.js @@ -0,0 +1,56 @@ +define([ + './custom-values-facet' +], function (CustomValuesFacet) { + + return CustomValuesFacet.extend({ + prepareSearch: function () { + var url = baseUrl + '/api/issues/tags?ps=10', + tags = this.options.app.state.get('query').tags; + if (tags != null) { + url += '&tags=' + tags; + } + return this.$('.js-custom-value').select2({ + placeholder: 'Search...', + minimumInputLength: 0, + allowClear: false, + formatNoMatches: function () { + return t('select2.noMatches'); + }, + formatSearching: function () { + return t('select2.searching'); + }, + width: '100%', + ajax: { + quietMillis: 300, + url: url, + data: function (term) { + return { q: term, ps: 10 }; + }, + results: function (data) { + var results = data.tags.map(function (tag) { + return { id: tag, text: tag }; + }); + return { more: false, results: results }; + } + } + }); + }, + + getValuesWithLabels: function () { + var values = this.model.getValues(), + tags = this.options.app.facets.tags; + values.forEach(function (v) { + v.label = v.val; + v.extra = ''; + }); + return values; + }, + + serializeData: function () { + return _.extend(CustomValuesFacet.prototype.serializeData.apply(this, arguments), { + values: this.sortValues(this.getValuesWithLabels()) + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/filters-view.js b/server/sonar-web/src/main/js/apps/issues/filters-view.js new file mode 100644 index 00000000000..03813eff3c0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/filters-view.js @@ -0,0 +1,93 @@ +define([ + './templates' +], function () { + + var $ = jQuery; + + return Marionette.ItemView.extend({ + template: Templates['issues-filters'], + + events: { + 'click .js-toggle-filters': 'toggleFilters', + 'click .js-filter': 'applyFilter', + 'click .js-filter-save-as': 'saveAs', + 'click .js-filter-save': 'save', + 'click .js-filter-copy': 'copy', + 'click .js-filter-edit': 'edit' + }, + + initialize: function (options) { + var that = this; + this.listenTo(options.app.state, 'change:filter', this.render); + this.listenTo(options.app.state, 'change:changed', this.render); + this.listenTo(options.app.filters, 'all', this.render); + window.onSaveAs = window.onCopy = window.onEdit = function (id) { + $('#modal').dialog('close'); + return that.options.app.controller.fetchFilters().done(function () { + var filter = that.collection.get(id); + return filter.fetch().done(function () { + return that.options.app.controller.applyFilter(filter); + }); + }); + }; + }, + + onRender: function () { + this.$el.toggleClass('search-navigator-filters-selected', this.options.app.state.has('filter')); + }, + + toggleFilters: function (e) { + var that = this; + e.stopPropagation(); + this.$('.search-navigator-filters-list').toggle(); + return $('body').on('click.issues-filters', function () { + $('body').off('click.issues-filters'); + return that.$('.search-navigator-filters-list').hide(); + }); + }, + + applyFilter: function (e) { + var that = this; + var id = $(e.currentTarget).data('id'), + filter = this.collection.get(id); + return filter.fetch().done(function () { + return that.options.app.controller.applyFilter(filter); + }); + }, + + saveAs: function () { + var query = this.options.app.controller.getQuery('&'), + url = baseUrl + '/issues/save_as_form?' + query; + window.openModalWindow(url, {}); + }, + + save: function () { + var that = this; + var query = this.options.app.controller.getQuery('&'), + url = baseUrl + '/issues/save/' + (this.options.app.state.get('filter').id) + '?' + query; + return $.post(url).done(function () { + return that.options.app.state.set({ changed: false }); + }); + }, + + copy: function () { + var url = baseUrl + '/issues/copy_form/' + (this.options.app.state.get('filter').id); + window.openModalWindow(url, {}); + }, + + edit: function () { + var url = baseUrl + '/issues/edit_form/' + (this.options.app.state.get('filter').id); + window.openModalWindow(url, {}); + }, + + serializeData: function () { + var filter = this.options.app.state.get('filter'); + return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { + state: this.options.app.state.toJSON(), + filter: filter != null ? filter.toJSON() : null, + currentUser: window.SS.user + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/issue-filter-view.js b/server/sonar-web/src/main/js/apps/issues/issue-filter-view.js new file mode 100644 index 00000000000..67f9a617d45 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/issue-filter-view.js @@ -0,0 +1,29 @@ +define([ + 'components/issue/views/action-options-view', + './templates' +], function (ActionOptionsView) { + + var $ = jQuery; + + return ActionOptionsView.extend({ + template: Templates['issues-issue-filter-form'], + + selectInitialOption: function () { + return this.makeActive(this.getOptions().first()); + }, + + selectOption: function (e) { + var property = $(e.currentTarget).data('property'), + value = $(e.currentTarget).data('value'); + this.trigger('select', property, value); + return ActionOptionsView.prototype.selectOption.apply(this, arguments); + }, + + serializeData: function () { + return _.extend(ActionOptionsView.prototype.serializeData.apply(this, arguments), { + s: this.model.get('severity') + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/layout.js b/server/sonar-web/src/main/js/apps/issues/layout.js new file mode 100644 index 00000000000..06b2f893af6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/layout.js @@ -0,0 +1,55 @@ +define([ + './templates' +], function () { + + var $ = jQuery; + return Marionette.Layout.extend({ + template: Templates['issues-layout'], + + regions: { + filtersRegion: '.search-navigator-filters', + facetsRegion: '.search-navigator-facets', + workspaceHeaderRegion: '.search-navigator-workspace-header', + workspaceListRegion: '.search-navigator-workspace-list', + workspaceComponentViewerRegion: '.issues-workspace-component-viewer', + workspaceHomeRegion: '.issues-workspace-home' + }, + + onRender: function () { + if (this.options.app.state.get('isContext')) { + this.$(this.filtersRegion.el).addClass('hidden'); + } + $('.search-navigator').addClass('sticky'); + var top = $('.search-navigator').offset().top; + this.$('.search-navigator-workspace-header').css({ top: top }); + this.$('.search-navigator-side').css({ top: top }).isolatedScroll(); + }, + + showSpinner: function (region) { + return this[region].show(new Marionette.ItemView({ + template: _.template('<i class="spinner"></i>') + })); + }, + + showComponentViewer: function () { + this.scroll = $(window).scrollTop(); + $('.issues').addClass('issues-extended-view'); + }, + + hideComponentViewer: function () { + $('.issues').removeClass('issues-extended-view'); + if (this.scroll != null) { + $(window).scrollTop(this.scroll); + } + }, + + showHomePage: function () { + $('.issues').addClass('issues-home-view'); + }, + + hideHomePage: function () { + $('.issues').removeClass('issues-home-view'); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/models/facet.js b/server/sonar-web/src/main/js/apps/issues/models/facet.js new file mode 100644 index 00000000000..3716ba52d19 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/models/facet.js @@ -0,0 +1,20 @@ +define(function () { + + return Backbone.Model.extend({ + idAttribute: 'property', + + defaults: { + enabled: false + }, + + getValues: function () { + return this.get('values') || []; + }, + + toggle: function () { + var enabled = this.get('enabled'); + return this.set({ enabled: !enabled }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/models/facets.js b/server/sonar-web/src/main/js/apps/issues/models/facets.js new file mode 100644 index 00000000000..31c685de08d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/models/facets.js @@ -0,0 +1,9 @@ +define([ + './facet' +], function (Facet) { + + return Backbone.Collection.extend({ + model: Facet + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/models/filter.js b/server/sonar-web/src/main/js/apps/issues/models/filter.js new file mode 100644 index 00000000000..42b11507ee1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/models/filter.js @@ -0,0 +1,17 @@ +define(function () { + + return Backbone.Model.extend({ + url: function () { + return '/api/issue_filters/show/' + this.id; + }, + + parse: function (r) { + if (r.filter != null) { + return r.filter; + } else { + return r; + } + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/models/filters.js b/server/sonar-web/src/main/js/apps/issues/models/filters.js new file mode 100644 index 00000000000..bb66327e423 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/models/filters.js @@ -0,0 +1,9 @@ +define([ + './filter' +], function (Filter) { + + return Backbone.Collection.extend({ + model: Filter + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/models/issues.js b/server/sonar-web/src/main/js/apps/issues/models/issues.js new file mode 100644 index 00000000000..af41dd3fe12 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/models/issues.js @@ -0,0 +1,65 @@ +define([ + 'components/issue/models/issue' +], function (Issue) { + + return Backbone.Collection.extend({ + model: Issue, + + url: function () { + return baseUrl + '/api/issues/search'; + }, + + parseIssues: function (r) { + var find = function (source, key, keyField) { + var searchDict = {}; + searchDict[keyField || 'key'] = key; + return _.findWhere(source, searchDict) || key; + }; + return r.issues.map(function (issue, index) { + var component = find(r.components, issue.component), + project = find(r.projects, issue.project), + subProject = find(r.components, issue.subProject), + rule = find(r.rules, issue.rule), + assignee = find(r.users, issue.assignee, 'login'); + _.extend(issue, { index: index }); + if (component) { + _.extend(issue, { + componentUuid: component.uuid, + componentLongName: component.longName, + componentQualifier: component.qualifier + }); + } + if (project) { + _.extend(issue, { + projectLongName: project.longName, + projectUuid: project.uuid + }); + } + if (subProject) { + _.extend(issue, { + subProjectLongName: subProject.longName, + subProjectUuid: subProject.uuid + }); + } + if (rule) { + _.extend(issue, { + ruleName: rule.name + }); + } + if (assignee) { + _.extend(issue, { + assigneeEmail: assignee.email + }); + } + return issue; + }); + }, + + setIndex: function () { + return this.forEach(function (issue, index) { + return issue.set({ index: index }); + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/models/state.js b/server/sonar-web/src/main/js/apps/issues/models/state.js new file mode 100644 index 00000000000..38814d71111 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/models/state.js @@ -0,0 +1,58 @@ +define([ + 'components/navigator/models/state' +], function (State) { + + return State.extend({ + defaults: { + page: 1, + maxResultsReached: false, + query: {}, + facets: ['severities', 'resolutions'], + isContext: false, + allFacets: [ + 'issues', + 'severities', + 'resolutions', + 'statuses', + 'createdAt', + 'rules', + 'tags', + 'projectUuids', + 'moduleUuids', + 'directories', + 'fileUuids', + 'assignees', + 'reporters', + 'authors', + 'languages', + 'actionPlans' + ], + facetsFromServer: [ + 'severities', + 'statuses', + 'resolutions', + 'actionPlans', + 'projectUuids', + 'directories', + 'rules', + 'moduleUuids', + 'tags', + 'assignees', + 'reporters', + 'authors', + 'fileUuids', + 'languages', + 'createdAt' + ], + transform: { + 'resolved': 'resolutions', + 'assigned': 'assignees', + 'planned': 'actionPlans', + 'createdBefore': 'createdAt', + 'createdAfter': 'createdAt', + 'createdInLast': 'createdAt' + } + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/router.js b/server/sonar-web/src/main/js/apps/issues/router.js new file mode 100644 index 00000000000..519689e8f40 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/router.js @@ -0,0 +1,45 @@ +define([ + 'components/navigator/router' +], function (Router) { + + return Router.extend({ + routes: { + '': 'home', + ':query': 'index' + }, + + initialize: function (options) { + Router.prototype.initialize.apply(this, arguments); + this.listenTo(options.app.state, 'change:filter', this.updateRoute); + }, + + home: function () { + if (this.options.app.state.get('isContext')) { + return this.navigate('resolved=false', { trigger: true, replace: true }); + } else { + return this.options.app.controller.showHomePage(); + } + }, + + index: function (query) { + var that = this; + query = this.options.app.controller.parseQuery(query); + if (query.id != null) { + var filter = this.options.app.filters.get(query.id); + delete query.id; + return filter.fetch().done(function () { + if (Object.keys(query).length > 0) { + that.options.app.controller.applyFilter(filter, true); + that.options.app.state.setQuery(query); + that.options.app.state.set({ changed: true }); + } else { + that.options.app.controller.applyFilter(filter); + } + }); + } else { + return this.options.app.state.setQuery(query); + } + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/templates/_issues-filter-name.hbs b/server/sonar-web/src/main/js/apps/issues/templates/_issues-filter-name.hbs new file mode 100644 index 00000000000..22b760eac1a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/_issues-filter-name.hbs @@ -0,0 +1,18 @@ +{{#if filter.name}} + {{filter.name}} + <span class='note nowrap'> + {{#unless filter.shared}} + [{{t 'issue_filter.private'}}] + {{else}} + {{#eq filter.user currentUser}} + [{{t 'issue_filter.shared_with_all_users'}}] + {{else}} + {{#if filter.user}} + [{{t 'issue_filter.shared'}}] + {{/if}} + {{/eq}} + {{/unless}} + </span> +{{else}} + {{t 'issues'}} +{{/if}} diff --git a/server/sonar-web/src/main/js/apps/issues/templates/facets/_issues-facet-header.hbs b/server/sonar-web/src/main/js/apps/issues/templates/facets/_issues-facet-header.hbs new file mode 100644 index 00000000000..8e012d1fd67 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/facets/_issues-facet-header.hbs @@ -0,0 +1,4 @@ +<a class='search-navigator-facet-header js-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/js/apps/issues/templates/facets/issues-action-plan-facet.hbs b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-action-plan-facet.hbs new file mode 100644 index 00000000000..e934608d214 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-action-plan-facet.hbs @@ -0,0 +1,18 @@ +{{> '_issues-facet-header'}} + +<div class='search-navigator-facet-list'> + {{#each values}} + {{#eq val ''}} + {{! unplanned }} + <a class='facet search-navigator-facet js-facet' data-unplanned title='{{t 'issue.unplanned'}}'> + <span class='facet-name'>{{t 'issue.unplanned'}}</span> + <span class='facet-stat'>{{numberShort count}}</span> + </a> + {{else}} + <a class='facet search-navigator-facet js-facet' data-value='{{val}}' title='{{label}}'> + <span class='facet-name'>{{label}}</span> + <span class='facet-stat'>{{numberShort count}}</span> + </a> + {{/eq}} + {{/each}} +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-assignee-facet.hbs b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-assignee-facet.hbs new file mode 100644 index 00000000000..44d4b341f72 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-assignee-facet.hbs @@ -0,0 +1,30 @@ +{{> '_issues-facet-header'}} + +<div class='search-navigator-facet-list'> + {{#notNull myIssues}} + <a class='facet search-navigator-facet js-facet' data-value='__me__' title='{{t 'me'}}'> + <span class='facet-name'>{{t 'me'}}</span> + <span class='facet-stat'>{{myIssues}}</span> + </a> + <hr> + {{/notNull}} + + {{#each values}} + {{#eq val ''}} + {{! unassigned }} + <a class='facet search-navigator-facet js-facet' data-unassigned title='{{t 'unassigned'}}'> + <span class='facet-name'>{{t 'unassigned'}}</span> + <span class='facet-stat'>{{numberShort count}}</span> + </a> + {{else}} + <a class='facet search-navigator-facet js-facet' data-value='{{val}}' title='{{label}}'> + <span class='facet-name'>{{label}}</span> + <span class='facet-stat'>{{numberShort count}}</span> + </a> + {{/eq}} + {{/each}} + + <div class='search-navigator-facet-custom-value'> + <input type='hidden' class='js-custom-value'> + </div> +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-base-facet.hbs b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-base-facet.hbs new file mode 100644 index 00000000000..d8b5f0b0179 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-base-facet.hbs @@ -0,0 +1,10 @@ +{{> '_issues-facet-header'}} + +<div class='search-navigator-facet-list'> + {{#each values}} + <a class='facet search-navigator-facet js-facet' data-value='{{val}}' title='{{default label val}}'> + <span class='facet-name'>{{default label val}}</span> + <span class='facet-stat'>{{numberShort count}}</span> + </a> + {{/each}} +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-context-facet.hbs b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-context-facet.hbs new file mode 100644 index 00000000000..5c570f8718c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-context-facet.hbs @@ -0,0 +1,3 @@ +<div class='search-navigator-facet-query'> + Issues of {{qualifierIcon state.contextComponentQualifier}} {{state.contextComponentName}} +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-creation-date-facet.hbs b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-creation-date-facet.hbs new file mode 100644 index 00000000000..9003a170522 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-creation-date-facet.hbs @@ -0,0 +1,32 @@ +{{> '_issues-facet-header'}} + +{{#if createdAt}} + <input type='hidden' name='createdAt'> + <div class='search-navigator-facet-container'> + {{dt createdAt}} ({{fromNow createdAt}}) + </div> +{{else}} + <div class='search-navigator-facet-container'> + <div class='js-barchart' data-height='75' {{#if periodEnd}}data-end-date='{{periodEnd}}'{{/if}}></div> + <div class='search-navigator-date-facet-selection'> + <a class='js-select-period-start search-navigator-date-facet-selection-dropdown-left'> + {{#if periodStart}}{{d periodStart}}{{else}}Past{{/if}} <i class='icon-dropdown'></i> + </a> + <a class='js-select-period-end search-navigator-date-facet-selection-dropdown-right'> + {{#if periodEnd}}{{d periodEnd}}{{else}}Now{{/if}} <i class='icon-dropdown'></i> + </a> + <input class='js-period-start search-navigator-date-facet-selection-input-left' + type='text' value='{{#if periodStart}}{{ds periodStart}}{{/if}}' name='createdAfter'> + <input class='js-period-end search-navigator-date-facet-selection-input-right' + type='text' value='{{#if periodEnd}}{{ds periodEnd}}{{/if}}' name='createdBefore'> + </div> + + <div class='spacer-top'> + <span class='spacer-right'>{{t 'issues.facet.createdAt.or'}}</span> + <a class='js-all spacer-right' href='#'>{{t 'issues.facet.createdAt.all'}}</a> + <a class='js-last-week spacer-right {{#eq createdInLast '1w'}}active-link{{/eq}}' href='#'>{{t 'issues.facet.createdAt.last_week'}}</a> + <a class='js-last-month spacer-right {{#eq createdInLast '1m'}}active-link{{/eq}}' href='#'>{{t 'issues.facet.createdAt.last_month'}}</a> + <a class='js-last-year {{#eq createdInLast '1y'}}active-link{{/eq}}' href='#'>{{t 'issues.facet.createdAt.last_year'}}</a> + </div> + </div> +{{/if}} diff --git a/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-custom-values-facet.hbs b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-custom-values-facet.hbs new file mode 100644 index 00000000000..30372e84ae0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-custom-values-facet.hbs @@ -0,0 +1,14 @@ +{{> '_issues-facet-header'}} + +<div class='search-navigator-facet-list'> + {{#each values}} + <a class='facet search-navigator-facet js-facet' data-value='{{val}}' title='{{#if extra}}({{extra}}) {{/if}}{{default label val}}'> + <span class='facet-name'>{{default label val}}</span> + <span class='facet-stat'>{{numberShort count}}</span> + </a> + {{/each}} + + <div class='search-navigator-facet-custom-value'> + <input type='hidden' class='js-custom-value'> + </div> +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-file-facet.hbs b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-file-facet.hbs new file mode 100644 index 00000000000..c070fb7785a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-file-facet.hbs @@ -0,0 +1,10 @@ +{{> '_issues-facet-header'}} + +<div class='search-navigator-facet-list search-navigator-facet-list-align-right'> + {{#each values}} + <a class='facet search-navigator-facet js-facet' data-value='{{val}}' title='{{default label val}}'> + <span class='facet-name'>{{default label val}}</span> + <span class='facet-stat'>{{numberShort count}}</span> + </a> + {{/each}} +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-issue-key-facet.hbs b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-issue-key-facet.hbs new file mode 100644 index 00000000000..540e9a00014 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-issue-key-facet.hbs @@ -0,0 +1,7 @@ +{{> '_issues-facet-header'}} + +<div class='search-navigator-facet-container'> + <div class='facet search-navigator-facet active' style='cursor: default;'> + <span class='facet-name'>{{issues}}</span> + </div> +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-resolution-facet.hbs b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-resolution-facet.hbs new file mode 100644 index 00000000000..6fb81c5548f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-resolution-facet.hbs @@ -0,0 +1,20 @@ +{{> '_issues-facet-header'}} + +<div class='search-navigator-facet-list'> + {{#each values}} + {{#eq val ''}} + {{! unresolved }} + <a class='facet search-navigator-facet search-navigator-facet-half js-facet' data-unresolved + title='{{t 'issue.unresolved.description'}}' data-toggle='tooltip' data-placement='right'> + <span class='facet-name'>{{t 'unresolved'}}</span> + <span class='facet-stat'>{{numberShort count}}</span> + </a> + {{else}} + <a class='facet search-navigator-facet search-navigator-facet-half js-facet' data-value='{{val}}' + title='{{t 'issue.resolution' val 'description'}}' data-toggle='tooltip' data-placement='right'> + <span class='facet-name'>{{t 'issue.resolution' val}}</span> + <span class='facet-stat'>{{numberShort count}}</span> + </a> + {{/eq}} + {{/each}} +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-severity-facet.hbs b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-severity-facet.hbs new file mode 100644 index 00000000000..92ff7b52444 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-severity-facet.hbs @@ -0,0 +1,11 @@ +{{> '_issues-facet-header'}} + +<div class='search-navigator-facet-list'> + {{#each values}} + <a class='facet search-navigator-facet search-navigator-facet-half js-facet' + data-value='{{val}}' title='{{t 'severity' val 'description'}}' data-toggle='tooltip' data-placement='right'> + <span class='facet-name'>{{severityIcon val}} {{t 'severity' val}}</span> + <span class='facet-stat'>{{numberShort count}}</span> + </a> + {{/each}} +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-status-facet.hbs b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-status-facet.hbs new file mode 100644 index 00000000000..55a78a63bf1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/facets/issues-status-facet.hbs @@ -0,0 +1,11 @@ +{{> '_issues-facet-header'}} + +<div class='search-navigator-facet-list'> + {{#each values}} + <a class='facet search-navigator-facet search-navigator-facet-half js-facet' + data-value='{{val}}' title='{{t 'issue.status' val 'description'}}' data-toggle='tooltip' data-placement='right'> + <span class='facet-name'>{{statusIcon val}} {{t 'issue.status' val}}</span> + <span class='facet-stat'>{{numberShort count}}</span> + </a> + {{/each}} +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/issues-filters.hbs b/server/sonar-web/src/main/js/apps/issues/templates/issues-filters.hbs new file mode 100644 index 00000000000..e010deb9a0e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/issues-filters.hbs @@ -0,0 +1,55 @@ +<h1 class='page-title dropdown'> + {{#if state.canManageFilters}} + <a class='search-navigator-filters-show-list dropdown-toggle' data-toggle='dropdown'> + <i class='icon-list'></i><span class='issues-filters-name'>{{> '_issues-filter-name'}}</span> + </a> + <ul class='dropdown-menu'> + {{#each items}} + <li> + <a class='search-navigator-filters-button search-navigator-filters-filter js-filter' data-id='{{id}}'> + {{name}} + </a> + </li> + {{/each}} + {{#notEmpty items}} + <li class='divider'></li> + {{/notEmpty}} + <li> + <a class='search-navigator-filters-manage' href='{{link '/issues/manage'}}'>{{t 'manage'}}</a> + </li> + </ul> + {{#if filter.description}} + <div class='search-navigator-filters-description'>{{filter.description}}</div> + {{/if}} + {{else}} + <span class='search-navigator-filters-name'>{{t 'issues'}}</span> + {{/if}} +</h1> + +<div class='page-actions'> + <div class='button-group'> + {{#if state.canManageFilters}} + {{#if filter.canModify}} + {{#if state.changed}} + <button class='js-filter-save' id='issues-filter-save'>{{t 'save'}}</button> + {{/if}} + {{/if}} + + {{#unless filter.id}} + <button class='js-filter-save-as' id='issues-filter-save-as'>{{t 'save_as'}}</button> + {{/unless}} + + {{#if filter.id}} + {{#unless state.changed}} + <button class='js-filter-copy' id='issues-filter-copy'>{{t 'copy'}}</button> + {{/unless}} + {{/if}} + + {{#if filter.canModify}} + {{#if filter.id}} + <button class='js-filter-edit' id='issues-filter-edit'>{{t 'edit'}}</button> + {{/if}} + {{/if}} + {{/if}} + </div> +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/issues-issue-filter-form.hbs b/server/sonar-web/src/main/js/apps/issues/templates/issues-issue-filter-form.hbs new file mode 100644 index 00000000000..1954f67f62d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/issues-issue-filter-form.hbs @@ -0,0 +1,71 @@ +<h6>{{t 'issue.filter_similar_issues'}}</h6> + +<div class='issue-action-options'> + <a href='#' class='issue-action-option' data-property='severities' data-value='{{s}}'> + {{severityIcon severity}} {{t 'severity' severity}} + </a> + + <a href='#' class='issue-action-option' data-property='statuses' data-value='{{status}}'> + {{statusIcon status}} {{t 'issue.status' status}} + </a> + + {{#if resolution}} + <a href='#' class='issue-action-option' data-property='resolutions' data-value='{{resolution}}'> + {{t 'issue.resolution' resolution}} + </a> + {{else}} + <a href='#' class='issue-action-option' data-property='resolved' data-value='false'> + {{t 'unresolved'}} + </a> + {{/if}} + + {{#if assignee}} + <a href='#' class='issue-action-option' data-property='assignees' data-value='{{assignee}}'> + {{t 'assigned_to'}} {{assigneeName}} + </a> + {{else}} + <a href='#' class='issue-action-option' data-property='assigned' data-value='false'> + {{t 'unassigned'}} + </a> + {{/if}} + + {{#if actionPlan}} + <a href='#' class='issue-action-option' data-property='actionPlans' data-value='{{actionPlan}}'> + {{t 'issue.planned_for'}} {{actionPlanName}} + </a> + {{else}} + <a href='#' class='issue-action-option' data-property='planned' data-value='false'> + {{t 'issue.unplanned'}} + </a> + {{/if}} + + <hr> + + <a href='#' class='issue-action-option' data-property='rules' data-value='{{rule}}'> + {{limitString ruleName}} + </a> + + {{#each tags}} + <a href='#' class='issue-action-option' data-property='tags' data-value='{{this}}'> + <i class='icon-tags icon-half-transparent'></i> {{this}} + </a> + {{/each}} + + <hr> + + <a href='#' class='issue-action-option' data-property='projectUuids' data-value='{{projectUuid}}'> + {{qualifierIcon 'TRK'}} {{projectLongName}} + </a> + + {{#if subProject}} + <a href='#' class='issue-action-option' data-property='moduleUuids' data-value='{{subProjectUuid}}'> + {{qualifierIcon 'BRC'}} {{subProjectLongName}} + </a> + {{/if}} + + <a href='#' class='issue-action-option' data-property='fileUuids' data-value='{{componentUuid}}'> + {{qualifierIcon componentQualifier}} {{fileFromPath componentLongName}} + </a> +</div> + +<div class='bubble-popup-arrow'></div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/issues-issue-filter.hbs b/server/sonar-web/src/main/js/apps/issues/templates/issues-issue-filter.hbs new file mode 100644 index 00000000000..3ab307c3958 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/issues-issue-filter.hbs @@ -0,0 +1,5 @@ +<div class='issue-meta'> + <a class='issue-action issue-action-with-options js-issue-filter' href='#'> + <i class='icon-filter icon-half-transparent'></i> <i class='icon-dropdown'></i> + </a> +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/issues-layout.hbs b/server/sonar-web/src/main/js/apps/issues/templates/issues-layout.hbs new file mode 100644 index 00000000000..cd434ab2340 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/issues-layout.hbs @@ -0,0 +1,11 @@ +<div class='search-navigator-side'> + <div class='search-navigator-filters'></div> + <div class='search-navigator-facets'></div> +</div> + +<div class='search-navigator-workspace'> + <div class='search-navigator-workspace-header'></div> + <div class='search-navigator-workspace-list'></div> + <div class='issues-workspace-component-viewer'></div> + <div class='issues-workspace-home'></div> +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/issues-workspace-header.hbs b/server/sonar-web/src/main/js/apps/issues/templates/issues-workspace-header.hbs new file mode 100644 index 00000000000..2c7d2d6e3ee --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/issues-workspace-header.hbs @@ -0,0 +1,37 @@ +<div class='issues-header-component nowrap'> + {{#if state.component}} + <a class='js-back'>{{t 'issues.return_to_list'}}</a> + + {{#with state.component}} + {{qualifierIcon 'TRK'}} <a href='{{dashboardUrl project}}' title='{{projectName}}'>{{projectName}}</a> + + {{qualifierIcon qualifier}} <a href='{{dashboardUrl key}}' title='{{name}}'>{{name}}</a> + {{/with}} + {{else}} + + {{/if}} +</div> + + +<div class='search-navigator-header-actions'> + {{#notNull state.total}} + <div class='search-navigator-header-pagination'> + {{#gt state.total 0}} + <a class='js-prev icon-prev' title='{{t 'paging_previous'}}'></a> + <span class='current'>{{sum state.selectedIndex 1}} / <span id='issues-total'>{{state.total}}</span></span> + <a class='js-next icon-next' title='{{t 'paging_next'}}'></a> + {{else}} + <span class='current'>0 / <span id='issues-total'>0</span></span> + {{/gt}} + </div> + {{/notNull}} + + + <div class='search-navigator-header-buttons button-group'> + <button id='issues-reload' class='js-reload'>{{t 'reload'}}</button> + <button class='js-new-search' id='issues-new-search'>{{t 'issue_filter.new_search'}}</button> + {{#if state.canBulkChange}} + <button id='issues-bulk-change' class='js-bulk-change'>{{t 'bulk_change'}}</button> + {{/if}} + </div> +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/issues-workspace-home.hbs b/server/sonar-web/src/main/js/apps/issues/templates/issues-workspace-home.hbs new file mode 100644 index 00000000000..374dad390b7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/issues-workspace-home.hbs @@ -0,0 +1,77 @@ +<div class='spacer-top spacer-bottom'> + <div class='columns'> + <div class='column-half {{#unless user}}column-one{{/unless}}'> + <h3 class='text-center'>{{t 'issues.home.recent_issues'}}</h3> + <p class='note text-center'>({{t 'issues.home.over_last_week'}})</p> + + <div class='spacer-top text-center js-barchart' data-height='75' data-width='300'></div> + <h4 class='spacer-top spacer-bottom text-center'>{{t 'issues.home.projects'}}</h4> + <table class='data zebra spacer-top'> + {{#each projects}} + <tr> + <td>{{qualifierIcon 'TRK'}} <a href='{{issuesHomeLink 'projectUuids' val}}'>{{label}}</a></td> + <td class='thin text-right'>+{{numberShort count}}</td> + </tr> + {{/each}} + </table> + <h4 class='spacer-top spacer-bottom text-center'>{{t 'issues.home.authors'}}</h4> + <table class='data zebra spacer-top'> + {{#each authors}} + <tr> + <td><a href='{{issuesHomeLink 'authors' val}}'>{{val}}</a></td> + <td class='thin text-right'>+{{numberShort count}}</td> + </tr> + {{/each}} + </table> + <h4 class='spacer-top spacer-bottom text-center'>{{t 'issues.home.tags'}}</h4> + <ul class='list-inline'> + {{#each tags}} + <li><a class='link-no-underline' style='font-size: {{size}}px;' data-toggle='tooltip' data-placement='bottom' + href='{{issuesHomeLink 'tags' val}}' + title='+{{numberShort count}}'>{{val}}</a></li> + {{/each}} + </ul> + </div> + + {{#if user}} + <div class='column-half'> + <h3 class='text-center'>{{t 'issues.home.my_recent_issues'}}</h3> + <p class='note text-center'>({{t 'issues.home.over_last_week'}})</p> + + <div class='spacer-top text-center js-my-barchart' data-height='75' data-width='300'></div> + {{#notEmpty myProjects}} + <h4 class='spacer-top spacer-bottom text-center'>{{t 'issues.home.projects'}}</h4> + <table class='data zebra spacer-top'> + {{#each myProjects}} + <tr> + <td>{{qualifierIcon 'TRK'}} <a href='{{myIssuesHomeLink 'projectUuids' val}}'>{{label}}</a></td> + <td class='thin text-right'>+{{numberShort count}}</td> + </tr> + {{/each}} + </table> + {{/notEmpty}} + {{#notEmpty myTags}} + <h4 class='spacer-top spacer-bottom text-center'>{{t 'issues.home.tags'}}</h4> + <ul class='list-inline'> + {{#each myTags}} + <li><a class='link-no-underline' style='font-size: {{size}}px;' data-toggle='tooltip' data-placement='bottom' + href='{{myIssuesHomeLink 'tags' val}}' + title='+{{numberShort count}}'>{{val}}</a></li> + {{/each}} + </ul> + {{/notEmpty}} + {{#notEmpty filters}} + <h3 class='spacer-bottom text-center' style='padding-top: 12px; margin-top: 20px; border-top: 1px solid #efefef;'> + {{t 'issues.home.my_filters'}}</h3> + <ul class='list-inline'> + {{#each filters}} + <li> + <a href='{{issueFilterHomeLink id}}'>{{name}}</a> + </li> + {{/each}} + </ul> + {{/notEmpty}} + </div> + {{/if}} + </div> +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/issues-workspace-list-component.hbs b/server/sonar-web/src/main/js/apps/issues/templates/issues-workspace-list-component.hbs new file mode 100644 index 00000000000..73d15ac8fbe --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/issues-workspace-list-component.hbs @@ -0,0 +1,13 @@ +<div class='issues-workspace-list-component'> + <a class='issues-workspace-list-component-part' href='{{dashboardUrl project}}'> + {{qualifierIcon 'TRK'}} {{projectLongName}} + </a> + {{#if subProject}} + <a class='issues-workspace-list-component-part' href='{{dashboardUrl subProject}}'> + {{qualifierIcon 'TRK'}} {{subProjectLongName}} + </a> + {{/if}} + <a class='issues-workspace-list-component-part' href='{{dashboardUrl component}}'> + {{qualifierIcon componentQualifier}} {{componentLongName}} + </a> +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/templates/issues-workspace-list.hbs b/server/sonar-web/src/main/js/apps/issues/templates/issues-workspace-list.hbs new file mode 100644 index 00000000000..a81b673736c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/templates/issues-workspace-list.hbs @@ -0,0 +1,5 @@ +<div class='js-list'></div> + +<div class='search-navigator-workspace-list-more js-more'> + <i class='spinner'></i> +</div> diff --git a/server/sonar-web/src/main/js/apps/issues/workspace-header-view.js b/server/sonar-web/src/main/js/apps/issues/workspace-header-view.js new file mode 100644 index 00000000000..620b68bfccd --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/workspace-header-view.js @@ -0,0 +1,47 @@ +define([ + 'components/navigator/workspace-header-view', + './templates' +], function (WorkspaceHeaderView) { + + var $ = jQuery; + + return WorkspaceHeaderView.extend({ + template: Templates['issues-workspace-header'], + + events: function () { + return _.extend(WorkspaceHeaderView.prototype.events.apply(this, arguments), { + 'click .js-back': 'returnToList', + 'click .js-new-search': 'newSearch' + }); + }, + + initialize: function () { + var that = this; + WorkspaceHeaderView.prototype.initialize.apply(this, arguments); + this._onBulkIssues = window.onBulkIssues; + window.onBulkIssues = function () { + $('#modal').dialog('close'); + return that.options.app.controller.fetchList(); + }; + }, + + onClose: function () { + window.onBulkIssues = this._onBulkIssues; + }, + + returnToList: function () { + this.options.app.controller.closeComponentViewer(); + }, + + newSearch: function () { + this.options.app.controller.newSearch(); + }, + + bulkChange: function () { + var query = this.options.app.controller.getQuery('&', true), + url = baseUrl + '/issues/bulk_change_form?' + query; + window.openModalWindow(url, {}); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/workspace-home-view.js b/server/sonar-web/src/main/js/apps/issues/workspace-home-view.js new file mode 100644 index 00000000000..408900fb63e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/workspace-home-view.js @@ -0,0 +1,160 @@ +define([ + 'widgets/issue-filter/widget', + './templates' +], function (IssueFilter) { + + var $ = jQuery; + + Handlebars.registerHelper('issuesHomeLink', function (property, value) { + return baseUrl + '/issues/search#resolved=false|createdInLast=1w|' + + property + '=' + (encodeURIComponent(value)); + }); + + Handlebars.registerHelper('myIssuesHomeLink', function (property, value) { + return baseUrl + '/issues/search#resolved=false|createdInLast=1w|assignees=__me__|' + + property + '=' + (encodeURIComponent(value)); + }); + + Handlebars.registerHelper('issueFilterHomeLink', function (id) { + return baseUrl + '/issues/search#id=' + id; + }); + + return Marionette.ItemView.extend({ + template: Templates['issues-workspace-home'], + + modelEvents: { + 'change': 'render' + }, + + events: { + 'click .js-barchart rect': 'selectBar', + 'click .js-my-barchart rect': 'selectMyBar' + }, + + initialize: function () { + this.model = new Backbone.Model(); + this.requestIssues(); + this.requestMyIssues(); + }, + + _getProjects: function (r) { + var projectFacet = _.findWhere(r.facets, { property: 'projectUuids' }); + if (projectFacet != null) { + var values = _.head(projectFacet.values, 3); + values.forEach(function (v) { + var project = _.findWhere(r.projects, { uuid: v.val }); + v.label = project.longName; + }); + return values; + } + }, + + _getAuthors: function (r) { + var authorFacet = _.findWhere(r.facets, { property: 'authors' }); + if (authorFacet != null) { + return _.head(authorFacet.values, 3); + } + }, + + _getTags: function (r) { + var MIN_SIZE = 10, + MAX_SIZE = 24, + tagFacet = _.findWhere(r.facets, { property: 'tags' }); + if (tagFacet != null) { + var values = _.head(tagFacet.values, 10), + minCount = _.min(values, function (v) { + return v.count; + }).count, + maxCount = _.max(values, function (v) { + return v.count; + }).count, + scale = d3.scale.linear().domain([minCount, maxCount]).range([MIN_SIZE, MAX_SIZE]); + values.forEach(function (v) { + v.size = scale(v.count); + }); + return values; + } + }, + + requestIssues: function () { + var that = this; + var url = baseUrl + '/api/issues/search', + options = { + resolved: false, + createdInLast: '1w', + ps: 1, + facets: 'createdAt,projectUuids,authors,tags' + }; + return $.get(url, options).done(function (r) { + var createdAt = _.findWhere(r.facets, { property: 'createdAt' }); + that.model.set({ + createdAt: createdAt != null ? createdAt.values : null, + projects: that._getProjects(r), + authors: that._getAuthors(r), + tags: that._getTags(r) + }); + }); + }, + + requestMyIssues: function () { + var that = this; + var url = baseUrl + '/api/issues/search', + options = { + resolved: false, + createdInLast: '1w', + assignees: '__me__', + ps: 1, + facets: 'createdAt,projectUuids,authors,tags' + }; + return $.get(url, options).done(function (r) { + var createdAt = _.findWhere(r.facets, { property: 'createdAt' }); + return that.model.set({ + myCreatedAt: createdAt != null ? createdAt.values : null, + myProjects: that._getProjects(r), + myTags: that._getTags(r) + }); + }); + }, + + onRender: function () { + var values = this.model.get('createdAt'), + myValues = this.model.get('myCreatedAt'); + if (values != null) { + this.$('.js-barchart').barchart(values); + } + if (myValues != null) { + this.$('.js-my-barchart').barchart(myValues); + } + this.$('[data-toggle="tooltip"]').tooltip({ container: 'body' }); + }, + + selectBar: function (e) { + var periodStart = $(e.currentTarget).data('period-start'), + periodEnd = $(e.currentTarget).data('period-end'); + this.options.app.state.setQuery({ + resolved: false, + createdAfter: periodStart, + createdBefore: periodEnd + }); + }, + + selectMyBar: function (e) { + var periodStart = $(e.currentTarget).data('period-start'), + periodEnd = $(e.currentTarget).data('period-end'); + this.options.app.state.setQuery({ + resolved: false, + assignees: '__me__', + createdAfter: periodStart, + createdBefore: periodEnd + }); + }, + + serializeData: function () { + return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { + user: window.SS.user, + filters: _.sortBy(this.options.app.filters.toJSON(), 'name') + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/workspace-list-empty-view.js b/server/sonar-web/src/main/js/apps/issues/workspace-list-empty-view.js new file mode 100644 index 00000000000..ada57f15a2d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/workspace-list-empty-view.js @@ -0,0 +1,11 @@ +define(function () { + + return Marionette.ItemView.extend({ + className: 'search-navigator-no-results', + + template: function () { + return t('issue_filter.no_issues'); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/workspace-list-item-view.js b/server/sonar-web/src/main/js/apps/issues/workspace-list-item-view.js new file mode 100644 index 00000000000..6d3df9c6901 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/workspace-list-item-view.js @@ -0,0 +1,113 @@ +define([ + 'components/issue/issue-view', + './issue-filter-view', + './templates' +], function (IssueView, IssueFilterView) { + + var $ = jQuery, + SHOULD_NULL = { + any: ['issues'], + resolutions: ['resolved'], + resolved: ['resolutions'], + assignees: ['assigned'], + assigned: ['assignees'], + actionPlans: ['planned'], + planned: ['actionPlans'] + }; + + return IssueView.extend({ + filterTemplate: Templates['issues-issue-filter'], + + events: function () { + return _.extend(IssueView.prototype.events.apply(this, arguments), { + 'click': 'selectCurrent', + 'dblclick': 'openComponentViewer', + 'click .js-issue-navigate': 'openComponentViewer', + 'click .js-issue-filter': 'onIssueFilterClick' + }); + }, + + initialize: function (options) { + IssueView.prototype.initialize.apply(this, arguments); + this.listenTo(options.app.state, 'change:selectedIndex', this.select); + }, + + onRender: function () { + IssueView.prototype.onRender.apply(this, arguments); + this.select(); + this.addFilterSelect(); + this.$el.addClass('issue-navigate-right'); + }, + + onIssueFilterClick: function (e) { + var that = this; + e.preventDefault(); + e.stopPropagation(); + $('body').click(); + this.popup = new IssueFilterView({ + triggerEl: $(e.currentTarget), + bottomRight: true, + model: this.model + }); + this.popup.on('select', function (property, value) { + var obj; + obj = {}; + obj[property] = '' + value; + SHOULD_NULL.any.forEach(function (p) { + obj[p] = null; + }); + if (SHOULD_NULL[property] != null) { + SHOULD_NULL[property].forEach(function (p) { + obj[p] = null; + }); + } + that.options.app.state.updateFilter(obj); + that.popup.close(); + }); + this.popup.render(); + }, + + addFilterSelect: function () { + this.$('.issue-table-meta-cell-first') + .find('.issue-meta-list') + .append(this.filterTemplate(this.model.toJSON())); + }, + + select: function () { + var selected = this.model.get('index') === this.options.app.state.get('selectedIndex'); + this.$el.toggleClass('selected', selected); + }, + + selectCurrent: function () { + this.options.app.state.set({ selectedIndex: this.model.get('index') }); + }, + + resetIssue: function (options) { + var that = this; + var key = this.model.get('key'), + componentUuid = this.model.get('componentUuid'), + index = this.model.get('index'); + this.model.clear({ silent: true }); + this.model.set({ key: key, componentUuid: componentUuid, index: index }, { silent: true }); + return this.model.fetch(options).done(function () { + return that.trigger('reset'); + }); + }, + + openComponentViewer: function () { + this.options.app.state.set({ selectedIndex: this.model.get('index') }); + if (this.options.app.state.has('component')) { + return this.options.app.controller.closeComponentViewer(); + } else { + return this.options.app.controller.showComponentViewer(this.model); + } + }, + + serializeData: function () { + return _.extend(IssueView.prototype.serializeData.apply(this, arguments), { + showComponent: true + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/issues/workspace-list-view.js b/server/sonar-web/src/main/js/apps/issues/workspace-list-view.js new file mode 100644 index 00000000000..37e7ade70e9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/workspace-list-view.js @@ -0,0 +1,106 @@ +define([ + 'components/navigator/workspace-list-view', + './workspace-list-item-view', + './workspace-list-empty-view', + './templates' +], function (WorkspaceListView, IssueView, EmptyView) { + + var $ = jQuery, + COMPONENT_HEIGHT = 29, + BOTTOM_OFFSET = 10; + + return WorkspaceListView.extend({ + template: Templates['issues-workspace-list'], + componentTemplate: Templates['issues-workspace-list-component'], + itemView: IssueView, + itemViewContainer: '.js-list', + emptyView: EmptyView, + + bindShortcuts: function () { + var that = this; + var doAction = function (action) { + var selectedIssue = that.collection.at(that.options.app.state.get('selectedIndex')); + if (selectedIssue == null) { + return; + } + var selectedIssueView = that.children.findByModel(selectedIssue); + selectedIssueView.$('.js-issue-' + action).click(); + }; + WorkspaceListView.prototype.bindShortcuts.apply(this, arguments); + key('right', 'list', function () { + var selectedIssue = that.collection.at(that.options.app.state.get('selectedIndex')); + that.options.app.controller.showComponentViewer(selectedIssue); + return false; + }); + key('f', 'list', function () { + return doAction('transition'); + }); + key('a', 'list', function () { + return doAction('assign'); + }); + key('m', 'list', function () { + return doAction('assign-to-me'); + }); + key('p', 'list', function () { + return doAction('plan'); + }); + key('i', 'list', function () { + return doAction('set-severity'); + }); + key('c', 'list', function () { + return doAction('comment'); + }); + return key('t', 'list', function () { + return doAction('edit-tags'); + }); + }, + + scrollTo: function () { + var selectedIssue = this.collection.at(this.options.app.state.get('selectedIndex')); + if (selectedIssue == null) { + return; + } + var selectedIssueView = this.children.findByModel(selectedIssue), + parentTopOffset = this.$el.offset().top, + viewTop = selectedIssueView.$el.offset().top - parentTopOffset; + if (selectedIssueView.$el.prev().is('.issues-workspace-list-component')) { + viewTop -= COMPONENT_HEIGHT; + } + var viewBottom = selectedIssueView.$el.offset().top + selectedIssueView.$el.outerHeight() + BOTTOM_OFFSET, + windowTop = $(window).scrollTop(), + windowBottom = windowTop + $(window).height(); + if (viewTop < windowTop) { + $(window).scrollTop(viewTop); + } + if (viewBottom > windowBottom) { + $(window).scrollTop($(window).scrollTop() - windowBottom + viewBottom); + } + }, + + appendHtml: function (compositeView, itemView, index) { + var $container = this.getItemViewContainer(compositeView), + model = this.collection.at(index); + if (model != null) { + var prev = this.collection.at(index - 1), + putComponent = prev == null; + if (prev != null) { + var fullComponent = [model.get('project'), model.get('component')].join(' '), + fullPrevComponent = [prev.get('project'), prev.get('component')].join(' '); + if (fullComponent !== fullPrevComponent) { + putComponent = true; + } + } + if (putComponent) { + $container.append(this.componentTemplate(model.toJSON())); + } + } + $container.append(itemView.el); + }, + + closeChildren: function () { + WorkspaceListView.prototype.closeChildren.apply(this, arguments); + this.$('.issues-workspace-list-component').remove(); + } + }); + +}); |