aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2017-03-03 11:47:55 +0100
committerStas Vilchik <vilchiks@gmail.com>2017-03-03 11:47:55 +0100
commitb6baff8775a45fd057ed9f15ba9ac29633261fea (patch)
tree1f3f8e2ae50ab60ff56e8afb207c836fad62566a
parent313ffad34e614acb9379fe1e0a0879a49b05be52 (diff)
downloadsonarqube-b6baff8775a45fd057ed9f15ba9ac29633261fea.tar.gz
sonarqube-b6baff8775a45fd057ed9f15ba9ac29633261fea.zip
Revert "refactor source viewer (#1705)"
This reverts commit ce9f0892fc3d15638c4eaa4054ed06f3d7e5fc19.
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java26
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/component/ws/app-example.json2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java2
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app.json2
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app_with_measures.json2
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app_with_ut_measure.json2
-rw-r--r--server/sonar-web/.eslintrc3
-rw-r--r--server/sonar-web/src/main/js/api/components.js23
-rw-r--r--server/sonar-web/src/main/js/api/issues.js22
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/App.js4
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/ComponentPin.js2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/bubbleChart/BubbleChart.js2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js17
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js17
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/details/treemap/MeasureTreemap.js2
-rw-r--r--server/sonar-web/src/main/js/apps/component/components/App.js45
-rw-r--r--server/sonar-web/src/main/js/apps/issues/component-viewer/issue-view.js (renamed from server/sonar-web/src/main/js/components/issue/ConnectedIssue.js)20
-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.js9
-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.js90
-rw-r--r--server/sonar-web/src/main/js/apps/issues/workspace-list-view.js23
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/App.js4
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewer.js47
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js499
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js222
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.js185
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerIssuesIndicator.js44
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerLine.js377
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewer.js47
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewerBase.js50
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/getCoverageStatus.js37
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.js115
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/indexing.js119
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/issueLocations.js59
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.js76
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/types.js40
-rw-r--r--server/sonar-web/src/main/js/components/common/popup.js22
-rw-r--r--server/sonar-web/src/main/js/components/issue/Issue.js133
-rw-r--r--server/sonar-web/src/main/js/components/issue/issue-view.js55
-rw-r--r--server/sonar-web/src/main/js/components/issue/templates/issue.hbs15
-rw-r--r--server/sonar-web/src/main/js/components/issue/types.js40
-rw-r--r--server/sonar-web/src/main/js/components/shared/WithStore.js44
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/SourceViewer.js82
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/main.js17
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/measures-overlay.js3
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/more-actions.js5
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/popups/coverage-popup.js17
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/popups/duplication-popup.js20
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/popups/line-actions-popup.js6
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js8
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/source.js1
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-coverage-popup.hbs4
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-duplication-popup.hbs4
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-header.hbs2
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-measures.hbs22
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-scm-popup.hbs8
-rw-r--r--server/sonar-web/src/main/js/components/workspace/main.js3
-rw-r--r--server/sonar-web/src/main/js/components/workspace/models/item.js4
-rw-r--r--server/sonar-web/src/main/js/components/workspace/models/items.js7
-rw-r--r--server/sonar-web/src/main/js/components/workspace/views/viewer-view.js55
-rw-r--r--server/sonar-web/src/main/js/helpers/issues.js121
-rw-r--r--server/sonar-web/src/main/js/helpers/request.js25
-rw-r--r--server/sonar-web/src/main/js/store/favorites/duck.js37
-rw-r--r--server/sonar-web/src/main/js/store/issues/duck.js52
-rw-r--r--server/sonar-web/src/main/js/store/rootReducer.js6
-rw-r--r--server/sonar-web/src/main/less/components/issues.less3
-rw-r--r--server/sonar-web/src/main/less/components/source.less24
-rw-r--r--server/sonar-web/src/main/less/pages/issues.less8
-rw-r--r--server/sonar-web/src/main/less/sonar-colorizer.less8
71 files changed, 509 insertions, 2811 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java
index fa07c2e8920..73fc5366f6e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java
@@ -44,17 +44,14 @@ import org.sonar.db.metric.MetricDto;
import org.sonar.db.property.PropertyDto;
import org.sonar.db.property.PropertyQuery;
import org.sonar.server.component.ComponentFinder;
-import org.sonar.server.component.ComponentFinder.ParamNames;
import org.sonar.server.user.UserSession;
import static com.google.common.collect.Lists.newArrayList;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
-import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
public class AppAction implements RequestHandler {
private static final String PARAM_COMPONENT_ID = "componentId";
- private static final String PARAM_COMPONENT = "component";
private static final String PARAM_PERIOD = "period";
static final List<String> METRIC_KEYS = newArrayList(CoreMetrics.LINES_KEY, CoreMetrics.VIOLATIONS_KEY,
CoreMetrics.COVERAGE_KEY, CoreMetrics.DUPLICATED_LINES_DENSITY_KEY, CoreMetrics.TESTS_KEY,
@@ -82,40 +79,31 @@ public class AppAction implements RequestHandler {
action
.createParam(PARAM_COMPONENT_ID)
+ .setRequired(true)
.setDescription("Component ID")
- .setDeprecatedSince("6.4")
.setDeprecatedKey("uuid", "6.4")
.setExampleValue(UUID_EXAMPLE_01);
- action.createParam(PARAM_COMPONENT)
- .setDescription("Component key")
- .setExampleValue(KEY_PROJECT_EXAMPLE_001)
- .setSince("6.4");
-
action
.createParam(PARAM_PERIOD)
.setDescription("User leak Period in order to get differential measures")
- .setDeprecatedSince("6.4")
.setPossibleValues(1);
}
@Override
public void handle(Request request, Response response) {
- try (DbSession session = dbClient.openSession(false)) {
- ComponentDto component = componentFinder.getByUuidOrKey(session,
- request.param(PARAM_COMPONENT_ID),
- request.param(PARAM_COMPONENT),
- ParamNames.COMPONENT_ID_AND_COMPONENT);
+ try (DbSession session = dbClient.openSession(false);
+ JsonWriter json = response.newJsonWriter()) {
+ json.beginObject();
+ String componentUuid = request.mandatoryParam(PARAM_COMPONENT_ID);
+ ComponentDto component = componentFinder.getByUuid(session, componentUuid);
userSession.checkComponentPermission(UserRole.USER, component);
- JsonWriter json = response.newJsonWriter();
- json.beginObject();
Map<String, MeasureDto> measuresByMetricKey = measuresByMetricKey(component, session);
appendComponent(json, component, userSession, session);
appendPermissions(json, component, userSession);
appendMeasures(json, measuresByMetricKey);
json.endObject();
- json.close();
}
}
@@ -150,7 +138,7 @@ public class AppAction implements RequestHandler {
private static void appendPermissions(JsonWriter json, ComponentDto component, UserSession userSession) {
boolean hasBrowsePermission = userSession.hasComponentPermission(UserRole.USER, component);
- json.prop("canMarkAsFavorite", userSession.isLoggedIn() && hasBrowsePermission);
+ json.prop("canMarkAsFavourite", userSession.isLoggedIn() && hasBrowsePermission);
}
private static void appendMeasures(JsonWriter json, Map<String, MeasureDto> measuresByMetricKey) {
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/component/ws/app-example.json b/server/sonar-server/src/main/resources/org/sonar/server/component/ws/app-example.json
index d06748ba5e9..8863fa6535d 100644
--- a/server/sonar-server/src/main/resources/org/sonar/server/component/ws/app-example.json
+++ b/server/sonar-server/src/main/resources/org/sonar/server/component/ws/app-example.json
@@ -7,7 +7,7 @@
"project": "com.sonarsource:java-markdown",
"projectName": "Java Markdown",
"fav": false,
- "canMarkAsFavorite": true,
+ "canMarkAsFavourite": true,
"canCreateManualIssue": true,
"measures": {
"lines": "786",
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java
index b1d32d8d8cf..ce42f92b41e 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java
@@ -83,6 +83,6 @@ public class ComponentsWsTest {
assertThat(action.isInternal()).isTrue();
assertThat(action.isPost()).isFalse();
assertThat(action.handler()).isNotNull();
- assertThat(action.params()).hasSize(3);
+ assertThat(action.params()).hasSize(2);
}
}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app.json b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app.json
index e3554e20775..f7f2947c189 100644
--- a/server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app.json
+++ b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app.json
@@ -10,6 +10,6 @@
"project": "org.sonarsource.sonarqube:sonarqube",
"projectName": "SonarQube",
"fav": false,
- "canMarkAsFavorite": true,
+ "canMarkAsFavourite": true,
"measures": {}
}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app_with_measures.json b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app_with_measures.json
index 64548fda18f..f50715b4b2c 100644
--- a/server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app_with_measures.json
+++ b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app_with_measures.json
@@ -10,7 +10,7 @@
"project": "org.sonarsource.sonarqube:sonarqube",
"projectName": "SonarQube",
"fav": false,
- "canMarkAsFavorite": true,
+ "canMarkAsFavourite": true,
"measures": {
"lines": "200.0",
"coverage": "95.4",
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app_with_ut_measure.json b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app_with_ut_measure.json
index f906f7dccbf..59b6db6325a 100644
--- a/server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app_with_ut_measure.json
+++ b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app_with_ut_measure.json
@@ -10,7 +10,7 @@
"project": "org.sonarsource.sonarqube:sonarqube",
"projectName": "SonarQube",
"fav": false,
- "canMarkAsFavorite": true,
+ "canMarkAsFavourite": true,
"measures": {
"coverage": "95.4"
}
diff --git a/server/sonar-web/.eslintrc b/server/sonar-web/.eslintrc
index be74c6cf9b4..6632e18d8e4 100644
--- a/server/sonar-web/.eslintrc
+++ b/server/sonar-web/.eslintrc
@@ -13,8 +13,7 @@
"globals": {
"key": true,
"d3": true,
- "baseUrl": true,
- "SyntheticInputEvent": true
+ "baseUrl": true
},
"parser": "babel-eslint",
diff --git a/server/sonar-web/src/main/js/api/components.js b/server/sonar-web/src/main/js/api/components.js
index c09192522e3..07ca4204d0b 100644
--- a/server/sonar-web/src/main/js/api/components.js
+++ b/server/sonar-web/src/main/js/api/components.js
@@ -140,26 +140,3 @@ export function bulkChangeKey (project: string, from: string, to: string, dryRun
export const getSuggestions = (query: string): Promise<Object> => (
getJSON('/api/components/suggestions', { s: query })
);
-
-export const getComponentForSourceViewer = (component: string): Promise<*> => (
- getJSON('/api/components/app', { component })
-);
-
-export const getSources = (component: string, from?: number, to?: number): Promise<Array<*>> => {
- const data: Object = { key: component };
- if (from) {
- Object.assign(data, { from });
- }
- if (to) {
- Object.assign(data, { to });
- }
- return getJSON('/api/sources/lines', data).then(r => r.sources);
-};
-
-export const getDuplications = (component: string): Promise<*> => (
- getJSON('/api/duplications/show', { key: component })
-);
-
-export const getTests = (component: string, line: number | string): Promise<*> => (
- getJSON('/api/tests/list', { sourceFileKey: component, sourceFileLineNumber: line }).then(r => r.tests)
-);
diff --git a/server/sonar-web/src/main/js/api/issues.js b/server/sonar-web/src/main/js/api/issues.js
index 75cbdec6e2d..adcb3a1626b 100644
--- a/server/sonar-web/src/main/js/api/issues.js
+++ b/server/sonar-web/src/main/js/api/issues.js
@@ -20,21 +20,7 @@
// @flow
import { getJSON, post } from '../helpers/request';
-type IssuesResponse = {
- components?: Array<*>,
- debtTotal?: number,
- facets: Array<*>,
- issues: Array<*>,
- paging: {
- pageIndex: number,
- pageSize: number,
- total: number
- },
- rules?: Array<*>,
- users?: Array<*>
-};
-
-export const searchIssues = (query: {}): Promise<IssuesResponse> => (
+export const searchIssues = (query: {}) => (
getJSON('/api/issues/search', query)
);
@@ -66,10 +52,10 @@ export function getTags (query: {}): Promise<*> {
export function extractAssignees (
facet: Array<{ val: string }>,
- response: IssuesResponse
+ response: { users: Array<{ login: string }> }
) {
return facet.map(item => {
- const user = response.users ? response.users.find(user => user.login = item.val) : null;
+ const user = response.users.find(user => user.login = item.val);
return { ...item, user };
});
}
@@ -81,7 +67,7 @@ export function getAssignees (query: {}): Promise<*> {
export function getIssuesCount (query: {}): Promise<*> {
const data = { ...query, ps: 1, facetMode: 'effort' };
return searchIssues(data).then(r => {
- return { issues: r.paging.total, debt: r.debtTotal };
+ return { issues: r.total, debt: r.debtTotal };
});
}
diff --git a/server/sonar-web/src/main/js/apps/code/components/App.js b/server/sonar-web/src/main/js/apps/code/components/App.js
index 4208e8d409b..d4a107033d1 100644
--- a/server/sonar-web/src/main/js/apps/code/components/App.js
+++ b/server/sonar-web/src/main/js/apps/code/components/App.js
@@ -22,7 +22,7 @@ import React from 'react';
import { connect } from 'react-redux';
import Components from './Components';
import Breadcrumbs from './Breadcrumbs';
-import SourceViewer from './../../../components/SourceViewer/StandaloneSourceViewer';
+import SourceViewer from './../../../components/source-viewer/SourceViewer';
import Search from './Search';
import ListFooter from '../../../components/controls/ListFooter';
import { retrieveComponentChildren, retrieveComponent, loadMoreChildren, parseError } from '../utils';
@@ -203,7 +203,7 @@ class App extends React.Component {
{shouldShowSourceViewer && (
<div className="spacer-top">
- <SourceViewer component={sourceViewer.key}/>
+ <SourceViewer component={sourceViewer}/>
</div>
)}
</div>
diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.js b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.js
index 20670ac9bcb..33adce19b8a 100644
--- a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.js
+++ b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.js
@@ -25,7 +25,7 @@ import { translate } from '../../../helpers/l10n';
const ComponentPin = ({ component }) => {
const handleClick = e => {
e.preventDefault();
- Workspace.openComponent({ key: component.key });
+ Workspace.openComponent({ uuid: component.id });
};
return (
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/bubbleChart/BubbleChart.js b/server/sonar-web/src/main/js/apps/component-measures/components/bubbleChart/BubbleChart.js
index 4cd674b7c1f..d8ad4c31afc 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/bubbleChart/BubbleChart.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/bubbleChart/BubbleChart.js
@@ -118,7 +118,7 @@ export default class BubbleChart extends React.Component {
handleBubbleClick (component) {
if (['FIL', 'UTS'].includes(component.qualifier)) {
- Workspace.openComponent({ key: component.key });
+ Workspace.openComponent({ uuid: component.id });
} else {
window.location = getComponentUrl(component.refKey || component.key);
}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js b/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js
index bbfc0ae32bf..149582321dc 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js
@@ -19,11 +19,10 @@
*/
import React from 'react';
import classNames from 'classnames';
-import moment from 'moment';
import ComponentsList from './ComponentsList';
import ListHeader from './ListHeader';
import Spinner from '../../components/Spinner';
-import SourceViewer from '../../../../components/SourceViewer/StandaloneSourceViewer';
+import SourceViewer from '../../../../components/source-viewer/SourceViewer';
import ListFooter from '../../../../components/controls/ListFooter';
export default class ListView extends React.Component {
@@ -105,16 +104,6 @@ export default class ListView extends React.Component {
}
const selectedIndex = components.indexOf(selected);
const sourceViewerPeriod = metric.key.indexOf('new_') === 0 && !!leakPeriod ? leakPeriod : null;
- const sourceViewerPeriodDate = sourceViewerPeriod != null ? moment(sourceViewerPeriod.date).toDate() : null;
-
- const filterLine = sourceViewerPeriodDate != null ? line => {
- if (line.scmDate) {
- const scmDate = moment(line.scmDate).toDate();
- return scmDate >= sourceViewerPeriodDate;
- } else {
- return false;
- }
- } : undefined;
return (
<div ref="container" className="measure-details-plain-list">
@@ -151,8 +140,8 @@ export default class ListView extends React.Component {
{!!selected && (
<div className="measure-details-viewer">
<SourceViewer
- component={selected.key}
- filterLine={filterLine}/>
+ component={selected}
+ period={sourceViewerPeriod}/>
</div>
)}
</div>
diff --git a/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js b/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js
index fb0bb744bd0..554a2904227 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js
@@ -18,11 +18,10 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
-import moment from 'moment';
import ComponentsList from './ComponentsList';
import ListHeader from './ListHeader';
import Spinner from '../../components/Spinner';
-import SourceViewer from '../../../../components/SourceViewer/StandaloneSourceViewer';
+import SourceViewer from '../../../../components/source-viewer/SourceViewer';
import ListFooter from '../../../../components/controls/ListFooter';
export default class TreeView extends React.Component {
@@ -98,16 +97,6 @@ export default class TreeView extends React.Component {
const selectedIndex = components.indexOf(selected);
const sourceViewerPeriod = metric.key.indexOf('new_') === 0 && !!leakPeriod ? leakPeriod : null;
- const sourceViewerPeriodDate = sourceViewerPeriod != null ? moment(sourceViewerPeriod.date).toDate() : null;
-
- const filterLine = sourceViewerPeriodDate != null ? line => {
- if (line.scmDate) {
- const scmDate = moment(line.scmDate).toDate();
- return scmDate >= sourceViewerPeriodDate;
- } else {
- return false;
- }
- } : undefined;
return (
<div ref="container" className="measure-details-plain-list">
@@ -144,8 +133,8 @@ export default class TreeView extends React.Component {
{!!selected && (
<div className="measure-details-viewer">
<SourceViewer
- component={selected.key}
- filterLine={filterLine}/>
+ component={selected}
+ period={sourceViewerPeriod}/>
</div>
)}
</div>
diff --git a/server/sonar-web/src/main/js/apps/component-measures/details/treemap/MeasureTreemap.js b/server/sonar-web/src/main/js/apps/component-measures/details/treemap/MeasureTreemap.js
index b599af3cd2b..2255ef5c4c8 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/details/treemap/MeasureTreemap.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/details/treemap/MeasureTreemap.js
@@ -134,7 +134,7 @@ export default class MeasureTreemap extends React.Component {
const isFile = node.qualifier === 'FIL' || node.qualifier === 'UTS';
if (isFile) {
- Workspace.openComponent({ key: node.key });
+ Workspace.openComponent({ uuid: node.id });
return;
}
diff --git a/server/sonar-web/src/main/js/apps/component/components/App.js b/server/sonar-web/src/main/js/apps/component/components/App.js
index 041625d8243..d889e4df77f 100644
--- a/server/sonar-web/src/main/js/apps/component/components/App.js
+++ b/server/sonar-web/src/main/js/apps/component/components/App.js
@@ -19,43 +19,32 @@
*/
// @flow
import React from 'react';
-import SourceViewer from '../../../components/SourceViewer/StandaloneSourceViewer';
+import SourceViewer from '../../../components/source-viewer/SourceViewer';
+import { getComponentNavigation } from '../../../api/nav';
export default class App extends React.Component {
- props: {
- location: {
- query: {
- id: string,
- line?: string
- }
- }
- }
-
- scrollToLine = () => {
- const { line } = this.props.location.query;
- if (line) {
- const row = document.querySelector(`.source-line[data-line-number="${line}"]`);
- if (row) {
- const rect = row.getBoundingClientRect();
- const topOffset = window.innerHeight / 2 - 60;
- const goal = rect.top - topOffset;
- window.scrollTo(0, goal);
- }
- }
+ static propTypes = {
+ location: React.PropTypes.object.isRequired
};
+ state = {};
+
+ componentDidMount () {
+ getComponentNavigation(this.props.location.query.id).then(component => (
+ this.setState({ component })
+ ));
+ }
+
render () {
- const { id, line } = this.props.location.query;
+ if (!this.state.component) {
+ return null;
+ }
- const finalLine = line != null ? Number(line) : null;
+ const { line } = this.props.location.query;
return (
<div className="page">
- <SourceViewer
- aroundLine={finalLine}
- component={id}
- highlightedLine={finalLine}
- onLoaded={this.scrollToLine}/>
+ <SourceViewer component={{ id: this.state.component.id }} line={line}/>
</div>
);
}
diff --git a/server/sonar-web/src/main/js/components/issue/ConnectedIssue.js b/server/sonar-web/src/main/js/apps/issues/component-viewer/issue-view.js
index 28be4c7ba4b..95e18194208 100644
--- a/server/sonar-web/src/main/js/components/issue/ConnectedIssue.js
+++ b/server/sonar-web/src/main/js/apps/issues/component-viewer/issue-view.js
@@ -17,13 +17,19 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-// @flow
-import { connect } from 'react-redux';
-import Issue from './Issue';
-import { getIssueByKey } from '../../store/rootReducer';
+import IssueView from '../workspace-list-item-view';
-const mapStateToProps = (state, ownProps) => ({
- issue: getIssueByKey(state, ownProps.issueKey)
+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
+ };
+ }
});
-export default connect(mapStateToProps)(Issue);
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 49d7152c45f..0f08c21112a 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,114 +18,186 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import $ from 'jquery';
-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>';
+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'
+ };
},
initialize (options) {
- 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);
- },
-
- 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);
+ SourceViewer.prototype.initialize.apply(this, arguments);
+ return this.listenTo(options.app.state, 'change:selectedIndex', this.select);
},
- 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);
+ 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'));
+ }
},
bindShortcuts () {
+ const that = this;
+ const doAction = function (action) {
+ const selectedIssueView = that.getSelectedIssueEl();
+ if (!selectedIssueView) {
+ return;
+ }
+ selectedIssueView.find('.js-issue-' + action).click();
+ };
key('up', 'componentViewer', () => {
- this.options.app.controller.selectPrev();
+ that.options.app.controller.selectPrev();
return false;
});
key('down', 'componentViewer', () => {
- this.options.app.controller.selectNext();
+ that.options.app.controller.selectNext();
return false;
});
key('left,backspace', 'componentViewer', () => {
- this.options.app.controller.closeComponentViewer();
+ that.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 () {
- key.deleteScope('componentViewer');
+ return key.deleteScope('componentViewer');
+ },
+
+ onDestroy () {
+ SourceViewer.prototype.onDestroy.apply(this, arguments);
+ this.unbindScrollEvents();
+ return this.unbindShortcuts();
},
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
+ };
+ },
- if (selectedIssue.get('component') === this.baseIssue.get('component')) {
- this.baseIssue = selectedIssue;
- this.showViewer(this.scrollToBaseIssue);
- this.scrollToBaseIssue();
+ 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();
} else {
- this.options.app.controller.showComponentViewer(selectedIssue);
+ r = $.Deferred().resolve().promise();
}
+ 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;
- $(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 });
+ return $(window).scrollTop(goal);
},
- scrollToBaseIssue () {
- this.scrollToLine(this.baseIssue.get('line'));
+ closeComponentViewer () {
+ 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
index 71df7acdb7c..edc86827050 100644
--- a/server/sonar-web/src/main/js/apps/issues/controller.js
+++ b/server/sonar-web/src/main/js/apps/issues/controller.js
@@ -44,7 +44,14 @@ export default Controller.extend({
this.options.app.state.set({ selectedIndex: 0, page: 1 }, { silent: true });
this.closeComponentViewer();
}
- const data = this.getQueryAsObject();
+ const data = this._issuesParameters();
+ Object.assign(data, this.options.app.state.get('query'));
+ if (this.options.app.state.get('query').assigned_to_me) {
+ Object.assign(data, { assignees: '__me__' });
+ }
+ if (this.options.app.state.get('isContext')) {
+ Object.assign(data, this.options.app.state.get('contextQuery'));
+ }
return $.get(window.baseUrl + '/api/issues/search', data).done(r => {
const issues = that.options.app.list.parseIssues(r);
if (firstPage) {
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
new file mode 100644
index 00000000000..dbb50e24779
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/issues/templates/issues-issue-checkbox.hbs
@@ -0,0 +1,3 @@
+<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
new file mode 100644
index 00000000000..16a212ddd60
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/issues/templates/issues-issue-filter.hbs
@@ -0,0 +1,6 @@
+<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 43817e46586..9c57d181aa9 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,12 +18,10 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import $ from 'jquery';
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import Marionette from 'backbone.marionette';
-import Issue from '../../components/issue/Issue';
+import IssueView from '../../components/issue/issue-view';
import IssueFilterView from './issue-filter-view';
-import WithStore from '../../components/shared/WithStore';
+import CheckboxTemplate from './templates/issues-issue-checkbox.hbs';
+import FilterTemplate from './templates/issues-issue-filter.hbs';
const SHOULD_NULL = {
any: ['issues'],
@@ -33,43 +31,35 @@ const SHOULD_NULL = {
assigned: ['assignees']
};
-export default Marionette.ItemView.extend({
- className: 'issues-workspace-list-item',
+export default IssueView.extend({
+ checkboxTemplate: CheckboxTemplate,
+ filterTemplate: FilterTemplate,
- 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);
+ 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'
+ };
},
- template () {
- return '<div></div>';
+ initialize (options) {
+ IssueView.prototype.initialize.apply(this, arguments);
+ this.listenTo(options.app.state, 'change:selectedIndex', this.select);
},
onRender () {
- this.showIssue();
- },
-
- onDestroy () {
- unmountComponentAtNode(this.el);
- },
-
- showIssue () {
- const selected = this.model.get('index') === this.options.app.state.get('selectedIndex');
-
- render((
- <WithStore>
- <Issue
- issue={this.model}
- checked={this.model.get('selected')}
- onCheck={this.onIssueCheck}
- onClick={this.openComponentViewer}
- onFilterClick={this.onIssueFilterClick}
- selected={selected}/>
- </WithStore>
- ), this.el);
+ 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');
+ }
},
onIssueFilterClick (e) {
@@ -99,21 +89,26 @@ export default Marionette.ItemView.extend({
this.popup.render();
},
- onIssueCheck (e) {
+ onIssueToggle (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 });
},
- changeSelection () {
+ 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 () {
const selected = this.model.get('index') === this.options.app.state.get('selectedIndex');
- if (selected) {
- this.select();
- } else {
- this.unselect();
- }
+ this.$el.toggleClass('selected', selected);
},
selectCurrent () {
@@ -142,5 +137,12 @@ export default Marionette.ItemView.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 383d3145f24..669f4c139e6 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,6 +37,14 @@ 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'));
@@ -48,12 +56,26 @@ 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 () {
@@ -100,6 +122,7 @@ 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 });
diff --git a/server/sonar-web/src/main/js/apps/overview/components/App.js b/server/sonar-web/src/main/js/apps/overview/components/App.js
index 91e636eb52a..84147530647 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/App.js
+++ b/server/sonar-web/src/main/js/apps/overview/components/App.js
@@ -54,10 +54,10 @@ class App extends React.Component {
const { component } = this.props;
if (['FIL', 'UTS'].includes(component.qualifier)) {
- const SourceViewer = require('../../../components/SourceViewer/StandaloneSourceViewer').default;
+ const SourceViewer = require('../../../components/source-viewer/SourceViewer').default;
return (
<div className="page">
- <SourceViewer component={component.key}/>
+ <SourceViewer component={component}/>
</div>
);
}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewer.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewer.js
deleted file mode 100644
index 2c6e5b594a6..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewer.js
+++ /dev/null
@@ -1,47 +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.
- */
-// @flow
-import { connect } from 'react-redux';
-import SourceViewerBase from './SourceViewerBase';
-import { receiveFavorites } from '../../store/favorites/duck';
-import { receiveIssues } from '../../store/issues/duck';
-
-const mapStateToProps = null;
-
-const onReceiveComponent = (component: { key: string, canMarkAsFavorite: boolean, fav: boolean }) => dispatch => {
- if (component.canMarkAsFavorite) {
- const favorites = [];
- const notFavorites = [];
- if (component.fav) {
- favorites.push({ key: component.key });
- } else {
- notFavorites.push({ key: component.key });
- }
- dispatch(receiveFavorites(favorites, notFavorites));
- }
-};
-
-const onReceiveIssues = (issues: Array<*>) => dispatch => {
- dispatch(receiveIssues(issues));
-};
-
-const mapDispatchToProps = { onReceiveComponent, onReceiveIssues };
-
-export default connect(mapStateToProps, mapDispatchToProps)(SourceViewerBase);
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js
deleted file mode 100644
index 2ad750e7120..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js
+++ /dev/null
@@ -1,499 +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.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-import uniqBy from 'lodash/uniqBy';
-import SourceViewerHeader from './SourceViewerHeader';
-import SourceViewerCode from './SourceViewerCode';
-import CoveragePopupView from '../source-viewer/popups/coverage-popup';
-import DuplicationPopupView from '../source-viewer/popups/duplication-popup';
-import LineActionsPopupView from '../source-viewer/popups/line-actions-popup';
-import SCMPopupView from '../source-viewer/popups/scm-popup';
-import MeasuresOverlay from '../source-viewer/measures-overlay';
-import { TooltipsContainer } from '../mixins/tooltips-mixin';
-import Source from '../source-viewer/source';
-import loadIssues from './helpers/loadIssues';
-import getCoverageStatus from './helpers/getCoverageStatus';
-import {
- issuesByLine,
- locationsByLine,
- locationsByIssueAndLine,
- locationMessagesByIssueAndLine,
- duplicationsByLine,
- symbolsByLine
-} from './helpers/indexing';
-import { getComponentForSourceViewer, getSources, getDuplications, getTests } from '../../api/components';
-import { translate } from '../../helpers/l10n';
-import type { SourceLine } from './types';
-import type { Issue } from '../issue/types';
-
-// TODO react-virtualized
-
-type Props = {
- aroundLine?: number,
- component: string,
- displayAllIssues: boolean,
- filterLine?: (line: SourceLine) => boolean,
- highlightedLine?: number,
- loadComponent: (string) => Promise<*>,
- loadIssues: (string, number, number) => Promise<*>,
- loadSources: (string, number, number) => Promise<*>,
- onLoaded?: (component: Object, sources: Array<*>, issues: Array<*>) => void,
- onIssueSelect: (string) => void,
- onIssueUnselect: () => void,
- onReceiveComponent: ({ canMarkAsFavorite: boolean, fav: boolean, key: string }) => void,
- onReceiveIssues: (issues: Array<*>) => void,
- selectedIssue: string | null,
-};
-
-type State = {
- component?: Object,
- displayDuplications: boolean,
- duplications?: Array<{
- blocks: Array<{
- _ref: string,
- from: number,
- size: number
- }>
- }>,
- duplicationsByLine: { [number]: Array<number> },
- duplicatedFiles?: Array<{ key: string }>,
- hasSourcesAfter: boolean,
- highlightedLine: number | null,
- highlightedSymbol: string | null,
- issues?: Array<Issue>,
- issuesByLine: { [number]: Array<string> },
- issueLocationsByLine: { [number]: Array<{ from: number, to: number }> },
- issueSecondaryLocationsByIssueByLine: {
- [string]: {
- [number]: Array<{ from: number, to: number }>
- }
- },
- issueSecondaryLocationMessagesByIssueByLine: {
- [issueKey: string]: {
- [line: number]: Array<{ msg: string, index?: number }>
- }
- },
- loading: boolean,
- loadingSourcesAfter: boolean,
- loadingSourcesBefore: boolean,
- notAccessible: boolean,
- notExist: boolean,
- sources?: Array<SourceLine>,
- symbolsByLine: { [number]: Array<string> }
-};
-
-const LINES = 500;
-
-const loadComponent = (key: string): Promise<*> => {
- return getComponentForSourceViewer(key);
-};
-
-const loadSources = (key: string, from?: number, to?: number): Promise<Array<*>> => {
- return getSources(key, from, to);
-};
-
-export default class SourceViewerBase extends React.Component {
- mounted: boolean;
- node: HTMLElement;
- props: Props;
- state: State;
-
- static defaultProps = {
- displayAllIssues: false,
- onIssueSelect: () => { },
- onIssueUnselect: () => { },
- loadComponent,
- loadIssues,
- loadSources
- };
-
- constructor (props: Props) {
- super(props);
- this.state = {
- displayDuplications: false,
- duplicationsByLine: {},
- hasSourcesAfter: false,
- highlightedLine: props.highlightedLine || null,
- highlightedSymbol: null,
- issuesByLine: {},
- issueLocationsByLine: {},
- issueSecondaryLocationsByIssueByLine: {},
- issueSecondaryLocationMessagesByIssueByLine: {},
- loading: true,
- loadingSourcesAfter: false,
- loadingSourcesBefore: false,
- notAccessible: false,
- notExist: false,
- selectedIssue: props.defaultSelectedIssue || null,
- symbolsByLine: {}
- };
- }
-
- componentDidMount () {
- this.mounted = true;
- this.fetchComponent();
- }
-
- componentDidUpdate (prevProps: Props) {
- if (prevProps.component !== this.props.component) {
- this.fetchComponent();
- } else if (this.props.aroundLine != null && prevProps.aroundLine !== this.props.aroundLine &&
- this.isLineOutsideOfRange(this.props.aroundLine)) {
- this.fetchSources();
- }
- }
-
- componentWillUnmount () {
- this.mounted = false;
- }
-
- computeCoverageStatus (lines: Array<SourceLine>): Array<SourceLine> {
- return lines.map(line => ({ ...line, coverageStatus: getCoverageStatus(line) }));
- }
-
- isLineOutsideOfRange (lineNumber: number) {
- const { sources } = this.state;
- if (sources != null && sources.length > 0) {
- const firstLine = sources[0];
- const lastList = sources[sources.length - 1];
- return lineNumber < firstLine.line || lineNumber > lastList.line;
- } else {
- return true;
- }
- }
-
- fetchComponent () {
- this.setState({ loading: true });
-
- const loadIssues = (component, sources) => {
- this.props.loadIssues(this.props.component, 1, LINES).then(issues => {
- this.props.onReceiveIssues(issues);
- if (this.mounted) {
- const finalSources = sources.slice(0, LINES);
- this.setState({
- component,
- issues,
- issuesByLine: issuesByLine(issues),
- issueLocationsByLine: locationsByLine(issues),
- issueSecondaryLocationsByIssueByLine: locationsByIssueAndLine(issues),
- issueSecondaryLocationMessagesByIssueByLine: locationMessagesByIssueAndLine(issues),
- loading: false,
- hasSourcesAfter: sources.length > LINES,
- sources: this.computeCoverageStatus(finalSources),
- symbolsByLine: symbolsByLine(sources.slice(0, LINES))
- }, () => {
- if (this.props.onLoaded) {
- this.props.onLoaded(component, finalSources, issues);
- }
- });
- }
- });
- };
-
- const onFailLoadComponent = ({ response }) => {
- // TODO handle other statuses
- if (this.mounted && response.status === 404) {
- this.setState({ loading: false, notExist: true });
- }
- };
-
- const onFailLoadSources = (response, component) => {
- // TODO handle other statuses
- if (this.mounted) {
- if (response.status === 403) {
- this.setState({ component, loading: false, notAccessible: true });
- }
- }
- };
-
- const onResolve = component => {
- this.props.onReceiveComponent(component);
- this.loadSources().then(
- sources => loadIssues(component, sources),
- response => onFailLoadSources(response, component)
- );
- };
-
- this.props.loadComponent(this.props.component).then(onResolve, onFailLoadComponent);
- }
-
- fetchSources () {
- this.loadSources().then(sources => {
- if (this.mounted) {
- const finalSources = sources.slice(0, LINES);
- this.setState({
- sources: sources.slice(0, LINES),
- hasSourcesAfter: sources.length > LINES
- }, () => {
- if (this.props.onLoaded) {
- // $FlowFixMe
- this.props.onLoaded(this.state.component, finalSources, this.state.issues);
- }
- });
- }
- });
- }
-
- loadSources () {
- return new Promise((resolve, reject) => {
- const onFailLoadSources = ({ response }) => {
- // TODO handle other statuses
- if (this.mounted) {
- if (response.status === 403) {
- reject(response);
- } else if (response.status === 404) {
- resolve([]);
- }
- }
- };
-
- const from = this.props.aroundLine ? Math.max(1, this.props.aroundLine - LINES / 2 + 1) : 1;
- // request one additional line to define `hasSourcesAfter`
- const to = this.props.aroundLine ? this.props.aroundLine + LINES / 2 + 1 : LINES + 1;
-
- return this.props.loadSources(this.props.component, from, to).then(
- sources => resolve(sources),
- onFailLoadSources
- );
- });
- }
-
- loadSourcesBefore = () => {
- if (!this.state.sources) {
- return;
- }
- const firstSourceLine = this.state.sources[0];
- this.setState({ loadingSourcesBefore: true });
- const from = Math.max(1, firstSourceLine.line - LINES);
- this.props.loadSources(this.props.component, from, firstSourceLine.line - 1).then(sources => {
- this.props.loadIssues(this.props.component, from, firstSourceLine.line - 1).then(issues => {
- this.props.onReceiveIssues(issues);
- if (this.mounted) {
- this.setState(prevState => ({
- issues: uniqBy([...issues, ...prevState.issues], issue => issue.key),
- loadingSourcesBefore: false,
- sources: [...this.computeCoverageStatus(sources), ...prevState.sources],
- symbolsByLine: { ...prevState.symbolsByLine, ...symbolsByLine(sources) }
- }));
- }
- });
- });
- };
-
- loadSourcesAfter = () => {
- if (!this.state.sources) {
- return;
- }
- const lastSourceLine = this.state.sources[this.state.sources.length - 1];
- this.setState({ loadingSourcesAfter: true });
- const fromLine = lastSourceLine.line + 1;
- // request one additional line to define `hasSourcesAfter`
- const toLine = lastSourceLine.line + LINES + 1;
- this.props.loadSources(this.props.component, fromLine, toLine).then(sources => {
- this.props.loadIssues(this.props.component, fromLine, toLine).then(issues => {
- this.props.onReceiveIssues(issues);
- if (this.mounted) {
- this.setState(prevState => ({
- issues: uniqBy([...prevState.issues, ...issues], issue => issue.key),
- hasSourcesAfter: sources.length > LINES,
- loadingSourcesAfter: false,
- sources: [...prevState.sources, ...this.computeCoverageStatus(sources.slice(0, LINES))],
- symbolsByLine: { ...prevState.symbolsByLine, ...symbolsByLine(sources.slice(0, LINES)) }
- }));
- }
- });
- });
- };
-
- loadDuplications = (line: SourceLine, element: HTMLElement) => {
- getDuplications(this.props.component).then(r => {
- if (this.mounted) {
- this.setState({
- displayDuplications: true,
- duplications: r.duplications,
- duplicationsByLine: duplicationsByLine(r.duplications),
- duplicatedFiles: r.files
- }, () => {
- // immediately show dropdown popup if there is only one duplicated block
- if (r.duplications.length === 1) {
- this.handleDuplicationClick(0, line.line, element);
- }
- });
- }
- });
- };
-
- openNewWindow = () => {
- const { component } = this.state;
- if (component != null) {
- let query = 'id=' + encodeURIComponent(component.key);
- const windowParams = 'resizable=1,scrollbars=1,status=1';
- if (this.state.highlightedLine) {
- query = query + '&line=' + this.state.highlightedLine;
- }
- window.open(window.baseUrl + '/component/index?' + query, component.name, windowParams);
- }
- };
-
- showMeasures = () => {
- const model = new Source(this.state.component);
- const measuresOvervlay = new MeasuresOverlay({ model, large: true });
- measuresOvervlay.render();
- };
-
- handleCoverageClick = (line: SourceLine, element: HTMLElement) => {
- getTests(this.props.component, line.line).then(tests => {
- const popup = new CoveragePopupView({ line, tests, triggerEl: element });
- popup.render();
- });
- };
-
- handleDuplicationClick = (index: number, line: number) => {
- const duplication = this.state.duplications && this.state.duplications[index];
- let blocks = (duplication && duplication.blocks) || [];
- const inRemovedComponent = blocks.some(b => b._ref == null);
- let foundOne = false;
- blocks = blocks.filter(b => {
- const outOfBounds = b.from > line || b.from + b.size < line;
- const currentFile = b._ref === '1';
- const shouldDisplayForCurrentFile = outOfBounds || foundOne;
- const shouldDisplay = !currentFile || shouldDisplayForCurrentFile;
- const isOk = (b._ref != null) && shouldDisplay;
- if (b._ref === '1' && !outOfBounds) {
- foundOne = true;
- }
- return isOk;
- });
-
- const element = this.node.querySelector(`.source-line-duplications-extra[data-line-number="${line}"]`);
- if (element) {
- const popup = new DuplicationPopupView({
- blocks,
- inRemovedComponent,
- component: this.state.component,
- files: this.state.duplicatedFiles,
- triggerEl: element
- });
- popup.render();
- }
- };
-
- displayLinePopup (line: number, element: HTMLElement) {
- const popup = new LineActionsPopupView({
- line,
- triggerEl: element,
- component: this.state.component
- });
- popup.render();
- }
-
- handleLineClick = (line: number, element: HTMLElement) => {
- this.setState(prevState => ({
- highlightedLine: prevState.highlightedLine === line ? null : line
- }));
- this.displayLinePopup(line, element);
- };
-
- handleSymbolClick = (symbol: string) => {
- this.setState(prevState => ({
- highlightedSymbol: prevState.highlightedSymbol === symbol ? null : symbol
- }));
- };
-
- handleSCMClick = (line: SourceLine, element: HTMLElement) => {
- const popup = new SCMPopupView({ triggerEl: element, line });
- popup.render();
- };
-
- renderCode (sources: Array<SourceLine>) {
- const hasSourcesBefore = sources.length > 0 && sources[0].line > 1;
- return (
- <TooltipsContainer>
- <SourceViewerCode
- displayAllIssues={this.props.displayAllIssues}
- duplications={this.state.duplications}
- duplicationsByLine={this.state.duplicationsByLine}
- duplicatedFiles={this.state.duplicatedFiles}
- hasSourcesBefore={hasSourcesBefore}
- hasSourcesAfter={this.state.hasSourcesAfter}
- filterLine={this.props.filterLine}
- highlightedLine={this.state.highlightedLine}
- highlightedSymbol={this.state.highlightedSymbol}
- issues={this.state.issues}
- issuesByLine={this.state.issuesByLine}
- issueLocationsByLine={this.state.issueLocationsByLine}
- issueSecondaryLocationsByIssueByLine={this.state.issueSecondaryLocationsByIssueByLine}
- issueSecondaryLocationMessagesByIssueByLine={this.state.issueSecondaryLocationMessagesByIssueByLine}
- loadDuplications={this.loadDuplications}
- loadSourcesAfter={this.loadSourcesAfter}
- loadSourcesBefore={this.loadSourcesBefore}
- loadingSourcesAfter={this.state.loadingSourcesAfter}
- loadingSourcesBefore={this.state.loadingSourcesBefore}
- onCoverageClick={this.handleCoverageClick}
- onDuplicationClick={this.handleDuplicationClick}
- onIssueSelect={this.props.onIssueSelect}
- onIssueUnselect={this.props.onIssueUnselect}
- onLineClick={this.handleLineClick}
- onSCMClick={this.handleSCMClick}
- onSymbolClick={this.handleSymbolClick}
- selectedIssue={this.props.selectedIssue}
- sources={sources}
- symbolsByLine={this.state.symbolsByLine}/>
- </TooltipsContainer>
- );
- }
-
- render () {
- const { component, loading } = this.state;
-
- if (loading) {
- return null;
- }
-
- if (this.state.notExist) {
- return (
- <div className="alert alert-warning spacer-top">{translate('component_viewer.no_component')}</div>
- );
- }
-
- if (component == null) {
- return null;
- }
-
- const className = classNames('source-viewer', { 'source-duplications-expanded': this.state.displayDuplications });
-
- return (
- <div className={className} ref={node => this.node = node}>
- <SourceViewerHeader
- component={this.state.component}
- openNewWindow={this.openNewWindow}
- showMeasures={this.showMeasures}/>
- {this.state.notAccessible && (
- <div className="alert alert-warning spacer-top">
- {translate('code_viewer.no_source_code_displayed_due_to_security')}
- </div>
- )}
- {this.state.sources != null && this.renderCode(this.state.sources)}
- </div>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js
deleted file mode 100644
index 32092dd47c5..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js
+++ /dev/null
@@ -1,222 +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.
- */
-// @flow
-import React from 'react';
-import SourceViewerLine from './SourceViewerLine';
-import { translate } from '../../helpers/l10n';
-import type { Duplication, SourceLine } from './types';
-import type { Issue } from '../issue/types';
-
-const EMPTY_ARRAY = [];
-
-const ZERO_LINE = {
- code: '',
- duplicated: false,
- line: 0
-};
-
-export default class SourceViewerCode extends React.Component {
- props: {
- displayAllIssues: boolean,
- duplications?: Array<Duplication>,
- duplicationsByLine: { [number]: Array<number> },
- duplicatedFiles?: Array<{ key: string }>,
- filterLine?: (SourceLine) => boolean,
- hasSourcesAfter: boolean,
- hasSourcesBefore: boolean,
- highlightedLine: number | null,
- highlightedSymbol: string | null,
- issues: Array<Issue>,
- issuesByLine: { [number]: Array<string> },
- issueLocationsByLine: { [number]: Array<{ from: number, to: number }> },
- issueSecondaryLocationsByIssueByLine: {
- [string]: {
- [number]: Array<{ from: number, to: number }>
- }
- },
- issueSecondaryLocationMessagesByIssueByLine: {
- [issueKey: string]: {
- [line: number]: Array<{ msg: string, index?: number }>
- }
- },
- loadDuplications: (SourceLine, HTMLElement) => void,
- loadSourcesAfter: () => void,
- loadSourcesBefore: () => void,
- loadingSourcesAfter: boolean,
- loadingSourcesBefore: boolean,
- onCoverageClick: (SourceLine, HTMLElement) => void,
- onDuplicationClick: (number, number) => void,
- onIssueSelect: (string) => void,
- onIssueUnselect: () => void,
- onLineClick: (number, HTMLElement) => void,
- onSCMClick: (SourceLine, HTMLElement) => void,
- onSymbolClick: (string) => void,
- selectedIssue: string | null,
- sources: Array<SourceLine>,
- symbolsByLine: { [number]: Array<string> }
- };
-
- isSCMChanged (s: SourceLine, p: null | SourceLine) {
- let changed = true;
- if (p != null && s.scmAuthor != null && p.scmAuthor != null) {
- changed = (s.scmAuthor !== p.scmAuthor) || (s.scmDate !== p.scmDate);
- }
- return changed;
- }
-
- getDuplicationsForLine (line: SourceLine) {
- return this.props.duplicationsByLine[line.line] || EMPTY_ARRAY;
- }
-
- getIssuesForLine (line: SourceLine): Array<string> {
- return this.props.issuesByLine[line.line] || EMPTY_ARRAY;
- }
-
- getIssueLocationsForLine (line: SourceLine) {
- return this.props.issueLocationsByLine[line.line] || EMPTY_ARRAY;
- }
-
- getSecondaryIssueLocationsForLine (line: SourceLine, issueKey: string) {
- const index = this.props.issueSecondaryLocationsByIssueByLine;
- if (index[issueKey] == null) {
- return EMPTY_ARRAY;
- }
- return index[issueKey][line.line] || EMPTY_ARRAY;
- }
-
- getSecondaryIssueLocationMessagesForLine (line: SourceLine, issueKey: string) {
- return this.props.issueSecondaryLocationMessagesByIssueByLine[issueKey][line.line] || EMPTY_ARRAY;
- }
-
- renderLine = (
- line: SourceLine,
- index: number,
- displayCoverage: boolean,
- displayDuplications: boolean,
- displayFiltered: boolean,
- displayIssues: boolean
- ) => {
- const { filterLine, selectedIssue, sources } = this.props;
- const filtered = filterLine ? filterLine(line) : null;
- const secondaryIssueLocations = selectedIssue ?
- this.getSecondaryIssueLocationsForLine(line, selectedIssue) : EMPTY_ARRAY;
- const secondaryIssueLocationMessages = selectedIssue ?
- this.getSecondaryIssueLocationMessagesForLine(line, selectedIssue) : EMPTY_ARRAY;
-
- const duplicationsCount = this.props.duplications ? this.props.duplications.length : 0;
-
- const issuesForLine = this.getIssuesForLine(line);
-
- // for the following properties pass null if the line for sure is not impacted
- const symbolsForLine = this.props.symbolsByLine[line.line] || [];
- const { highlightedSymbol } = this.props;
- const optimizedHighlightedSymbol = highlightedSymbol != null && symbolsForLine.includes(highlightedSymbol) ?
- highlightedSymbol : null;
-
- const optimizedSelectedIssue = selectedIssue != null && issuesForLine.includes(selectedIssue) ?
- selectedIssue : null;
-
- return (
- <SourceViewerLine
- displayAllIssues={this.props.displayAllIssues}
- displayCoverage={displayCoverage}
- displayDuplications={displayDuplications}
- displayFiltered={displayFiltered}
- displayIssues={displayIssues}
- displaySCM={this.isSCMChanged(line, index > 0 ? sources[index - 1] : null)}
- duplications={this.getDuplicationsForLine(line)}
- duplicationsCount={duplicationsCount}
- filtered={filtered}
- highlighted={line.line === this.props.highlightedLine}
- highlightedSymbol={optimizedHighlightedSymbol}
- issueLocations={this.getIssueLocationsForLine(line)}
- issues={issuesForLine}
- key={line.line}
- line={line}
- loadDuplications={this.props.loadDuplications}
- onClick={this.props.onLineClick}
- onCoverageClick={this.props.onCoverageClick}
- onDuplicationClick={this.props.onDuplicationClick}
- onIssueSelect={this.props.onIssueSelect}
- onIssueUnselect={this.props.onIssueUnselect}
- onSCMClick={this.props.onSCMClick}
- onSymbolClick={this.props.onSymbolClick}
- secondaryIssueLocations={secondaryIssueLocations}
- secondaryIssueLocationMessages={secondaryIssueLocationMessages}
- selectedIssue={optimizedSelectedIssue}/>
- );
- };
-
- render () {
- const { sources } = this.props;
-
- const hasCoverage = sources.some(s => s.coverageStatus != null);
- const hasDuplications = sources.some(s => s.duplicated);
- const displayFiltered = this.props.filterLine != null;
- const hasIssues = this.props.issues.length > 0;
-
- const hasFileIssues = hasIssues && this.props.issues.some(issue => !issue.line);
-
- return (
- <div>
- {this.props.hasSourcesBefore && (
- <div className="source-viewer-more-code">
- {this.props.loadingSourcesBefore ? (
- <div className="js-component-viewer-loading-before">
- <i className="spinner"/>
- <span className="note spacer-left">{translate('source_viewer.loading_more_code')}</span>
- </div>
- ) : (
- <button className="js-component-viewer-source-before" onClick={this.props.loadSourcesBefore}>
- {translate('source_viewer.load_more_code')}
- </button>
- )}
- </div>
- )}
-
- <table className="source-table">
- <tbody>
- {hasFileIssues && (
- this.renderLine(ZERO_LINE, -1, hasCoverage, hasDuplications, displayFiltered, hasIssues)
- )}
- {sources.map((line, index) => (
- this.renderLine(line, index, hasCoverage, hasDuplications, displayFiltered, hasIssues)
- ))}
- </tbody>
- </table>
-
- {this.props.hasSourcesAfter && (
- <div className="source-viewer-more-code">
- {this.props.loadingSourcesAfter ? (
- <div className="js-component-viewer-loading-after">
- <i className="spinner"/>
- <span className="note spacer-left">{translate('source_viewer.loading_more_code')}</span>
- </div>
- ) : (
- <button className="js-component-viewer-source-after" onClick={this.props.loadSourcesAfter}>
- {translate('source_viewer.load_more_code')}
- </button>
- )}
- </div>
- )}
- </div>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.js
deleted file mode 100644
index 14dedd85572..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.js
+++ /dev/null
@@ -1,185 +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.
- */
-// @flow
-import React from 'react';
-import { Link } from 'react-router';
-import QualifierIcon from '../shared/qualifier-icon';
-import FavoriteContainer from '../controls/FavoriteContainer';
-import Workspace from '../workspace/main';
-import { getProjectUrl, getIssuesUrl } from '../../helpers/urls';
-import { collapsedDirFromPath, fileFromPath } from '../../helpers/path';
-import { translate } from '../../helpers/l10n';
-import { formatMeasure } from '../../helpers/measures';
-
-export default class SourceViewerHeader extends React.Component {
- props: {
- component: {
- canMarkAsFavorite: boolean,
- key: string,
- measures: {
- coverage?: string,
- duplicationDensity?: string,
- issues?: string,
- lines?: string,
- tests?: string
- },
- path: string,
- project: string,
- projectName: string,
- q: string,
- subProject?: string,
- subProjectName?: string
- },
- openNewWindow: () => void,
- showMeasures: () => void
- };
-
- showMeasures = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.props.showMeasures();
- };
-
- openNewWindow = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.props.openNewWindow();
- };
-
- openInWorkspace = (e: SyntheticInputEvent) => {
- e.preventDefault();
- const { key } = this.props.component;
- Workspace.openComponent({ key });
- };
-
- render () {
- const { key, measures, path, project, projectName, q, subProject, subProjectName } = this.props.component;
- const isUnitTest = q === 'UTS';
- // TODO check if source viewer is displayed inside workspace
- const workspace = false;
- const rawSourcesLink = `${window.baseUrl}/api/sources/raw?key=${encodeURIComponent(this.props.component.key)}`;
-
- // TODO favorite
- return (
- <div className="source-viewer-header">
- <div className="source-viewer-header-component">
- <div className="component-name">
- <div className="component-name-parent">
- <Link to={getProjectUrl(project)} className="link-with-icon">
- <QualifierIcon qualifier="TRK"/> <span>{projectName}</span>
- </Link>
- </div>
-
- {subProject != null && (
- <div className="component-name-parent">
- <Link to={getProjectUrl(subProject)} className="link-with-icon">
- <QualifierIcon qualifier="BRC"/> <span>{subProjectName}</span>
- </Link>
- </div>
- )}
-
- <div className="component-name-path">
- <QualifierIcon qualifier={q}/>
- {' '}
- <span>{collapsedDirFromPath(path)}</span>
- <span className="component-name-file">{fileFromPath(path)}</span>
-
- {this.props.component.canMarkAsFavorite && (
- <FavoriteContainer className="component-name-favorite" componentKey={key}/>
- )}
- </div>
- </div>
- </div>
-
- <div className="dropdown source-viewer-header-actions">
- <a className="js-actions icon-list dropdown-toggle"
- data-toggle="dropdown"
- title={translate('component_viewer.more_actions')}/>
- <ul className="dropdown-menu dropdown-menu-right">
- <li>
- <a className="js-measures" href="#" onClick={this.showMeasures}>
- {translate('component_viewer.show_details')}
- </a>
- </li>
- <li>
- <a className="js-new-window" href="#" onClick={this.openNewWindow}>
- {translate('component_viewer.new_window')}
- </a>
- </li>
- {!workspace && (
- <li>
- <a className="js-workspace" href="#" onClick={this.openInWorkspace}>
- {translate('component_viewer.open_in_workspace')}
- </a>
- </li>
- )}
- <li>
- <a className="js-raw-source" href={rawSourcesLink} target="_blank">
- {translate('component_viewer.show_raw_source')}
- </a>
- </li>
- </ul>
- </div>
-
- <div className="source-viewer-header-measures">
- {isUnitTest && (
- <div className="source-viewer-header-measure">
- <span className="source-viewer-header-measure-value">{formatMeasure(measures.tests, 'SHORT_INT')}</span>
- <span className="source-viewer-header-measure-label">{translate('metric.tests.name')}</span>
- </div>
- )}
-
- {!isUnitTest && (
- <div className="source-viewer-header-measure">
- <span className="source-viewer-header-measure-value">{formatMeasure(measures.lines, 'SHORT_INT')}</span>
- <span className="source-viewer-header-measure-label">{translate('metric.lines.name')}</span>
- </div>
- )}
-
- <div className="source-viewer-header-measure">
- <span className="source-viewer-header-measure-value">
- <Link to={getIssuesUrl({ resolved: 'false', componentKeys: key })}
- className="source-viewer-header-external-link" target="_blank">
- {measures.issues != null ? formatMeasure(measures.issues, 'SHORT_INT') : 0}
- {' '}
- <i className="icon-detach"/>
- </Link>
- </span>
- <span className="source-viewer-header-measure-label">{translate('metric.violations.name')}</span>
- </div>
-
- {measures.coverage != null && (
- <div className="source-viewer-header-measure">
- <span className="source-viewer-header-measure-value">{formatMeasure(measures.coverage, 'PERCENT')}</span>
- <span className="source-viewer-header-measure-label">{translate('metric.coverage.name')}</span>
- </div>
- )}
-
- {measures.duplicationDensity != null && (
- <div className="source-viewer-header-measure">
- <span className="source-viewer-header-measure-value">
- {formatMeasure(measures.duplicationDensity, 'PERCENT')}
- </span>
- <span className="source-viewer-header-measure-label">{translate('duplications')}</span>
- </div>
- )}
- </div>
- </div>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerIssuesIndicator.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerIssuesIndicator.js
deleted file mode 100644
index f6993949244..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerIssuesIndicator.js
+++ /dev/null
@@ -1,44 +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.
- */
-// @flow
-import React from 'react';
-import { connect } from 'react-redux';
-import SeverityIcon from '../shared/severity-icon';
-import { getIssueByKey } from '../../store/rootReducer';
-import { sortBySeverity } from '../../helpers/issues';
-
-class SourceViewerIssuesIndicator extends React.Component {
- props: {
- issue: { severity: string }
- };
-
- render () {
- return (
- <SeverityIcon severity={this.props.issue.severity}/>
- );
- }
-}
-
-const mapStateToProps = (state, ownProps: { issues: Array<string> }) => {
- const issues = ownProps.issues.map(issueKey => getIssueByKey(state, issueKey));
- return { issue: sortBySeverity(issues)[0] };
-};
-
-export default connect(mapStateToProps)(SourceViewerIssuesIndicator);
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerLine.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerLine.js
deleted file mode 100644
index 72cb0d5c053..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerLine.js
+++ /dev/null
@@ -1,377 +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.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-import times from 'lodash/times';
-import ConnectedIssue from '../issue/ConnectedIssue';
-import SourceViewerIssuesIndicator from './SourceViewerIssuesIndicator';
-import { translate } from '../../helpers/l10n';
-import { splitByTokens, highlightSymbol, highlightIssueLocations, generateHTML } from './helpers/highlight';
-import type { SourceLine } from './types';
-
-type Props = {
- displayAllIssues: boolean,
- displayCoverage: boolean,
- displayDuplications: boolean,
- displayFiltered: boolean,
- displayIssues: boolean,
- displaySCM: boolean,
- duplications: Array<number>,
- duplicationsCount: number,
- filtered: boolean | null,
- highlighted: boolean,
- highlightedSymbol: string | null,
- issueLocations: Array<{ from: number, to: number }>,
- issues: Array<string>,
- line: SourceLine,
- loadDuplications: (SourceLine, HTMLElement) => void,
- onClick: (number, HTMLElement) => void,
- onCoverageClick: (SourceLine, HTMLElement) => void,
- onDuplicationClick: (number, number) => void,
- onIssueSelect: (string) => void,
- onIssueUnselect: () => void,
- onSCMClick: (SourceLine, HTMLElement) => void,
- onSymbolClick: (string) => void,
- selectedIssue: string | null,
- // $FlowFixMe
- secondaryIssueLocations: Array<{ from: number, to: number }>,
- // $FlowFixMe
- secondaryIssueLocationMessages: Array<{ msg: string, index?: number }>
-};
-
-type State = {
- issuesOpen: boolean
-};
-
-export default class SourceViewerLine extends React.PureComponent {
- codeNode: HTMLElement;
- props: Props;
- issueElements: { [string]: HTMLElement } = {};
- issueViews: { [string]: { destroy: () => void } } = {};
- state: State = { issuesOpen: false };
- symbols: NodeList<HTMLElement>;
-
- componentDidMount () {
- this.attachEvents();
- }
-
- componentWillUpdate () {
- this.detachEvents();
- }
-
- componentDidUpdate (prevProps: Props) {
- /* eslint-disable no-console */
- console.log('re-render line', this.props.line.line, 'because they are not equal:');
- Object.keys(this.props).forEach(prop => {
- if (this.props[prop] !== prevProps[prop]) {
- console.log(prop);
- }
- });
- console.log('');
-
- this.attachEvents();
- }
-
- componentWillUnmount () {
- this.detachEvents();
- }
-
- attachEvents () {
- this.symbols = this.codeNode.querySelectorAll('.sym');
- for (const symbol of this.symbols) {
- symbol.addEventListener('click', this.handleSymbolClick);
- }
- }
-
- detachEvents () {
- if (this.symbols) {
- for (const symbol of this.symbols) {
- symbol.removeEventListener('click', this.handleSymbolClick);
- }
- }
- }
-
- handleClick = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.props.onClick(this.props.line.line, e.target);
- };
-
- handleCoverageClick = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.props.onCoverageClick(this.props.line, e.target);
- };
-
- handleIssuesIndicatorClick = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.setState(prevState => {
- // TODO not sure if side effects allowed here
- if (!prevState.issuesOpen) {
- const { issues } = this.props;
- if (issues.length > 0) {
- this.props.onIssueSelect(issues[0]);
- }
- } else {
- this.props.onIssueUnselect();
- }
-
- return { issuesOpen: !prevState.issuesOpen };
- });
- }
-
- handleSCMClick = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.props.onSCMClick(this.props.line, e.target);
- }
-
- handleSymbolClick = (e: Object) => {
- e.preventDefault();
- const key = e.currentTarget.className.match(/sym-\d+/);
- if (key && key[0]) {
- this.props.onSymbolClick(key[0]);
- }
- };
-
- handleIssueSelect = (issueKey: string) => {
- this.props.onIssueSelect(issueKey);
- };
-
- renderLineNumber () {
- const { line } = this.props;
- return (
- <td className="source-meta source-line-number"
- // don't display 0
- data-line-number={line.line ? line.line : undefined}
- role={line.line ? 'button' : undefined}
- tabIndex={line.line ? 0 : undefined}
- onClick={line.line ? this.handleClick : undefined}/>
- );
- }
-
- renderSCM () {
- const { line } = this.props;
- const clickable = !!line.line;
- return (
- <td className="source-meta source-line-scm"
- data-line-number={line.line}
- role={clickable ? 'button' : undefined}
- tabIndex={clickable ? 0 : undefined}
- onClick={clickable ? this.handleSCMClick : undefined}>
- {this.props.displaySCM && (
- <div className="source-line-scm-inner" data-author={line.scmAuthor}/>
- )}
- </td>
- );
- }
-
- renderCoverage () {
- const { line } = this.props;
- const className = 'source-meta source-line-coverage' +
- (line.coverageStatus != null ? ` source-line-${line.coverageStatus}` : '');
- return (
- <td className={className}
- data-line-number={line.line}
- title={line.coverageStatus != null && translate('source_viewer.tooltip', line.coverageStatus)}
- data-placement={line.coverageStatus != null && 'right'}
- data-toggle={line.coverageStatus != null && 'tooltip'}
- role={line.coverageStatus != null ? 'button' : undefined}
- tabIndex={line.coverageStatus != null ? 0 : undefined}
- onClick={line.coverageStatus != null && this.handleCoverageClick}>
- <div className="source-line-bar"/>
- </td>
- );
- }
-
- renderDuplications () {
- const { line } = this.props;
- const className = classNames('source-meta', 'source-line-duplications', {
- 'source-line-duplicated': line.duplicated
- });
-
- const handleDuplicationClick = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.props.loadDuplications(this.props.line, e.target);
- };
-
- return (
- <td className={className}
- title={line.duplicated && translate('source_viewer.tooltip.duplicated_line')}
- data-placement={line.duplicated && 'right'}
- data-toggle={line.duplicated && 'tooltip'}
- role="button"
- tabIndex="0"
- onClick={handleDuplicationClick}>
- <div className="source-line-bar"/>
- </td>
- );
- }
-
- renderDuplicationsExtra () {
- const { duplications, duplicationsCount } = this.props;
- return times(duplicationsCount).map(index => this.renderDuplication(index, duplications.includes(index)));
- }
-
- renderDuplication = (index: number, duplicated: boolean) => {
- const className = classNames('source-meta', 'source-line-duplications-extra', {
- 'source-line-duplicated': duplicated
- });
-
- const handleDuplicationClick = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.props.onDuplicationClick(index, this.props.line.line);
- };
-
- return (
- <td key={index}
- className={className}
- data-line-number={this.props.line.line}
- data-index={index}
- title={duplicated ? translate('source_viewer.tooltip.duplicated_block') : undefined}
- data-placement={duplicated ? 'right' : undefined}
- data-toggle={duplicated ? 'tooltip' : undefined}
- role={duplicated ? 'button' : undefined}
- tabIndex={duplicated ? '0' : undefined}
- onClick={duplicated ? handleDuplicationClick : undefined}>
- <div className="source-line-bar"/>
- </td>
- );
- };
-
- renderIssuesIndicator () {
- const { issues } = this.props;
- const hasIssues = issues.length > 0;
- const className = classNames('source-meta', 'source-line-issues', { 'source-line-with-issues': hasIssues });
- const onClick = hasIssues ? this.handleIssuesIndicatorClick : undefined;
-
- return (
- <td className={className}
- data-line-number={this.props.line.line}
- role="button"
- tabIndex="0"
- onClick={onClick}>
- {hasIssues && (
- <SourceViewerIssuesIndicator issues={issues}/>
- )}
- {issues.length > 1 && (
- <span className="source-line-issues-counter">{issues.length}</span>
- )}
- </td>
- );
- }
-
- renderSecondaryIssueLocationMessages (locationMessages: Array<{ msg: string, index?: number }>) {
- const limitString = (str: string) => (
- str.length > 30 ? str.substr(0, 30) + '...' : str
- );
-
- return (
- <div className="source-line-issue-locations">
- {locationMessages.map((locationMessage, index) => (
- <div key={index} className="source-viewer-issue-location" title={locationMessage.msg}>
- {locationMessage.index && (
- <strong>{locationMessage.index}: </strong>
- )}
- {limitString(locationMessage.msg)}
- </div>
- ))}
- </div>
- );
- }
-
- renderCode () {
- const { line, highlightedSymbol, issueLocations, issues, secondaryIssueLocations } = this.props;
- const { secondaryIssueLocationMessages } = this.props;
- const className = classNames('source-line-code', 'code', { 'has-issues': issues.length > 0 });
-
- const code = line.code || '';
- let tokens = splitByTokens(code);
-
- if (highlightedSymbol) {
- tokens = highlightSymbol(tokens, highlightedSymbol);
- }
-
- if (issueLocations.length > 0) {
- tokens = highlightIssueLocations(tokens, issueLocations);
- }
-
- if (secondaryIssueLocations) {
- tokens = highlightIssueLocations(tokens, secondaryIssueLocations, 'source-line-code-secondary-issue');
- }
-
- const finalCode = generateHTML(tokens);
-
- const showIssues = (this.state.issuesOpen || this.props.displayAllIssues) && issues.length > 0;
-
- return (
- <td className={className} data-line-number={line.line}>
- <div className="source-line-code-inner">
- <pre ref={node => this.codeNode = node} dangerouslySetInnerHTML={{ __html: finalCode }}/>
- {secondaryIssueLocationMessages != null && secondaryIssueLocationMessages.length > 0 && (
- this.renderSecondaryIssueLocationMessages(secondaryIssueLocationMessages)
- )}
- </div>
- {showIssues && (
- <div className="issue-list">
- {issues.map(issue => (
- <ConnectedIssue
- key={issue}
- issueKey={issue}
- onClick={this.handleIssueSelect}
- selected={this.props.selectedIssue === issue}/>
- ))}
- </div>
- )}
- </td>
- );
- }
-
- render () {
- const { line, duplicationsCount, filtered } = this.props;
- const className = classNames('source-line', {
- 'source-line-highlighted': this.props.highlighted,
- 'source-line-shadowed': filtered === false,
- 'source-line-filtered': filtered === true
- });
-
- return (
- <tr className={className} data-line-number={line.line}>
- {this.renderLineNumber()}
-
- {this.renderSCM()}
-
- {this.props.displayCoverage && this.renderCoverage()}
-
- {this.props.displayDuplications && this.renderDuplications()}
-
- {duplicationsCount > 0 && this.renderDuplicationsExtra()}
-
- {this.props.displayIssues && !this.props.displayAllIssues && this.renderIssuesIndicator()}
-
- {this.props.displayFiltered && (
- <td className="source-meta source-line-filtered-container" data-line-number={line.line}>
- <div className="source-line-bar"/>
- </td>
- )}
-
- {this.renderCode()}
- </tr>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewer.js b/server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewer.js
deleted file mode 100644
index d673bd44dd0..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewer.js
+++ /dev/null
@@ -1,47 +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.
- */
-// @flow
-import { connect } from 'react-redux';
-import StandaloneSourceViewerBase from './StandaloneSourceViewerBase';
-import { receiveFavorites } from '../../store/favorites/duck';
-import { receiveIssues } from '../../store/issues/duck';
-
-const mapStateToProps = null;
-
-const onReceiveComponent = (component: { key: string, canMarkAsFavorite: boolean, fav: boolean }) => dispatch => {
- if (component.canMarkAsFavorite) {
- const favorites = [];
- const notFavorites = [];
- if (component.fav) {
- favorites.push({ key: component.key });
- } else {
- notFavorites.push({ key: component.key });
- }
- dispatch(receiveFavorites(favorites, notFavorites));
- }
-};
-
-const onReceiveIssues = (issues: Array<*>) => dispatch => {
- dispatch(receiveIssues(issues));
-};
-
-const mapDispatchToProps = { onReceiveComponent, onReceiveIssues };
-
-export default connect(mapStateToProps, mapDispatchToProps)(StandaloneSourceViewerBase);
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewerBase.js b/server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewerBase.js
deleted file mode 100644
index ea28e00b36f..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewerBase.js
+++ /dev/null
@@ -1,50 +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.
- */
-// @flow
-import React from 'react';
-import SourceViewerBase from './SourceViewerBase';
-
-type State = {
- selectedIssue: string | null
-};
-
-export default class StandaloneSourceViewerBase extends React.Component {
- state: State = {
- selectedIssue: null
- };
-
- handleIssueSelect = (issue: string) => {
- this.setState({ selectedIssue: issue });
- };
-
- handleIssueUnselect = () => {
- this.setState({ selectedIssue: null });
- };
-
- render () {
- return (
- <SourceViewerBase
- {...this.props}
- onIssueSelect={this.handleIssueSelect}
- onIssueUnselect={this.handleIssueUnselect}
- selectedIssue={this.state.selectedIssue}/>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/getCoverageStatus.js b/server/sonar-web/src/main/js/components/SourceViewer/helpers/getCoverageStatus.js
deleted file mode 100644
index 2f99ed81675..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/getCoverageStatus.js
+++ /dev/null
@@ -1,37 +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.
- */
-// @flow
-import type { SourceLine } from '../types';
-
-const getCoverageStatus = (s: SourceLine): string | null => {
- let status = null;
- if (s.lineHits != null && s.lineHits > 0) {
- status = 'partially-covered';
- }
- if (s.lineHits != null && s.lineHits > 0 && s.conditions === s.coveredConditions) {
- status = 'covered';
- }
- if (s.lineHits === 0 || s.coveredConditions === 0) {
- status = 'uncovered';
- }
- return status;
-};
-
-export default getCoverageStatus;
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.js b/server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.js
deleted file mode 100644
index 0adc3f0d31f..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.js
+++ /dev/null
@@ -1,115 +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.
- */
-// @flow
-import escapeHtml from 'escape-html';
-
-type Token = { className: string, text: string };
-type Tokens = Array<Token>;
-
-const ISSUE_LOCATION_CLASS = 'source-line-code-issue';
-
-export const splitByTokens = (code: string, rootClassName: string = ''): Tokens => {
- const container = document.createElement('div');
- let tokens = [];
- container.innerHTML = code;
- [].forEach.call(container.childNodes, node => {
- if (node.nodeType === 1) {
- // ELEMENT NODE
- const fullClassName = rootClassName ? (rootClassName + ' ' + node.className) : node.className;
- const innerTokens = splitByTokens(node.innerHTML, fullClassName);
- tokens = tokens.concat(innerTokens);
- }
- if (node.nodeType === 3) {
- // TEXT NODE
- tokens.push({ className: rootClassName, text: node.nodeValue });
- }
- });
- return tokens;
-};
-
-export const highlightSymbol = (tokens: Tokens, symbol: string): Tokens => (
- tokens.map(token => token.className.includes(symbol) ?
- { ...token, className: `${token.className} highlighted` } :
- token
-));
-
-/**
- * Intersect two ranges
- * @param s1 Start position of the first range
- * @param e1 End position of the first range
- * @param s2 Start position of the second range
- * @param e2 End position of the second range
- */
-const intersect = (s1: number, e1: number, s2: number, e2: number): { from: number, to: number } => {
- return { from: Math.max(s1, s2), to: Math.min(e1, e2) };
-};
-
-/**
- * Get the substring of a string
- * @param str A string
- * @param from "From" offset
- * @param to "To" offset
- * @param acc Global offset to eliminate
- */
-const part = (str: string, from: number, to: number, acc: number): string => {
- // we do not want negative number as the first argument of `substr`
- return from >= acc ? str.substr(from - acc, to - from) : str.substr(0, to - from);
-};
-
-/**
- * Highlight issue locations in the list of tokens
- */
-export const highlightIssueLocations = (
- tokens: Tokens,
- issueLocations: Array<{ from: number, to: number }>,
- rootClassName: string = ISSUE_LOCATION_CLASS
-): Tokens => {
- issueLocations.forEach(location => {
- const nextTokens = [];
- let acc = 0;
- tokens.forEach(token => {
- const x = intersect(acc, acc + token.text.length, location.from, location.to);
- const p1 = part(token.text, acc, x.from, acc);
- const p2 = part(token.text, x.from, x.to, acc);
- const p3 = part(token.text, x.to, acc + token.text.length, acc);
- if (p1.length) {
- nextTokens.push({ className: token.className, text: p1 });
- }
- if (p2.length) {
- const newClassName = token.className.indexOf(rootClassName) === -1 ?
- `${token.className} ${rootClassName}` :
- token.className;
- nextTokens.push({ className: newClassName, text: p2 });
- }
- if (p3.length) {
- nextTokens.push({ className: token.className, text: p3 });
- }
- acc += token.text.length;
- });
- tokens = nextTokens.slice();
- });
- return tokens;
-};
-
-export const generateHTML = (tokens: Tokens): string => {
- return tokens.map(token => (
- `<span class="${token.className}">${escapeHtml(token.text)}</span>`
- )).join('');
-};
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/indexing.js b/server/sonar-web/src/main/js/components/SourceViewer/helpers/indexing.js
deleted file mode 100644
index a9016ef0c7c..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/indexing.js
+++ /dev/null
@@ -1,119 +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.
- */
-// @flow
-import { splitByTokens } from './highlight';
-import { getLinearLocations, getIssueLocations } from './issueLocations';
-import type { Issue } from '../../issue/types';
-import type { SourceLine } from '../types';
-
-export const issuesByLine = (issues: Array<Issue>) => {
- const index = {};
- issues.forEach(issue => {
- const line = issue.line || 0;
- if (!(line in index)) {
- index[line] = [];
- }
- index[line].push(issue.key);
- });
- return index;
-};
-
-export const locationsByLine = (issues: Array<Issue>) => {
- const index = {};
- issues.forEach(issue => {
- getLinearLocations(issue.textRange).forEach(location => {
- if (!(location.line in index)) {
- index[location.line] = [];
- }
- index[location.line].push(location);
- });
- });
- return index;
-};
-
-export const locationsByIssueAndLine = (issues: Array<Issue>) => {
- const index = {};
- issues.forEach(issue => {
- const byLine = {};
- getIssueLocations(issue).forEach(location => {
- getLinearLocations(location.textRange).forEach(linearLocation => {
- if (!(linearLocation.line in byLine)) {
- byLine[linearLocation.line] = [];
- }
- byLine[linearLocation.line].push({ from: linearLocation.from, to: linearLocation.to });
- });
- });
- index[issue.key] = byLine;
- });
- return index;
-};
-
-export const locationMessagesByIssueAndLine = (issues: Array<Issue>) => {
- const index = {};
- issues.forEach(issue => {
- const byLine = {};
- getIssueLocations(issue).forEach(location => {
- const line = location.textRange ? location.textRange.startLine : 0;
- if (!(line in byLine)) {
- byLine[line] = [];
- }
- byLine[line].push({ msg: location.msg, index: location.index });
- });
- index[issue.key] = byLine;
- });
- return index;
-};
-
-export const duplicationsByLine = (duplications: Array<*> | null) => {
- if (duplications == null) {
- return {};
- }
-
- const duplicationsByLine = {};
-
- duplications.forEach(({ blocks }, duplicationIndex) => {
- blocks.forEach(block => {
- if (block._ref === '1') {
- for (let line = block.from; line < block.from + block.size; line++) {
- if (!(line in duplicationsByLine)) {
- duplicationsByLine[line] = [];
- }
- duplicationsByLine[line].push(duplicationIndex);
- }
- }
- });
- });
-
- return duplicationsByLine;
-};
-
-export const symbolsByLine = (sources: Array<SourceLine>) => {
- const index = {};
- sources.forEach(line => {
- const tokens = splitByTokens(line.code);
- index[line.line] = tokens
- .map(token => {
- const key = token.className.match(/sym-\d+/);
- return key && key[0];
- })
- .filter(key => key);
- });
- return index;
-};
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/issueLocations.js b/server/sonar-web/src/main/js/components/SourceViewer/helpers/issueLocations.js
deleted file mode 100644
index d2c8991fc3c..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/issueLocations.js
+++ /dev/null
@@ -1,59 +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.
- */
-// @flow
-import type { TextRange, Issue } from '../../issue/types';
-
-export const getLinearLocations = (textRange?: TextRange): Array<{ line: number, from: number, to: number }> => {
- if (!textRange) {
- return [];
- }
- const locations = [];
-
- // go through all lines of the `textRange`
- for (let line = textRange.startLine; line <= textRange.endLine; line++) {
- // TODO fix 999999
- const from = line === textRange.startLine ? textRange.startOffset : 0;
- const to = line === textRange.endLine ? textRange.endOffset : 999999;
- locations.push({ line, from, to });
- }
- return locations;
-};
-
-export const getIssueLocations = (issue: Issue): Array<{ msg: string, textRange: TextRange, index?: number }> => {
- const primaryLocation = {
- msg: issue.message,
- textRange: issue.textRange
- };
- const allLocations = [primaryLocation];
- issue.flows.forEach(({ locations }) => {
- if (locations) {
- const locationsCount = locations.length;
- locations.forEach((location, index) => {
- const flowLocation = {
- ...location,
- // set index only for real flows, do not set for just secondary locations
- index: locationsCount > 1 ? locationsCount - index : undefined
- };
- allLocations.push(flowLocation);
- });
- }
- });
- return allLocations;
-};
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.js b/server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.js
deleted file mode 100644
index ddc2963c0e7..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.js
+++ /dev/null
@@ -1,76 +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.
- */
-// @flow
-import { searchIssues } from '../../../api/issues';
-import { parseIssueFromResponse } from '../../../helpers/issues';
-
-export type Query = { [string]: string };
-
-export type Issues = Array<*>;
-
-// maximum possible value
-const PAGE_SIZE = 500;
-
-const buildQuery = (component: string): Query => ({
- additionalFields: '_all',
- resolved: 'false',
- componentKeys: component,
- s: 'FILE_LINE'
-});
-
-export const loadPage = (query: Query, page: number, pageSize: number = PAGE_SIZE): Promise<Issues> => {
- return searchIssues({ ...query, p: page, ps: pageSize }).then(r => (
- r.issues.map(issue => parseIssueFromResponse(issue, r.components, r.users, r.rules))
- ));
-};
-
-export const loadPageAndNext = (
- query: Query,
- toLine: number,
- page: number,
- pageSize: number = PAGE_SIZE
-): Promise<Issues> => {
- return loadPage(query, page).then(issues => {
- if (issues.length === 0) {
- return [];
- }
-
- const lastIssue = issues[issues.length - 1];
-
- if ((lastIssue.line != null && lastIssue.line > toLine) || issues.length < pageSize) {
- return issues;
- }
-
- return loadPageAndNext(query, toLine, page + 1, pageSize).then(nextIssues => {
- return [...issues, ...nextIssues];
- });
- });
-};
-
-const loadIssues = (component: string, fromLine: number, toLine: number): Promise<Issues> => {
- const query = buildQuery(component);
- return new Promise(resolve => {
- loadPageAndNext(query, toLine, 1).then(issues => {
- resolve(issues);
- });
- });
-};
-
-export default loadIssues;
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/types.js b/server/sonar-web/src/main/js/components/SourceViewer/types.js
deleted file mode 100644
index 3dd00eeb553..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/types.js
+++ /dev/null
@@ -1,40 +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.
- */
-// @flow
-export type SourceLine = {
- code: string,
- conditions?: number,
- coverageStatus?: string | null,
- coveredConditions?: number,
- duplicated: boolean,
- line: number,
- lineHits?: number,
- scmAuthor?: string,
- scmDate?: string,
- scmRevision?: string
-};
-
-export type Duplication = {
- blocks: Array<{
- _ref: string,
- from: number,
- size: number
- }>
-};
diff --git a/server/sonar-web/src/main/js/components/common/popup.js b/server/sonar-web/src/main/js/components/common/popup.js
index af7d22f632c..363f0bdf72b 100644
--- a/server/sonar-web/src/main/js/components/common/popup.js
+++ b/server/sonar-web/src/main/js/components/common/popup.js
@@ -25,23 +25,22 @@ export default Marionette.ItemView.extend({
onRender () {
this.$el.detach().appendTo($('body'));
- const triggerEl = $(this.options.triggerEl);
if (this.options.bottom) {
this.$el.addClass('bubble-popup-bottom');
this.$el.css({
- top: triggerEl.offset().top + triggerEl.outerHeight(),
- left: triggerEl.offset().left
+ top: this.options.triggerEl.offset().top + this.options.triggerEl.outerHeight(),
+ left: this.options.triggerEl.offset().left
});
} else if (this.options.bottomRight) {
this.$el.addClass('bubble-popup-bottom-right');
this.$el.css({
- top: triggerEl.offset().top + triggerEl.outerHeight(),
- right: $(window).width() - triggerEl.offset().left - triggerEl.outerWidth()
+ top: this.options.triggerEl.offset().top + this.options.triggerEl.outerHeight(),
+ right: $(window).width() - this.options.triggerEl.offset().left - this.options.triggerEl.outerWidth()
});
} else {
this.$el.css({
- top: triggerEl.offset().top,
- left: triggerEl.offset().left + triggerEl.outerWidth()
+ top: this.options.triggerEl.offset().top,
+ left: this.options.triggerEl.offset().left + this.options.triggerEl.outerWidth()
});
}
this.attachCloseEvents();
@@ -49,7 +48,6 @@ export default Marionette.ItemView.extend({
attachCloseEvents () {
const that = this;
- const triggerEl = $(this.options.triggerEl);
key('escape', () => {
that.destroy();
});
@@ -57,8 +55,8 @@ export default Marionette.ItemView.extend({
$('body').off('click.bubble-popup');
that.destroy();
});
- triggerEl.on('click.bubble-popup', e => {
- triggerEl.off('click.bubble-popup');
+ this.options.triggerEl.on('click.bubble-popup', e => {
+ that.options.triggerEl.off('click.bubble-popup');
e.stopPropagation();
that.destroy();
});
@@ -66,7 +64,7 @@ export default Marionette.ItemView.extend({
onDestroy () {
$('body').off('click.bubble-popup');
- const triggerEl = $(this.options.triggerEl);
- triggerEl.off('click.bubble-popup');
+ this.options.triggerEl.off('click.bubble-popup');
}
});
+
diff --git a/server/sonar-web/src/main/js/components/issue/Issue.js b/server/sonar-web/src/main/js/components/issue/Issue.js
deleted file mode 100644
index c437b8f41af..00000000000
--- a/server/sonar-web/src/main/js/components/issue/Issue.js
+++ /dev/null
@@ -1,133 +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.
- */
-// @flow
-import React from 'react';
-import { connect } from 'react-redux';
-import IssueView from './issue-view';
-import IssueModel from './models/issue';
-import { receiveIssues } from '../../store/issues/duck';
-import type { Issue as IssueType } from './types';
-
-type Model = { toJSON: () => {} };
-
-type Props = {
- checked?: boolean,
- issue: IssueType | Model,
- onCheck?: () => void,
- onClick: () => void,
- onFilterClick?: () => void,
- onIssueChange: ({}) => void,
- selected: boolean
-};
-
-class Issue extends React.PureComponent {
- issueView: Object;
- node: HTMLElement;
- props: Props;
-
- componentDidMount () {
- this.renderIssueView();
- if (this.props.selected) {
- this.bindShortcuts();
- }
- }
-
- componentWillUpdate (nextProps: Props) {
- if (!nextProps.selected && this.props.selected) {
- this.unbindShortcuts();
- }
- this.destroyIssueView();
- }
-
- componentDidUpdate (prevProps: Props) {
- this.renderIssueView();
- if (!prevProps.selected && this.props.selected) {
- this.bindShortcuts();
- }
- }
-
- componentWillUnmount () {
- if (this.props.selected) {
- this.unbindShortcuts();
- }
- this.destroyIssueView();
- }
-
- bindShortcuts () {
- document.addEventListener('keypress', this.handleKeyPress);
- }
-
- unbindShortcuts () {
- document.removeEventListener('keypress', this.handleKeyPress);
- }
-
- doIssueAction (action: string) {
- this.issueView.$('.js-issue-' + action).click();
- }
-
- handleKeyPress = (e: Object) => {
- const tagName = e.target.tagName.toUpperCase();
- const shouldHandle = tagName !== 'INPUT' && tagName !== 'TEXTAREA' && tagName !== 'BUTTON';
-
- if (shouldHandle) {
- switch (e.key) {
- case 'f': return this.doIssueAction('transition');
- case 'a': return this.doIssueAction('assign');
- case 'm': return this.doIssueAction('assign-to-me');
- case 'p': return this.doIssueAction('plan');
- case 'i': return this.doIssueAction('set-severity');
- case 'c': return this.doIssueAction('comment');
- case 't': return this.doIssueAction('edit-tags');
- }
- }
- };
-
- renderIssueView () {
- const model = this.props.issue.toJSON ? this.props.issue : new IssueModel(this.props.issue);
- this.issueView = new IssueView({
- model,
- checked: this.props.checked,
- onCheck: this.props.onCheck,
- onClick: this.props.onClick,
- onFilterClick: this.props.onFilterClick,
- onIssueChange: this.props.onIssueChange
- });
- this.issueView.render().$el.appendTo(this.node);
- if (this.props.selected) {
- this.issueView.select();
- }
- }
-
- destroyIssueView () {
- this.issueView.destroy();
- }
-
- render () {
- return <div className="issue-container" ref={node => this.node = node}/>;
- }
-}
-
-const onIssueChange = issue => dispatch => {
- dispatch(receiveIssues([issue]));
-};
-
-const mapDispatchToProps = { onIssueChange };
-
-export default connect(null, mapDispatchToProps)(Issue);
diff --git a/server/sonar-web/src/main/js/components/issue/issue-view.js b/server/sonar-web/src/main/js/components/issue/issue-view.js
index a4a691fa955..71ced0ff47a 100644
--- a/server/sonar-web/src/main/js/components/issue/issue-view.js
+++ b/server/sonar-web/src/main/js/components/issue/issue-view.js
@@ -34,21 +34,16 @@ import Template from './templates/issue.hbs';
import getCurrentUserFromStore from '../../app/utils/getCurrentUserFromStore';
export default Marionette.ItemView.extend({
+ className: 'issue',
template: Template,
modelEvents: {
- 'change': 'notifyAndRender',
+ 'change': 'render',
'transition': 'onTransition'
},
- className () {
- const hasCheckbox = this.options.onCheck != null;
- return hasCheckbox ? 'issue issue-with-checkbox' : 'issue';
- },
-
events () {
return {
- 'click': 'handleClick',
'click .js-issue-comment': 'onComment',
'click .js-issue-comment-edit': 'editComment',
'click .js-issue-comment-delete': 'deleteComment',
@@ -61,24 +56,10 @@ export default Marionette.ItemView.extend({
'click .js-issue-show-changelog': 'showChangeLog',
'click .js-issue-rule': 'showRule',
'click .js-issue-edit-tags': 'editTags',
- 'click .js-issue-locations': 'showLocations',
- 'click .js-issue-filter': 'filterSimilarIssues',
- 'click .js-toggle': 'onIssueCheck'
+ 'click .js-issue-locations': 'showLocations'
};
},
- notifyAndRender () {
- const { onIssueChange } = this.options;
- if (onIssueChange) {
- onIssueChange(this.model.toJSON());
- }
-
- // if ConnectedIssue is used, this view can be destroyed just after onIssueChange()
- if (!this.isDestroyed) {
- this.render();
- }
- },
-
onRender () {
this.$el.attr('data-key', this.model.get('key'));
},
@@ -262,45 +243,19 @@ export default Marionette.ItemView.extend({
this.model.trigger('locations', this.model);
},
- select () {
- this.$el.addClass('selected');
- },
-
- unselect () {
- this.$el.removeClass('selected');
- },
-
onTransition (transition) {
if (transition === 'falsepositive' || transition === 'wontfix') {
this.comment({ fromTransition: true });
}
},
- handleClick (e) {
- e.preventDefault();
- const { onClick } = this.options;
- if (onClick) {
- onClick(this.model.get('key'));
- }
- },
-
- filterSimilarIssues (e) {
- this.options.onFilterClick(e);
- },
-
- onIssueCheck (e) {
- this.options.onCheck(e);
- },
-
serializeData () {
const issueKey = encodeURIComponent(this.model.get('key'));
return {
...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
permalink: window.baseUrl + '/issues/search#issues=' + issueKey,
- hasSecondaryLocations: this.model.get('flows').length,
- hasSimilarIssuesFilter: this.options.onFilterClick != null,
- hasCheckbox: this.options.onCheck != null,
- checked: this.options.checked
+ hasSecondaryLocations: this.model.get('flows').length
};
}
});
+
diff --git a/server/sonar-web/src/main/js/components/issue/templates/issue.hbs b/server/sonar-web/src/main/js/components/issue/templates/issue.hbs
index f951a40c0c4..a828ecf5e3e 100644
--- a/server/sonar-web/src/main/js/components/issue/templates/issue.hbs
+++ b/server/sonar-web/src/main/js/components/issue/templates/issue.hbs
@@ -35,15 +35,6 @@
<li class="issue-meta">
<a class="js-issue-permalink icon-link" href="{{permalink}}" target="_blank"></a>
</li>
-
- {{#if hasSimilarIssuesFilter}}
- <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>
- {{/if}}
</ul>
</td>
</tr>
@@ -174,9 +165,3 @@
<i class="issue-navigate-to-left icon-chevron-left"></i>
<i class="issue-navigate-to-right icon-chevron-right"></i>
</a>
-
-{{#if hasCheckbox}}
- <div class="js-toggle issue-checkbox-container">
- <i class="issue-checkbox icon-checkbox {{#if checked}}icon-checkbox-checked{{/if}}"></i>
- </div>
-{{/if}}
diff --git a/server/sonar-web/src/main/js/components/issue/types.js b/server/sonar-web/src/main/js/components/issue/types.js
deleted file mode 100644
index dd0bbc1d2e6..00000000000
--- a/server/sonar-web/src/main/js/components/issue/types.js
+++ /dev/null
@@ -1,40 +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.
- */
-// @flow
-export type TextRange = {
- startLine: number,
- startOffset: number,
- endLine: number,
- endOffset: number
-};
-
-export type Issue = {
- key: string,
- flows: Array<{
- locations?: Array<{
- msg: string,
- textRange?: TextRange
- }>
- }>,
- line?: number,
- message: string,
- severity: string,
- textRange: TextRange
-};
diff --git a/server/sonar-web/src/main/js/components/shared/WithStore.js b/server/sonar-web/src/main/js/components/shared/WithStore.js
deleted file mode 100644
index f3cb83233e2..00000000000
--- a/server/sonar-web/src/main/js/components/shared/WithStore.js
+++ /dev/null
@@ -1,44 +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.
- */
-// @flow
-import React from 'react';
-import getStore from '../../app/utils/getStore';
-
-export default class WithStore extends React.Component {
- store: {};
- props: { children: Object };
-
- static childContextTypes = {
- store: React.PropTypes.object
- };
-
- constructor (props: { children: Object }) {
- super(props);
- this.store = getStore();
- }
-
- getChildContext () {
- return { store: this.store };
- }
-
- render () {
- return this.props.children;
- }
-}
diff --git a/server/sonar-web/src/main/js/components/source-viewer/SourceViewer.js b/server/sonar-web/src/main/js/components/source-viewer/SourceViewer.js
new file mode 100644
index 00000000000..0589bcd0643
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/source-viewer/SourceViewer.js
@@ -0,0 +1,82 @@
+/*
+ * 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 React from 'react';
+import BaseSourceViewer from './main';
+import { getPeriodDate, getPeriodLabel } from '../../helpers/periods';
+
+export default class SourceViewer extends React.Component {
+ static propTypes = {
+ component: React.PropTypes.shape({
+ id: React.PropTypes.string.isRequired
+ }).isRequired,
+ period: React.PropTypes.object,
+ line: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string])
+ };
+
+ componentDidMount () {
+ this.renderSourceViewer();
+ }
+
+ shouldComponentUpdate (nextProps) {
+ return nextProps.component.id !== this.props.component.id;
+ }
+
+ componentWillUpdate () {
+ this.destroySourceViewer();
+ }
+
+ componentDidUpdate () {
+ this.renderSourceViewer();
+ }
+
+ componentWillUnmount () {
+ this.destroySourceViewer();
+ }
+
+ renderSourceViewer () {
+ this.sourceViewer = new BaseSourceViewer();
+ this.sourceViewer.render().$el.appendTo(this.refs.container);
+ this.sourceViewer.open(this.props.component.id);
+ this.sourceViewer.on('loaded', this.handleLoad.bind(this));
+ }
+
+ destroySourceViewer () {
+ this.sourceViewer.destroy();
+ }
+
+ handleLoad () {
+ const { period, line } = this.props;
+
+ if (period) {
+ const periodDate = getPeriodDate(period);
+ const periodLabel = getPeriodLabel(period);
+ this.sourceViewer.filterLinesByDate(periodDate, periodLabel);
+ }
+
+ if (line) {
+ this.sourceViewer.highlightLine(line);
+ this.sourceViewer.scrollToLine(line);
+ }
+ }
+
+ render () {
+ return <div ref="container"/>;
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/source-viewer/main.js b/server/sonar-web/src/main/js/components/source-viewer/main.js
index 8b1725d09f3..58bfb9ce4f8 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/main.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/main.js
@@ -21,6 +21,7 @@ import $ from 'jquery';
import moment from 'moment';
import sortBy from 'lodash/sortBy';
import toPairs from 'lodash/toPairs';
+import Backbone from 'backbone';
import Marionette from 'backbone.marionette';
import Source from './source';
import Issues from '../issue/collections/issues';
@@ -402,7 +403,7 @@ export default Marionette.LayoutView.extend({
const row = this.model.get('source').find(row => row.line === line);
const popup = new SCMPopupView({
triggerEl: $(e.currentTarget),
- line: row
+ model: new Backbone.Model(row)
});
popup.render();
},
@@ -421,8 +422,8 @@ export default Marionette.LayoutView.extend({
};
return $.get(url, options).done(data => {
const popup = new CoveragePopupView({
- line: row,
- tests: data.tests,
+ row,
+ collection: new Backbone.Collection(data.tests),
triggerEl: $(e.currentTarget)
});
popup.render();
@@ -467,11 +468,10 @@ export default Marionette.LayoutView.extend({
return isOk;
});
const popup = new DuplicationPopupView({
- blocks,
inRemovedComponent,
- component: this.model.toJSON(),
- files: this.model.get('duplicationFiles'),
- triggerEl: $(e.currentTarget)
+ triggerEl: $(e.currentTarget),
+ model: this.model,
+ collection: new Backbone.Collection(blocks)
});
popup.render();
},
@@ -498,7 +498,8 @@ export default Marionette.LayoutView.extend({
const popup = new LineActionsPopupView({
line,
triggerEl: $(e.currentTarget),
- component: this.model.toJSON()
+ model: this.model,
+ row: $(e.currentTarget).closest('.source-line')
});
popup.render();
},
diff --git a/server/sonar-web/src/main/js/components/source-viewer/measures-overlay.js b/server/sonar-web/src/main/js/components/source-viewer/measures-overlay.js
index 4baf170a2e8..a01d69b0f85 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/measures-overlay.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/measures-overlay.js
@@ -34,7 +34,7 @@ export default ModalView.extend({
initialize () {
this.testsScroll = 0;
const requests = [this.requestMeasures(), this.requestIssues()];
- if (this.model.get('q') === 'UTS') {
+ if (this.model.get('isUnitTest')) {
requests.push(this.requestTests());
}
Promise.all(requests).then(() => this.render());
@@ -282,3 +282,4 @@ export default ModalView.extend({
};
}
});
+
diff --git a/server/sonar-web/src/main/js/components/source-viewer/more-actions.js b/server/sonar-web/src/main/js/components/source-viewer/more-actions.js
index aba02a8e1de..9b7181a6463 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/more-actions.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/more-actions.js
@@ -50,8 +50,8 @@ export default Marionette.ItemView.extend({
},
openInWorkspace () {
- const key = this.options.parent.model.get('key');
- Workspace.openComponent({ key });
+ const uuid = this.options.parent.model.id;
+ Workspace.openComponent({ uuid });
},
showRawSource () {
@@ -66,3 +66,4 @@ export default Marionette.ItemView.extend({
};
}
});
+
diff --git a/server/sonar-web/src/main/js/components/source-viewer/popups/coverage-popup.js b/server/sonar-web/src/main/js/components/source-viewer/popups/coverage-popup.js
index 68fd0ccc388..1440241e42a 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/popups/coverage-popup.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/popups/coverage-popup.js
@@ -27,7 +27,7 @@ export default Popup.extend({
template: Template,
events: {
- 'click a[data-key]': 'goToFile'
+ 'click a[data-id]': 'goToFile'
},
onRender () {
@@ -37,19 +37,19 @@ export default Popup.extend({
goToFile (e) {
e.stopPropagation();
- const key = $(e.currentTarget).data('key');
- Workspace.openComponent({ key });
+ const id = $(e.currentTarget).data('id');
+ Workspace.openComponent({ uuid: id });
},
serializeData () {
- const row = this.options.line || {};
- const tests = groupBy(this.options.tests, 'fileKey');
- const testFiles = Object.keys(tests).map(fileKey => {
- const testSet = tests[fileKey];
+ const row = this.options.row || {};
+ const tests = groupBy(this.collection.toJSON(), 'fileId');
+ const testFiles = Object.keys(tests).map(fileId => {
+ const testSet = tests[fileId];
const test = testSet[0];
return {
file: {
- key: test.fileKey,
+ id: test.fileId,
longName: test.fileName
},
tests: testSet
@@ -58,3 +58,4 @@ export default Popup.extend({
return { testFiles, row };
}
});
+
diff --git a/server/sonar-web/src/main/js/components/source-viewer/popups/duplication-popup.js b/server/sonar-web/src/main/js/components/source-viewer/popups/duplication-popup.js
index da542333a30..24ad94fe254 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/popups/duplication-popup.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/popups/duplication-popup.js
@@ -28,35 +28,37 @@ export default Popup.extend({
template: Template,
events: {
- 'click a[data-key]': 'goToFile'
+ 'click a[data-uuid]': 'goToFile'
},
goToFile (e) {
e.stopPropagation();
- const key = $(e.currentTarget).data('key');
+ const uuid = $(e.currentTarget).data('uuid');
const line = $(e.currentTarget).data('line');
- Workspace.openComponent({ key, line });
+ Workspace.openComponent({ uuid, line });
},
serializeData () {
const that = this;
- const groupedBlocks = groupBy(this.options.blocks, '_ref');
+ const files = this.model.get('duplicationFiles');
+ const groupedBlocks = groupBy(this.collection.toJSON(), '_ref');
let duplications = Object.keys(groupedBlocks).map(fileRef => {
return {
blocks: groupedBlocks[fileRef],
- file: this.options.files[fileRef]
+ file: files[fileRef]
};
});
duplications = sortBy(duplications, d => {
- const a = d.file.projectName !== that.options.component.projectName;
- const b = d.file.subProjectName !== that.options.component.subProjectName;
- const c = d.file.key !== that.options.component.key;
+ const a = d.file.projectName !== that.model.get('projectName');
+ const b = d.file.subProjectName !== that.model.get('subProjectName');
+ const c = d.file.key !== that.model.get('key');
return '' + a + b + c;
});
return {
duplications,
- component: this.options.component,
+ component: this.model.toJSON(),
inRemovedComponent: this.options.inRemovedComponent
};
}
});
+
diff --git a/server/sonar-web/src/main/js/components/source-viewer/popups/line-actions-popup.js b/server/sonar-web/src/main/js/components/source-viewer/popups/line-actions-popup.js
index a2d94f568b8..aa89585bc44 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/popups/line-actions-popup.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/popups/line-actions-popup.js
@@ -29,9 +29,9 @@ export default Popup.extend({
getPermalink (e) {
e.preventDefault();
- const { component, line } = this.options;
- const url = `${window.baseUrl}/component/index?id=${encodeURIComponent(component.key)}&line=${line}`;
+ const url =
+ `${window.baseUrl}/component/index?id=${encodeURIComponent(this.model.key())}&line=${this.options.line}`;
const windowParams = 'resizable=1,scrollbars=1,status=1';
- window.open(url, component.name, windowParams);
+ window.open(url, this.model.get('name'), windowParams);
}
});
diff --git a/server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js b/server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js
index f140e37c56b..755a866baec 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js
@@ -34,12 +34,6 @@ export default Popup.extend({
onClick (e) {
e.stopPropagation();
- },
-
- serializeData () {
- return {
- ...Popup.prototype.serializeData.apply(this, arguments),
- line: this.options.line
- };
}
});
+
diff --git a/server/sonar-web/src/main/js/components/source-viewer/source.js b/server/sonar-web/src/main/js/components/source-viewer/source.js
index 3cb1198e328..43009061a22 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/source.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/source.js
@@ -96,3 +96,4 @@ export default Backbone.Model.extend({
return source.some(line => line.coverageStatus != null);
}
});
+
diff --git a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-coverage-popup.hbs b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-coverage-popup.hbs
index 57c6301119e..a0e7b62896e 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-coverage-popup.hbs
+++ b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-coverage-popup.hbs
@@ -15,7 +15,7 @@
{{#each testFiles}}
<div class="bubble-popup-section">
- <a class="component-viewer-popup-test-file link-action" data-key="{{file.key}}" title="{{file.longName}}">
+ <a class="component-viewer-popup-test-file link-action" data-id="{{file.id}}" title="{{file.longName}}">
<span>{{collapsePath file.longName}}</span>
</a>
<ul class="bubble-popup-list">
@@ -24,7 +24,7 @@
<i class="component-viewer-popup-test-status {{testStatusIconClass status}}"></i>
<span class="component-viewer-popup-test-name">
<a class="component-viewer-popup-test-file link-action" title="{{name}}"
- data-key="{{../file.key}}" data-method="{{name}}">
+ data-id="{{../file.id}}" data-method="{{name}}">
{{name}}
</a>
</span>
diff --git a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-duplication-popup.hbs b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-duplication-popup.hbs
index ea8fc2b2349..9b0783c6655 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-duplication-popup.hbs
+++ b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-duplication-popup.hbs
@@ -21,7 +21,7 @@
{{#notEq file.key ../component.key}}
<div class="component-name-path">
- <a class="link-action" data-key="{{file.key}}" title="{{file.name}}">
+ <a class="link-action" data-uuid="{{file.uuid}}" title="{{file.name}}">
<span>{{collapsedDirFromPath file.name}}</span><span
class="component-name-file">{{fileFromPath file.name}}</span>
</a>
@@ -31,7 +31,7 @@
<div class="component-name-path">
Lines:
{{#joinEach blocks ','}}
- <a class="link-action" data-key="{{../file.key}}" data-line="{{this.from}}">
+ <a class="link-action" data-uuid="{{../file.uuid}}" data-line="{{this.from}}">
{{this.from}} – {{sum from size -1}}
</a>
{{/joinEach}}
diff --git a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-header.hbs b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-header.hbs
index a4532354481..e276c7e938b 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-header.hbs
+++ b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-header.hbs
@@ -16,7 +16,7 @@
<div class="component-name-path">
{{qualifierIcon q}}&nbsp;<span>{{collapsedDirFromPath path}}</span><span class="component-name-file">{{fileFromPath path}}</span>
- {{#if canMarkAsFavorite}}
+ {{#if canMarkAsFavourite}}
<a class="js-favorite component-name-favorite {{#if fav}}icon-favorite{{else}}icon-not-favorite{{/if}}"
title="{{#if fav}}{{t 'click_to_remove_from_favorites'}}{{else}}{{t 'click_to_add_to_favorites'}}{{/if}}">
</a>
diff --git a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-measures.hbs b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-measures.hbs
index 0df076390c9..a3f4df55605 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-measures.hbs
+++ b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-measures.hbs
@@ -19,16 +19,7 @@
{{/unless}}
</div>
- {{#eq q 'UTS'}}
- <div class="source-viewer-measures">
- <div class="source-viewer-measures-section">
- {{> 'measures/_source-viewer-measures-tests'}}
- </div>
- </div>
- <div class="source-viewer-measures">
- {{> 'measures/_source-viewer-measures-test-cases'}}
- </div>
- {{else}}
+ {{#unless isUnitTest}}
<div class="source-viewer-measures">
<div class="source-viewer-measures-section">
<div class="source-viewer-measures-card">
@@ -52,7 +43,16 @@
{{> 'measures/_source-viewer-measures-duplications'}}
</div>
</div>
- {{/eq}}
+ {{else}}
+ <div class="source-viewer-measures">
+ <div class="source-viewer-measures-section">
+ {{> 'measures/_source-viewer-measures-tests'}}
+ </div>
+ </div>
+ <div class="source-viewer-measures">
+ {{> 'measures/_source-viewer-measures-test-cases'}}
+ </div>
+ {{/unless}}
<div class="spacer-bottom">&nbsp;</div>
diff --git a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-scm-popup.hbs b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-scm-popup.hbs
index dd82aca528c..768ea72341d 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-scm-popup.hbs
+++ b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-scm-popup.hbs
@@ -1,13 +1,13 @@
<div class="bubble-popup-container">
<div class="bubble-popup-section">
- {{line.scmAuthor}}
+ {{scmAuthor}}
</div>
<div class="bubble-popup-section">
- {{dt line.scmDate}}
+ {{dt scmDate}}
</div>
- {{#if line.scmRevision}}
+ {{#if scmRevision}}
<div class="bubble-popup-section">
- {{line.scmRevision}}
+ {{scmRevision}}
</div>
{{/if}}
</div>
diff --git a/server/sonar-web/src/main/js/components/workspace/main.js b/server/sonar-web/src/main/js/components/workspace/main.js
index 4e1170bac38..30082332e4b 100644
--- a/server/sonar-web/src/main/js/components/workspace/main.js
+++ b/server/sonar-web/src/main/js/components/workspace/main.js
@@ -99,8 +99,7 @@ Workspace.prototype = {
that.closeComponentViewer();
m.destroy();
});
- this.viewerView.$el.appendTo(document.body);
- this.viewerView.render();
+ this.viewerView.render().$el.appendTo(document.body);
},
showComponentViewer (model) {
diff --git a/server/sonar-web/src/main/js/components/workspace/models/item.js b/server/sonar-web/src/main/js/components/workspace/models/item.js
index 1dd6daf7fc5..0ecbef4ac33 100644
--- a/server/sonar-web/src/main/js/components/workspace/models/item.js
+++ b/server/sonar-web/src/main/js/components/workspace/models/item.js
@@ -25,8 +25,8 @@ export default Backbone.Model.extend({
if (!this.has('__type__')) {
return 'type is missing';
}
- if (this.get('__type__') === 'component' && !this.has('key')) {
- return 'key is missing';
+ if (this.get('__type__') === 'component' && !this.has('uuid')) {
+ return 'uuid is missing';
}
if (this.get('__type__') === 'rule' && !this.has('key')) {
return 'key is missing';
diff --git a/server/sonar-web/src/main/js/components/workspace/models/items.js b/server/sonar-web/src/main/js/components/workspace/models/items.js
index 97ff41e2267..5d015e037ea 100644
--- a/server/sonar-web/src/main/js/components/workspace/models/items.js
+++ b/server/sonar-web/src/main/js/components/workspace/models/items.js
@@ -47,13 +47,16 @@ export default Backbone.Collection.extend({
},
has (model) {
- const forComponent = model.isComponent() && this.findWhere({ key: model.get('key') }) != null;
+ const forComponent = model.isComponent() && this.findWhere({ uuid: model.get('uuid') }) != null;
const forRule = model.isRule() && this.findWhere({ key: model.get('key') }) != null;
return forComponent || forRule;
},
add2 (model) {
- const tryModel = this.findWhere({ key: model.get('key') });
+ const tryModel = model.isComponent() ?
+ this.findWhere({ uuid: model.get('uuid') }) :
+ this.findWhere({ key: model.get('key') });
return tryModel != null ? tryModel : this.add(model);
}
});
+
diff --git a/server/sonar-web/src/main/js/components/workspace/views/viewer-view.js b/server/sonar-web/src/main/js/components/workspace/views/viewer-view.js
index 7ab96e7c683..924ea80ad7f 100644
--- a/server/sonar-web/src/main/js/components/workspace/views/viewer-view.js
+++ b/server/sonar-web/src/main/js/components/workspace/views/viewer-view.js
@@ -17,13 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import $ from 'jquery';
-import React from 'react';
-import { render } from 'react-dom';
import BaseView from './base-viewer-view';
-import SourceViewer from '../../SourceViewer/StandaloneSourceViewer';
+import SourceViewer from '../../source-viewer/main';
import Template from '../templates/workspace-viewer.hbs';
-import WithStore from '../../shared/WithStore';
export default BaseView.extend({
template: Template,
@@ -33,39 +29,22 @@ export default BaseView.extend({
this.showViewer();
},
- scrollToLine (line) {
- const row = this.$el.find(`.source-line[data-line-number="${line}"]`);
- if (row.length > 0) {
- const sourceViewer = this.$el.find('.source-viewer');
- let p = sourceViewer.scrollParent();
- if (p.is(document) || p.is('body')) {
- p = $(window);
- }
- const pTopOffset = p.offset() != null ? p.offset().top : 0;
- const pHeight = p.height();
- const goal = row.offset().top - pHeight / 3 - pTopOffset;
- p.scrollTop(goal);
- }
- },
-
showViewer () {
- const { key, line } = this.model.toJSON();
-
- const el = document.querySelector(this.viewerRegion.el);
-
- render((
- <WithStore>
- <SourceViewer
- component={key}
- fromWorkspace={true}
- highlightedLine={line}
- onLoaded={component => {
- this.model.set({ name: component.name, q: component.q });
- if (line) {
- this.scrollToLine(line);
- }
- }}/>
- </WithStore>
- ), el);
+ const that = this;
+ const viewer = new SourceViewer();
+ const options = this.model.toJSON();
+ viewer.open(this.model.get('uuid'), { workspace: true });
+ viewer.on('loaded', () => {
+ that.model.set({
+ name: viewer.model.get('name'),
+ q: viewer.model.get('q')
+ });
+ if (options.line != null) {
+ viewer.highlightLine(options.line);
+ viewer.scrollToLine(options.line);
+ }
+ });
+ this.viewerRegion.show(viewer);
}
});
+
diff --git a/server/sonar-web/src/main/js/helpers/issues.js b/server/sonar-web/src/main/js/helpers/issues.js
deleted file mode 100644
index 3a1e509f790..00000000000
--- a/server/sonar-web/src/main/js/helpers/issues.js
+++ /dev/null
@@ -1,121 +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.
- */
-// @flow
-import sortBy from 'lodash/sortBy';
-import { SEVERITIES } from './constants';
-
-type TextRange = {
- startLine: number,
- endLine: number,
- startOffset: number,
- endOffset: number
-};
-
-type Comment = {
- login: string
-};
-
-type User = {
- login: string
-};
-
-type RawIssue = {
- assignee?: string,
- author: string,
- comments?: Array<Comment>,
- component: string,
- line?: number,
- project: string,
- rule: string,
- status: string,
- subProject?: string,
- textRange?: TextRange
-};
-
-export const sortBySeverity = (issues: Array<*>) => (
- sortBy(issues, issue => SEVERITIES.indexOf(issue.severity))
-);
-
-const injectRelational = (
- issue: RawIssue | Comment,
- source?: Array<*>,
- baseField: string,
- lookupField: string
-) => {
- const newFields = {};
- const baseValue = issue[baseField];
- if (baseValue != null && source != null) {
- const lookupValue = source.find(candidate => candidate[lookupField] === baseValue);
- if (lookupValue != null) {
- Object.keys(lookupValue).forEach(key => {
- const newKey = baseField + key.charAt(0).toUpperCase() + key.slice(1);
- newFields[newKey] = lookupValue[key];
- });
- }
- }
- return newFields;
-};
-
-const injectCommentsRelational = (issue: RawIssue, users?: Array<User>) => {
- if (!issue.comments) {
- return {};
- }
- const comments = issue.comments.map(comment => ({
- ...comment,
- author: comment.login,
- login: undefined,
- ...injectRelational(comment, users, 'author', 'login')
- }));
- return { comments };
-};
-
-const prepareClosed = (issue: RawIssue) => {
- return issue.status === 'CLOSED' ? { flows: undefined } : {};
-};
-
-const ensureTextRange = (issue: RawIssue) => {
- return issue.line && !issue.textRange ? {
- textRange: {
- startLine: issue.line,
- endLine: issue.line,
- startOffset: 0,
- endOffset: 999999
- }
- } : {};
-};
-
-export const parseIssueFromResponse = (
- issue: RawIssue,
- components?: Array<*>,
- users?: Array<*>,
- rules?: Array<*>
-) => {
- return {
- ...issue,
- ...injectRelational(issue, components, 'component', 'key'),
- ...injectRelational(issue, components, 'project', 'key'),
- ...injectRelational(issue, components, 'subProject', 'key'),
- ...injectRelational(issue, rules, 'rule', 'key'),
- ...injectRelational(issue, users, 'assignee', 'login'),
- ...injectCommentsRelational(issue, users),
- ...prepareClosed(issue),
- ...ensureTextRange(issue)
- };
-};
diff --git a/server/sonar-web/src/main/js/helpers/request.js b/server/sonar-web/src/main/js/helpers/request.js
index cbd5a4e7c01..80bb9e787cc 100644
--- a/server/sonar-web/src/main/js/helpers/request.js
+++ b/server/sonar-web/src/main/js/helpers/request.js
@@ -146,18 +146,19 @@ export function request (url: string): Request {
* @returns {*}
*/
export function checkStatus (response: Response): Promise<Object> {
- return new Promise((resolve, reject) => {
- if (response.status === 401) {
- // workaround cyclic dependencies
- const handleRequiredAuthentication = require('../app/utils/handleRequiredAuthentication').default;
- handleRequiredAuthentication();
- reject();
- } else if (response.status >= 200 && response.status < 300) {
- resolve(response);
- } else {
- reject({ response });
- }
- });
+ if (response.status === 401) {
+ // workaround cyclic dependencies
+ const handleRequiredAuthentication = require('../app/utils/handleRequiredAuthentication').default;
+ handleRequiredAuthentication();
+ return Promise.reject();
+ } else if (response.status >= 200 && response.status < 300) {
+ return Promise.resolve(response);
+ } else {
+ const error = new Error(response.status);
+ // $FlowFixMe complains that `response` is not found
+ error.response = response;
+ throw error;
+ }
}
/**
diff --git a/server/sonar-web/src/main/js/store/favorites/duck.js b/server/sonar-web/src/main/js/store/favorites/duck.js
index ceeb119abfa..c97f715edbd 100644
--- a/server/sonar-web/src/main/js/store/favorites/duck.js
+++ b/server/sonar-web/src/main/js/store/favorites/duck.js
@@ -17,58 +17,32 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-// @flow
import uniq from 'lodash/uniq';
import without from 'lodash/without';
-type Favorite = { key: string };
-
-type ReceiveFavoritesAction = {
- type: 'RECEIVE_FAVORITES',
- favorites: Array<Favorite>,
- notFavorites: Array<Favorite>
-};
-
-type AddFavoriteAction = {
- type: 'ADD_FAVORITE',
- componentKey: string
-};
-
-type RemoveFavoriteAction = {
- type: 'REMOVE_FAVORITE',
- componentKey: string
-};
-
-type Action = ReceiveFavoritesAction | AddFavoriteAction | RemoveFavoriteAction;
-
-type State = Array<string>;
-
export const actions = {
RECEIVE_FAVORITES: 'RECEIVE_FAVORITES',
ADD_FAVORITE: 'ADD_FAVORITE',
REMOVE_FAVORITE: 'REMOVE_FAVORITE'
};
-export const receiveFavorites = (
- favorites: Array<Favorite>,
- notFavorites: Array<Favorite> = []
-): ReceiveFavoritesAction => ({
+export const receiveFavorites = (favorites, notFavorites = []) => ({
type: actions.RECEIVE_FAVORITES,
favorites,
notFavorites
});
-export const addFavorite = (componentKey: string): AddFavoriteAction => ({
+export const addFavorite = componentKey => ({
type: actions.ADD_FAVORITE,
componentKey
});
-export const removeFavorite = (componentKey: string): RemoveFavoriteAction => ({
+export const removeFavorite = componentKey => ({
type: actions.REMOVE_FAVORITE,
componentKey
});
-export default (state: State = [], action: Action): State => {
+export default (state = [], action = {}) => {
if (action.type === actions.RECEIVE_FAVORITES) {
const toAdd = action.favorites.map(f => f.key);
const toRemove = action.notFavorites.map(f => f.key);
@@ -86,6 +60,7 @@ export default (state: State = [], action: Action): State => {
return state;
};
-export const isFavorite = (state: State, componentKey: string) => (
+export const isFavorite = (state, componentKey) => (
state.includes(componentKey)
);
+
diff --git a/server/sonar-web/src/main/js/store/issues/duck.js b/server/sonar-web/src/main/js/store/issues/duck.js
deleted file mode 100644
index 1126bcfd57f..00000000000
--- a/server/sonar-web/src/main/js/store/issues/duck.js
+++ /dev/null
@@ -1,52 +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.
- */
-// @flow
-import keyBy from 'lodash/keyBy';
-
-type Issue = { key: string };
-
-type ReceiveIssuesAction = {
- type: 'RECEIVE_ISSUES',
- issues: Array<Issue>
-};
-
-type Action = ReceiveIssuesAction;
-
-type State = { [key: string]: Issue };
-
-export const receiveIssues = (issues: Array<Issue>): ReceiveIssuesAction => ({
- type: 'RECEIVE_ISSUES',
- issues
-});
-
-const reducer = (state: State = {}, action: Action) => {
- switch (action.type) {
- case 'RECEIVE_ISSUES':
- return { ...state, ...keyBy(action.issues, 'key') };
- default:
- return state;
- }
-};
-
-export default reducer;
-
-export const getIssueByKey = (state: State, key: string): ?Issue => (
- state[key]
-);
diff --git a/server/sonar-web/src/main/js/store/rootReducer.js b/server/sonar-web/src/main/js/store/rootReducer.js
index 1b8539f84eb..aee309845c2 100644
--- a/server/sonar-web/src/main/js/store/rootReducer.js
+++ b/server/sonar-web/src/main/js/store/rootReducer.js
@@ -22,7 +22,6 @@ import appState from './appState/duck';
import components, * as fromComponents from './components/reducer';
import users, * as fromUsers from './users/reducer';
import favorites, * as fromFavorites from './favorites/duck';
-import issues, * as fromIssues from './issues/duck';
import languages, * as fromLanguages from './languages/reducer';
import measures, * as fromMeasures from './measures/reducer';
import notifications, * as fromNotifications from './notifications/duck';
@@ -41,7 +40,6 @@ export default combineReducers({
components,
globalMessages,
favorites,
- issues,
languages,
measures,
notifications,
@@ -82,10 +80,6 @@ export const isFavorite = (state, componentKey) => (
fromFavorites.isFavorite(state.favorites, componentKey)
);
-export const getIssueByKey = (state, key) => (
- fromIssues.getIssueByKey(state.issues, key)
-);
-
export const getComponentMeasure = (state, componentKey, metricKey) => (
fromMeasures.getComponentMeasure(state.measures, componentKey, metricKey)
);
diff --git a/server/sonar-web/src/main/less/components/issues.less b/server/sonar-web/src/main/less/components/issues.less
index 8202664e4cc..b56858fe641 100644
--- a/server/sonar-web/src/main/less/components/issues.less
+++ b/server/sonar-web/src/main/less/components/issues.less
@@ -50,8 +50,7 @@
border-color: @issueBorderColor !important;
}
-.issue + .issue,
-.issue-container + .issue-container {
+.issue + .issue {
margin-top: 5px;
}
diff --git a/server/sonar-web/src/main/less/components/source.less b/server/sonar-web/src/main/less/components/source.less
index 9a89d87959a..18a2cfa5d1c 100644
--- a/server/sonar-web/src/main/less/components/source.less
+++ b/server/sonar-web/src/main/less/components/source.less
@@ -143,14 +143,6 @@
user-select: none;
}
-.source-meta:focus {
- outline: none;
-}
-
-.source-meta[role="button"] {
- cursor: pointer;
-}
-
.source-meta + .source-meta {
border-left: 1px solid @barBackgroundColor;
}
@@ -162,6 +154,10 @@
color: @secondFontColor;
text-align: right;
+ &[data-line-number] {
+ cursor: pointer;
+ }
+
&:before {
content: attr(data-line-number);
}
@@ -211,6 +207,10 @@
.source-line-scm {
padding: 0 5px;
background-color: @barBackgroundColor;
+
+ &[data-line-number] {
+ cursor: pointer;
+ }
}
.source-line-scm-inner {
@@ -229,21 +229,29 @@
height: @source-line-height;
}
+.source-line-with-issues {
+ cursor: pointer;
+}
+
.source-line-covered {
background-color: @green !important;
+ cursor: pointer;
}
.source-line-uncovered {
background-color: @red !important;
+ cursor: pointer;
}
.source-line-partially-covered {
background-color: @orange !important;
background-image: repeating-linear-gradient(45deg, rgba(255, 255, 255, .5) 4px, transparent 4px, transparent 8px, rgba(255, 255, 255, .5) 8px, rgba(255, 255, 255, .5) 12px, transparent 12px, transparent 16px, rgba(255, 255, 255, .5) 16px, rgba(255, 255, 255, .5) 20px) !important;
+ cursor: pointer;
}
.source-line-duplicated {
background-color: @duplicationColor !important;
+ cursor: pointer;
}
diff --git a/server/sonar-web/src/main/less/pages/issues.less b/server/sonar-web/src/main/less/pages/issues.less
index 98bf375c2ea..fd1ddd7431e 100644
--- a/server/sonar-web/src/main/less/pages/issues.less
+++ b/server/sonar-web/src/main/less/pages/issues.less
@@ -45,15 +45,11 @@
padding: 0 10px;
}
-.issues-workspace-list-item + .issues-workspace-list-item {
- margin-top: 5px;
-}
-
-.issues-workspace-list-component + .issues-workspace-list-item {
+.issues-workspace-list-component + .issue {
margin-top: 10px;
}
-.issues-workspace-list-item + .issues-workspace-list-component {
+.issue + .issues-workspace-list-component {
margin-top: 25px;
}
diff --git a/server/sonar-web/src/main/less/sonar-colorizer.less b/server/sonar-web/src/main/less/sonar-colorizer.less
index 760aec28453..325bcad8177 100644
--- a/server/sonar-web/src/main/less/sonar-colorizer.less
+++ b/server/sonar-web/src/main/less/sonar-colorizer.less
@@ -70,11 +70,5 @@
cursor: pointer;
}
.highlighted {
- background-color: #b3d4ff;
- animation: highlightedFadeIn 0.3s forwards;
-}
-
-@keyframes highlightedFadeIn {
- from { background-color: transparent; }
- to { background-color: #b3d4ff; }
+ background-color: #B3D4FF;
}