aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps/issues
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main/js/apps/issues')
-rw-r--r--server/sonar-web/src/main/js/apps/issues/component-viewer/issue-view.js35
-rw-r--r--server/sonar-web/src/main/js/apps/issues/component-viewer/main.js214
-rw-r--r--server/sonar-web/src/main/js/apps/issues/controller.js8
-rw-r--r--server/sonar-web/src/main/js/apps/issues/templates/issues-issue-checkbox.hbs3
-rw-r--r--server/sonar-web/src/main/js/apps/issues/templates/issues-issue-filter.hbs6
-rw-r--r--server/sonar-web/src/main/js/apps/issues/workspace-list-item-view.js101
-rw-r--r--server/sonar-web/src/main/js/apps/issues/workspace-list-view.js23
7 files changed, 134 insertions, 256 deletions
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
deleted file mode 100644
index 95e18194208..00000000000
--- a/server/sonar-web/src/main/js/apps/issues/component-viewer/issue-view.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import IssueView from '../workspace-list-item-view';
-
-export default IssueView.extend({
- onRender () {
- IssueView.prototype.onRender.apply(this, arguments);
- this.$el.removeClass('issue-navigate-right issue-with-checkbox');
- },
-
- serializeData () {
- return {
- ...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
index 0f08c21112a..49d7152c45f 100644
--- 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
@@ -18,186 +18,114 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import $ from 'jquery';
-import SourceViewer from '../../../components/source-viewer/main';
-import IssueView from './issue-view';
-
-export default SourceViewer.extend({
- events () {
- return {
- ...SourceViewer.prototype.events.apply(this, arguments),
- 'click .js-close-component-viewer': 'closeComponentViewer',
- 'click .code-issue': 'selectIssue'
- };
+import React from 'react';
+import { render, unmountComponentAtNode } from 'react-dom';
+import Marionette from 'backbone.marionette';
+import SourceViewer from '../../../components/SourceViewer/SourceViewer';
+import WithStore from '../../../components/shared/WithStore';
+
+export default Marionette.ItemView.extend({
+ template () {
+ return '<div></div>';
},
initialize (options) {
- SourceViewer.prototype.initialize.apply(this, arguments);
- return this.listenTo(options.app.state, 'change:selectedIndex', this.select);
+ this.handleLoadIssues = this.handleLoadIssues.bind(this);
+ this.scrollToBaseIssue = this.scrollToBaseIssue.bind(this);
+ this.selectIssue = this.selectIssue.bind(this);
+ this.listenTo(options.app.state, 'change:selectedIndex', this.select);
},
- onLoaded () {
- SourceViewer.prototype.onLoaded.apply(this, arguments);
- this.bindShortcuts();
- if (this.baseIssue != null) {
- this.baseIssue.trigger('locations', this.baseIssue);
- this.scrollToLine(this.baseIssue.get('line'));
+ onRender () {
+ this.showViewer();
+ },
+
+ onDestroy () {
+ this.unbindShortcuts();
+ unmountComponentAtNode(this.el);
+ },
+
+ handleLoadIssues (component: string) {
+ // TODO fromLine: number, toLine: number
+ const issues = this.options.app.list.toJSON().filter(issue => issue.componentKey === component);
+ return Promise.resolve(issues);
+ },
+
+ showViewer (onLoaded) {
+ if (!this.baseIssue) {
+ return;
}
+
+ const componentKey = this.baseIssue.get('component');
+
+ render((
+ <WithStore>
+ <SourceViewer
+ aroundLine={this.baseIssue.get('line')}
+ component={componentKey}
+ displayAllIssues={true}
+ loadIssues={this.handleLoadIssues}
+ onLoaded={onLoaded}
+ onIssueSelect={this.selectIssue}
+ selectedIssue={this.baseIssue.get('key')}/>
+ </WithStore>
+ ), this.el);
+ },
+
+ openFileByIssue (issue) {
+ this.baseIssue = issue;
+ this.selectedIssue = issue.get('key');
+ this.showViewer(this.scrollToBaseIssue);
+ this.bindShortcuts();
},
bindShortcuts () {
- const that = this;
- const doAction = function (action) {
- const selectedIssueView = that.getSelectedIssueEl();
- if (!selectedIssueView) {
- return;
- }
- selectedIssueView.find('.js-issue-' + action).click();
- };
key('up', 'componentViewer', () => {
- that.options.app.controller.selectPrev();
+ this.options.app.controller.selectPrev();
return false;
});
key('down', 'componentViewer', () => {
- that.options.app.controller.selectNext();
+ this.options.app.controller.selectNext();
return false;
});
key('left,backspace', 'componentViewer', () => {
- that.options.app.controller.closeComponentViewer();
+ this.options.app.controller.closeComponentViewer();
return false;
});
- key('f', 'componentViewer', () => doAction('transition'));
- key('a', 'componentViewer', () => doAction('assign'));
- key('m', 'componentViewer', () => doAction('assign-to-me'));
- key('p', 'componentViewer', () => doAction('plan'));
- key('i', 'componentViewer', () => doAction('set-severity'));
- key('c', 'componentViewer', () => doAction('comment'));
},
unbindShortcuts () {
- return key.deleteScope('componentViewer');
- },
-
- onDestroy () {
- SourceViewer.prototype.onDestroy.apply(this, arguments);
- this.unbindScrollEvents();
- return this.unbindShortcuts();
+ key.deleteScope('componentViewer');
},
select () {
const selected = this.options.app.state.get('selectedIndex');
const selectedIssue = this.options.app.list.at(selected);
- if (selectedIssue.get('component') === this.model.get('key')) {
- selectedIssue.trigger('locations', selectedIssue);
- return this.scrollToIssue(selectedIssue.get('key'));
- } else {
- this.unbindShortcuts();
- return this.options.app.controller.showComponentViewer(selectedIssue);
- }
- },
-
- getSelectedIssueEl () {
- const selected = this.options.app.state.get('selectedIndex');
- if (selected == null) {
- return null;
- }
- const selectedIssue = this.options.app.list.at(selected);
- if (selectedIssue == null) {
- return null;
- }
- const selectedIssueView = this.$('#issue-' + (selectedIssue.get('key')));
- if (selectedIssueView.length > 0) {
- return selectedIssueView;
- } else {
- return null;
- }
- },
-
- selectIssue (e) {
- const key = $(e.currentTarget).data('issue-key');
- const issue = this.issues.find(model => model.get('key') === key);
- const index = this.options.app.list.indexOf(issue);
- return this.options.app.state.set({ selectedIndex: index });
- },
-
- scrollToIssue (key) {
- const el = this.$('#issue-' + key);
- if (el.length > 0) {
- const line = el.closest('[data-line-number]').data('line-number');
- return this.scrollToLine(line);
- } else {
- this.unbindShortcuts();
- const selected = this.options.app.state.get('selectedIndex');
- const selectedIssue = this.options.app.list.at(selected);
- return this.options.app.controller.showComponentViewer(selectedIssue);
- }
- },
-
- openFileByIssue (issue) {
- this.baseIssue = issue;
- const componentKey = issue.get('component');
- const componentUuid = issue.get('componentUuid');
- return this.open(componentUuid, componentKey);
- },
-
- linesLimit () {
- let 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 (issues) {
- const that = this;
- let index = this.ISSUES_LIMIT / 2;
- if ((this.baseIssue != null) && this.baseIssue.has('index')) {
- index = Math.max(index, this.baseIssue.get('index'));
- }
- return issues.filter(issue => Math.abs(issue.get('index') - index) <= that.ISSUES_LIMIT / 2);
- },
-
- requestIssues () {
- const that = this;
- let r;
- if (this.options.app.list.last().get('component') === this.model.get('key')) {
- r = this.options.app.controller.fetchNextPage();
+ if (selectedIssue.get('component') === this.baseIssue.get('component')) {
+ this.baseIssue = selectedIssue;
+ this.showViewer(this.scrollToBaseIssue);
+ this.scrollToBaseIssue();
} else {
- r = $.Deferred().resolve().promise();
+ this.options.app.controller.showComponentViewer(selectedIssue);
}
- return r.done(() => {
- that.issues.reset(that.options.app.list.filter(issue => issue.get('component') === that.model.key()));
- that.issues.reset(that.limitIssues(that.issues));
- return that.addIssuesPerLineMeta(that.issues);
- });
- },
-
- renderIssues () {
- this.issues.forEach(this.renderIssue, this);
- return this.$('.source-line-issues').addClass('hidden');
- },
-
- renderIssue (issue) {
- const issueView = new IssueView({
- el: '#issue-' + issue.get('key'),
- model: issue,
- app: this.options.app
- });
- this.issueViews.push(issueView);
- return issueView.render();
},
scrollToLine (line) {
const row = this.$(`[data-line-number=${line}]`);
const topOffset = $(window).height() / 2 - 60;
const goal = row.length > 0 ? row.offset().top - topOffset : 0;
- return $(window).scrollTop(goal);
+ $(window).scrollTop(goal);
+ },
+
+ selectIssue (issueKey) {
+ const issue = this.options.app.list.find(model => model.get('key') === issueKey);
+ const index = this.options.app.list.indexOf(issue);
+ this.options.app.state.set({ selectedIndex: index });
},
- closeComponentViewer () {
- return this.options.app.controller.closeComponentViewer();
+ scrollToBaseIssue () {
+ this.scrollToLine(this.baseIssue.get('line'));
}
});
diff --git a/server/sonar-web/src/main/js/apps/issues/controller.js b/server/sonar-web/src/main/js/apps/issues/controller.js
index edc86827050..dd03639f5b0 100644
--- a/server/sonar-web/src/main/js/apps/issues/controller.js
+++ b/server/sonar-web/src/main/js/apps/issues/controller.js
@@ -21,6 +21,8 @@ import $ from 'jquery';
import Backbone from 'backbone';
import Controller from '../../components/navigator/controller';
import ComponentViewer from './component-viewer/main';
+import getStore from '../../app/utils/getStore';
+import { receiveIssues } from '../../store/issues/duck';
const FACET_DATA_FIELDS = ['components', 'users', 'rules', 'languages'];
@@ -35,6 +37,11 @@ export default Controller.extend({
};
},
+ receiveIssues (issues) {
+ const store = getStore();
+ store.dispatch(receiveIssues(issues));
+ },
+
fetchList (firstPage) {
const that = this;
if (firstPage == null) {
@@ -54,6 +61,7 @@ export default Controller.extend({
}
return $.get(window.baseUrl + '/api/issues/search', data).done(r => {
const issues = that.options.app.list.parseIssues(r);
+ this.receiveIssues(issues);
if (firstPage) {
that.options.app.list.reset(issues);
} else {
diff --git a/server/sonar-web/src/main/js/apps/issues/templates/issues-issue-checkbox.hbs b/server/sonar-web/src/main/js/apps/issues/templates/issues-issue-checkbox.hbs
deleted file mode 100644
index dbb50e24779..00000000000
--- a/server/sonar-web/src/main/js/apps/issues/templates/issues-issue-checkbox.hbs
+++ /dev/null
@@ -1,3 +0,0 @@
-<div class="js-toggle issue-checkbox-container">
- <i class="issue-checkbox icon-checkbox {{#if selected}}icon-checkbox-checked{{/if}}"></i>
-</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
deleted file mode 100644
index 16a212ddd60..00000000000
--- a/server/sonar-web/src/main/js/apps/issues/templates/issues-issue-filter.hbs
+++ /dev/null
@@ -1,6 +0,0 @@
-<li class="issue-meta">
- <button class="button-link issue-action issue-action-with-options js-issue-filter"
- aria-label="{{t "issue.filter_similar_issues"}}">
- <i class="icon-filter icon-half-transparent"></i>&nbsp;<i class="icon-dropdown"></i>
- </button>
-</li>
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
index 9c57d181aa9..8c357939b0e 100644
--- 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
@@ -18,10 +18,14 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import $ from 'jquery';
-import IssueView from '../../components/issue/issue-view';
+import React from 'react';
+import { render, unmountComponentAtNode } from 'react-dom';
+import Marionette from 'backbone.marionette';
+import ConnectedIssue from '../../components/issue/ConnectedIssue';
import IssueFilterView from './issue-filter-view';
-import CheckboxTemplate from './templates/issues-issue-checkbox.hbs';
-import FilterTemplate from './templates/issues-issue-filter.hbs';
+import WithStore from '../../components/shared/WithStore';
+import getStore from '../../app/utils/getStore';
+import { getIssueByKey } from '../../store/rootReducer';
const SHOULD_NULL = {
any: ['issues'],
@@ -31,35 +35,52 @@ const SHOULD_NULL = {
assigned: ['assignees']
};
-export default IssueView.extend({
- checkboxTemplate: CheckboxTemplate,
- filterTemplate: FilterTemplate,
+export default Marionette.ItemView.extend({
+ className: 'issues-workspace-list-item',
- events () {
- return {
- ...IssueView.prototype.events.apply(this, arguments),
- 'click': 'selectCurrent',
- 'dblclick': 'openComponentViewer',
- 'click .js-issue-navigate': 'openComponentViewer',
- 'click .js-issue-filter': 'onIssueFilterClick',
- 'click .js-toggle': 'onIssueToggle'
- };
+ initialize (options) {
+ this.openComponentViewer = this.openComponentViewer.bind(this);
+ this.onIssueFilterClick = this.onIssueFilterClick.bind(this);
+ this.onIssueCheck = this.onIssueCheck.bind(this);
+ this.listenTo(options.app.state, 'change:selectedIndex', this.showIssue);
+ this.listenTo(this.model, 'change:selected', this.showIssue);
+ this.subscribeToStore();
},
- initialize (options) {
- IssueView.prototype.initialize.apply(this, arguments);
- this.listenTo(options.app.state, 'change:selectedIndex', this.select);
+ template () {
+ return '<div></div>';
+ },
+
+ subscribeToStore () {
+ const store = getStore();
+ store.subscribe(() => {
+ const issue = getIssueByKey(store.getState(), this.model.get('key'));
+ this.model.set(issue);
+ });
},
onRender () {
- IssueView.prototype.onRender.apply(this, arguments);
- this.select();
- this.addFilterSelect();
- this.addCheckbox();
- this.$el.addClass('issue-navigate-right');
- if (this.options.app.state.get('canBulkChange')) {
- this.$el.addClass('issue-with-checkbox');
- }
+ this.showIssue();
+ },
+
+ onDestroy () {
+ unmountComponentAtNode(this.el);
+ },
+
+ showIssue () {
+ const selected = this.model.get('index') === this.options.app.state.get('selectedIndex');
+
+ render((
+ <WithStore>
+ <ConnectedIssue
+ issueKey={this.model.get('key')}
+ checked={this.model.get('selected')}
+ onCheck={this.onIssueCheck}
+ onClick={this.openComponentViewer}
+ onFilterClick={this.onIssueFilterClick}
+ selected={selected}/>
+ </WithStore>
+ ), this.el);
},
onIssueFilterClick (e) {
@@ -89,26 +110,21 @@ export default IssueView.extend({
this.popup.render();
},
- onIssueToggle (e) {
+ onIssueCheck (e) {
e.preventDefault();
+ e.stopPropagation();
this.model.set({ selected: !this.model.get('selected') });
const selected = this.model.collection.where({ selected: true }).length;
this.options.app.state.set({ selected });
},
- addFilterSelect () {
- this.$('.issue-table-meta-cell-first')
- .find('.issue-meta-list')
- .append(this.filterTemplate(this.model.toJSON()));
- },
-
- addCheckbox () {
- this.$el.append(this.checkboxTemplate(this.model.toJSON()));
- },
-
- select () {
+ changeSelection () {
const selected = this.model.get('index') === this.options.app.state.get('selectedIndex');
- this.$el.toggleClass('selected', selected);
+ if (selected) {
+ this.select();
+ } else {
+ this.unselect();
+ }
},
selectCurrent () {
@@ -137,12 +153,5 @@ export default IssueView.extend({
} else {
return this.options.app.controller.showComponentViewer(this.model);
}
- },
-
- serializeData () {
- return {
- ...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
index 669f4c139e6..383d3145f24 100644
--- 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
@@ -37,14 +37,6 @@ export default WorkspaceListView.extend({
bindShortcuts () {
const that = this;
- const doAction = function (action) {
- const selectedIssue = that.collection.at(that.options.app.state.get('selectedIndex'));
- if (selectedIssue == null) {
- return;
- }
- const selectedIssueView = that.children.findByModel(selectedIssue);
- selectedIssueView.$('.js-issue-' + action).click();
- };
WorkspaceListView.prototype.bindShortcuts.apply(this, arguments);
key('right', 'list', () => {
const selectedIssue = that.collection.at(that.options.app.state.get('selectedIndex'));
@@ -56,26 +48,12 @@ export default WorkspaceListView.extend({
selectedIssue.set({ selected: !selectedIssue.get('selected') });
return false;
});
- key('f', 'list', () => doAction('transition'));
- key('a', 'list', () => doAction('assign'));
- key('m', 'list', () => doAction('assign-to-me'));
- key('p', 'list', () => doAction('plan'));
- key('i', 'list', () => doAction('set-severity'));
- key('c', 'list', () => doAction('comment'));
- key('t', 'list', () => doAction('edit-tags'));
},
unbindShortcuts () {
WorkspaceListView.prototype.unbindShortcuts.apply(this, arguments);
key.unbind('right', 'list');
key.unbind('space', 'list');
- key.unbind('f', 'list');
- key.unbind('a', 'list');
- key.unbind('m', 'list');
- key.unbind('p', 'list');
- key.unbind('i', 'list');
- key.unbind('c', 'list');
- key.unbind('t', 'list');
},
scrollTo () {
@@ -122,7 +100,6 @@ export default WorkspaceListView.extend({
displayComponent (container, model) {
const data = { ...model.toJSON() };
- /* eslint-disable no-console */
const qualifier = this.options.app.state.get('contextComponentQualifier');
if (qualifier === 'VW' || qualifier === 'SVW') {
Object.assign(data, { organization: undefined });