]> source.dussan.org Git - sonarqube.git/commitdiff
Revert "refactor source viewer (#1705)"
authorStas Vilchik <vilchiks@gmail.com>
Fri, 3 Mar 2017 10:47:55 +0000 (11:47 +0100)
committerStas Vilchik <vilchiks@gmail.com>
Fri, 3 Mar 2017 10:47:55 +0000 (11:47 +0100)
This reverts commit ce9f0892fc3d15638c4eaa4054ed06f3d7e5fc19.

72 files changed:
server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java
server/sonar-server/src/main/resources/org/sonar/server/component/ws/app-example.json
server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java
server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app.json
server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app_with_measures.json
server/sonar-server/src/test/resources/org/sonar/server/component/ws/AppActionTest/app_with_ut_measure.json
server/sonar-web/.eslintrc
server/sonar-web/src/main/js/api/components.js
server/sonar-web/src/main/js/api/issues.js
server/sonar-web/src/main/js/apps/code/components/App.js
server/sonar-web/src/main/js/apps/code/components/ComponentPin.js
server/sonar-web/src/main/js/apps/component-measures/components/bubbleChart/BubbleChart.js
server/sonar-web/src/main/js/apps/component-measures/details/drilldown/ListView.js
server/sonar-web/src/main/js/apps/component-measures/details/drilldown/TreeView.js
server/sonar-web/src/main/js/apps/component-measures/details/treemap/MeasureTreemap.js
server/sonar-web/src/main/js/apps/component/components/App.js
server/sonar-web/src/main/js/apps/issues/component-viewer/issue-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/issues/component-viewer/main.js
server/sonar-web/src/main/js/apps/issues/controller.js
server/sonar-web/src/main/js/apps/issues/templates/issues-issue-checkbox.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/issues/templates/issues-issue-filter.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/issues/workspace-list-item-view.js
server/sonar-web/src/main/js/apps/issues/workspace-list-view.js
server/sonar-web/src/main/js/apps/overview/components/App.js
server/sonar-web/src/main/js/components/SourceViewer/SourceViewer.js [deleted file]
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js [deleted file]
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js [deleted file]
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.js [deleted file]
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerIssuesIndicator.js [deleted file]
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerLine.js [deleted file]
server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewer.js [deleted file]
server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewerBase.js [deleted file]
server/sonar-web/src/main/js/components/SourceViewer/helpers/getCoverageStatus.js [deleted file]
server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.js [deleted file]
server/sonar-web/src/main/js/components/SourceViewer/helpers/indexing.js [deleted file]
server/sonar-web/src/main/js/components/SourceViewer/helpers/issueLocations.js [deleted file]
server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.js [deleted file]
server/sonar-web/src/main/js/components/SourceViewer/types.js [deleted file]
server/sonar-web/src/main/js/components/common/popup.js
server/sonar-web/src/main/js/components/issue/ConnectedIssue.js [deleted file]
server/sonar-web/src/main/js/components/issue/Issue.js [deleted file]
server/sonar-web/src/main/js/components/issue/issue-view.js
server/sonar-web/src/main/js/components/issue/templates/issue.hbs
server/sonar-web/src/main/js/components/issue/types.js [deleted file]
server/sonar-web/src/main/js/components/shared/WithStore.js [deleted file]
server/sonar-web/src/main/js/components/source-viewer/SourceViewer.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/source-viewer/main.js
server/sonar-web/src/main/js/components/source-viewer/measures-overlay.js
server/sonar-web/src/main/js/components/source-viewer/more-actions.js
server/sonar-web/src/main/js/components/source-viewer/popups/coverage-popup.js
server/sonar-web/src/main/js/components/source-viewer/popups/duplication-popup.js
server/sonar-web/src/main/js/components/source-viewer/popups/line-actions-popup.js
server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js
server/sonar-web/src/main/js/components/source-viewer/source.js
server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-coverage-popup.hbs
server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-duplication-popup.hbs
server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-header.hbs
server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-measures.hbs
server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-scm-popup.hbs
server/sonar-web/src/main/js/components/workspace/main.js
server/sonar-web/src/main/js/components/workspace/models/item.js
server/sonar-web/src/main/js/components/workspace/models/items.js
server/sonar-web/src/main/js/components/workspace/views/viewer-view.js
server/sonar-web/src/main/js/helpers/issues.js [deleted file]
server/sonar-web/src/main/js/helpers/request.js
server/sonar-web/src/main/js/store/favorites/duck.js
server/sonar-web/src/main/js/store/issues/duck.js [deleted file]
server/sonar-web/src/main/js/store/rootReducer.js
server/sonar-web/src/main/less/components/issues.less
server/sonar-web/src/main/less/components/source.less
server/sonar-web/src/main/less/pages/issues.less
server/sonar-web/src/main/less/sonar-colorizer.less

index fa07c2e89207341ad928091aa230707a42cfded5..73fc5366f6e0444b2658dcc5552778fc411cecb0 100644 (file)
@@ -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) {
index d06748ba5e99525ee11ad2e61cfc00ae1271f575..8863fa6535d1de822fae8a6c3fb739b9bc4e143a 100644 (file)
@@ -7,7 +7,7 @@
   "project": "com.sonarsource:java-markdown",
   "projectName": "Java Markdown",
   "fav": false,
-  "canMarkAsFavorite": true,
+  "canMarkAsFavourite": true,
   "canCreateManualIssue": true,
   "measures": {
     "lines": "786",
index b1d32d8d8cf879f34b6140b9c0bade97f56dddae..ce42f92b41ee2b1ebb1630ee9708fafd73e405d5 100644 (file)
@@ -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);
   }
 }
index e3554e20775632b7da79f8ca9b56be8b4cb11f7c..f7f2947c1894c8dd414e4299c1b8f1a8664b2b1c 100644 (file)
@@ -10,6 +10,6 @@
   "project": "org.sonarsource.sonarqube:sonarqube",
   "projectName": "SonarQube",
   "fav": false,
-  "canMarkAsFavorite": true,
+  "canMarkAsFavourite": true,
   "measures": {}
 }
index 64548fda18fd9b5bac4a2b62535af6bf7a63d598..f50715b4b2c3f1cfd19a7c899515587be05d4bc9 100644 (file)
@@ -10,7 +10,7 @@
   "project": "org.sonarsource.sonarqube:sonarqube",
   "projectName": "SonarQube",
   "fav": false,
-  "canMarkAsFavorite": true,
+  "canMarkAsFavourite": true,
   "measures": {
     "lines": "200.0",
     "coverage": "95.4",
index f906f7dccbf4c10e9573726e9ab20ace57ea47dd..59b6db6325a0eab5b0fe92c301c398ec37434f13 100644 (file)
@@ -10,7 +10,7 @@
   "project": "org.sonarsource.sonarqube:sonarqube",
   "projectName": "SonarQube",
   "fav": false,
-  "canMarkAsFavorite": true,
+  "canMarkAsFavourite": true,
   "measures": {
     "coverage": "95.4"
   }
index be74c6cf9b4317017a58d30ca40d82edcd90acb5..6632e18d8e4ea27c465dbf814a6cea5252887b3c 100644 (file)
@@ -13,8 +13,7 @@
   "globals": {
     "key": true,
     "d3": true,
-    "baseUrl": true,
-    "SyntheticInputEvent": true
+    "baseUrl": true
   },
 
   "parser": "babel-eslint",
index c09192522e35efffece69edbad6cf8ce97be5bf9..07ca4204d0bcd520ca5974f38361d5f39a97c368 100644 (file)
@@ -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)
-);
index 75cbdec6e2d3a10cc01260ea719dd6fa04f767f2..adcb3a1626be5f6335737c0d951930291bc21fc4 100644 (file)
 // @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 };
   });
 }
 
index 4208e8d409b87a6ff6bb4d31453120bb41c48ffd..d4a107033d1466546055f85e85a3e3522b3857e1 100644 (file)
@@ -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>
index 20670ac9bcb2c12b8b1b6050a5bbccc17b3650b6..33adce19b8a0416cbcc84bce0b848e3734fb53bb 100644 (file)
@@ -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 (
index 4cd674b7c1f6f14a30c0aa2d832870697ad879f5..d8ad4c31afc023f86085ad142e9ddf0db7228d17 100644 (file)
@@ -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);
     }
index bbfc0ae32bf6d3dd09bf1d7c8fb03cb09c8b56e5..149582321dc83969bad9c4df263721dac0d3f54e 100644 (file)
  */
 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>
index fb0bb744bd04b10f5ee3a3d3d064f46236f15309..554a290422798d5c5ba9981967cadcac86dff7c4 100644 (file)
  * 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>
index b599af3cd2bb80b6af93e2d5c184fbc7772cf3c4..2255ef5c4c824a8043475c39d1ff3d974aefd0d2 100644 (file)
@@ -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;
     }
 
index 041625d8243770b114f547635a2877ad1d431c6d..d889e4df77f27a005a6265c47e152d600d817397 100644 (file)
  */
 // @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/apps/issues/component-viewer/issue-view.js b/server/sonar-web/src/main/js/apps/issues/component-viewer/issue-view.js
new file mode 100644 (file)
index 0000000..95e1819
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import IssueView from '../workspace-list-item-view';
+
+export default IssueView.extend({
+  onRender () {
+    IssueView.prototype.onRender.apply(this, arguments);
+    this.$el.removeClass('issue-navigate-right issue-with-checkbox');
+  },
+
+  serializeData () {
+    return {
+      ...IssueView.prototype.serializeData.apply(this, arguments),
+      showComponent: false
+    };
+  }
+});
+
index 49d7152c45fc15b9dd4cb7f0394e6205893a6e85..0f08c21112a26667bd48053c3ee8fca85512271d 100644 (file)
  * 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();
   }
 });
 
index 71df7acdb7ce4798080c97c14b8353f01e83d61d..edc868270507eeb10539d37fee14bfcd8dbbc3a1 100644 (file)
@@ -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 (file)
index 0000000..dbb50e2
--- /dev/null
@@ -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 (file)
index 0000000..16a212d
--- /dev/null
@@ -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>
index 43817e46586a770353dead2d9ed850a4444c37ed..9c57d181aa93bf1f858fc70d7a3bce70b2444d6b 100644 (file)
  * 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
+    };
   }
 });
index 383d3145f24b0c4d4780d94757b02fc388b45698..669f4c139e64b12b21ec99e1df5c586d36835a74 100644 (file)
@@ -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 });
index 91e636eb52ab014ce41b94dc0f0dcf15aaeb2fc1..841475306470d66e7e8c8c66aa4d87b9bdcae0b7 100644 (file)
@@ -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 (file)
index 2c6e5b5..0000000
+++ /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 (file)
index 2ad750e..0000000
+++ /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 (file)
index 32092dd..0000000
+++ /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 (file)
index 14dedd8..0000000
+++ /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 (file)
index f699394..0000000
+++ /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 (file)
index 72cb0d5..0000000
+++ /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 (file)
index d673bd4..0000000
+++ /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 (file)
index ea28e00..0000000
+++ /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 (file)
index 2f99ed8..0000000
+++ /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 (file)
index 0adc3f0..0000000
+++ /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 (file)
index a9016ef..0000000
+++ /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 (file)
index d2c8991..0000000
+++ /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 (file)
index ddc2963..0000000
+++ /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 (file)
index 3dd00ee..0000000
+++ /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
-  }>
-};
index af7d22f632c1e9c3f4280a86c3709214d775a509..363f0bdf72b331fb87282cba56f720f51dcf99c7 100644 (file)
@@ -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/ConnectedIssue.js b/server/sonar-web/src/main/js/components/issue/ConnectedIssue.js
deleted file mode 100644 (file)
index 28be4c7..0000000
+++ /dev/null
@@ -1,29 +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 Issue from './Issue';
-import { getIssueByKey } from '../../store/rootReducer';
-
-const mapStateToProps = (state, ownProps) => ({
-  issue: getIssueByKey(state, ownProps.issueKey)
-});
-
-export default connect(mapStateToProps)(Issue);
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 (file)
index c437b8f..0000000
+++ /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);
index a4a691fa95582c0ceb6a5abc120e0b870f41aee1..71ced0ff47ab259b4608fb556b2dc735ecde907e 100644 (file)
@@ -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
     };
   }
 });
+
index f951a40c0c4bf928d9c4c840cf6cd90393090650..a828ecf5e3eb049e9be845bbbfc0d125a412aa9c 100644 (file)
           <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>
   <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 (file)
index dd0bbc1..0000000
+++ /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 (file)
index f3cb832..0000000
+++ /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 (file)
index 0000000..0589bcd
--- /dev/null
@@ -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"/>;
+  }
+}
index 8b1725d09f3d8820f07436383520edf8de6a2597..58bfb9ce4f802b5498d8d8f791672de20c2d7f5a 100644 (file)
@@ -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();
   },
index 4baf170a2e8f81c79d5c9632976371340fa84e2f..a01d69b0f8550e4f18d1c767e7903b734545bb77 100644 (file)
@@ -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({
     };
   }
 });
+
index aba02a8e1de7e378c4374c49bebaf73303bf5b31..9b7181a64634f2d900dafddb1ea3b8f10007e8ac 100644 (file)
@@ -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({
     };
   }
 });
+
index 68fd0ccc388c081738f2612c712ca8ca7e725d18..1440241e42a7f527c68dadd95d5ac883ea1d34dc 100644 (file)
@@ -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 };
   }
 });
+
index da542333a306fba01fb21441c99e7df643ecad9c..24ad94fe25491648580357027e25b385844cb8b3 100644 (file)
@@ -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
     };
   }
 });
+
index a2d94f568b8131a3a8030cfd7ce309f26eb51018..aa89585bc44b2a81070241559e07e4bc311db932 100644 (file)
@@ -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);
   }
 });
index f140e37c56bf540a1ee8258907b97a5dbcb0ccc7..755a866baec5472319f9c6501a16bd4774eff04a 100644 (file)
@@ -34,12 +34,6 @@ export default Popup.extend({
 
   onClick (e) {
     e.stopPropagation();
-  },
-
-  serializeData () {
-    return {
-      ...Popup.prototype.serializeData.apply(this, arguments),
-      line: this.options.line
-    };
   }
 });
+
index 3cb1198e3284cbea8582dc8a42e2aa253c216e91..43009061a22d608937ae83f26e36481c77bb26f2 100644 (file)
@@ -96,3 +96,4 @@ export default Backbone.Model.extend({
     return source.some(line => line.coverageStatus != null);
   }
 });
+
index 57c6301119ec5fe8e9a41b1d58e083a9c4982697..a0e7b62896eed81cd5ffd357f3d8a0e9d84b7d46 100644 (file)
@@ -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>
index ea8fc2b2349956e1860f87e3aab95d3f715d532a..9b0783c6655a30778d60a5c315596d8b6605e586 100644 (file)
@@ -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}}
index a453235448180bf838c5cafe46d3271978eda9ad..e276c7e938b1c8dd01fda45514affce6d984eb75 100644 (file)
@@ -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>
index 0df076390c9846fbdbb7ea965a2445ddb4c6a128..a3f4df55605c95ed22e6d53af7e5f5eba31a394f 100644 (file)
     {{/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">
         {{> '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>
index dd82aca528c7f0e115815f2124ab45197219e832..768ea72341dd8ff46dbf800139c053fc03d854b3 100644 (file)
@@ -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>
index 4e1170bac3869e339f3198b02ba97d16a3dc5b9b..30082332e4b2d807d2f380d5f293dfdf326fb747 100644 (file)
@@ -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) {
index 1dd6daf7fc5fb7cc0d85e15de74b8eb6cbb2302e..0ecbef4ac33744ebbcd77158545e5e3f1cd9f791 100644 (file)
@@ -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';
index 97ff41e226733d614d1d4120fb7107788d57cd22..5d015e037ea12f2f4c6a9bcd52bced40adab65fb 100644 (file)
@@ -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);
   }
 });
+
index 7ab96e7c683ccf5bb6130b1052aac3d98b38e567..924ea80ad7fbb28fd1cbf4c631627bd6a657e29f 100644 (file)
  * 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 (file)
index 3a1e509..0000000
+++ /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)
-  };
-};
index cbd5a4e7c0198f2ed37dd12b1b7865568dead135..80bb9e787ccee47f3b439a5513da7f4100f4f340 100644 (file)
@@ -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;
+  }
 }
 
 /**
index ceeb119abfa8508e724a629438714a07bc6834b2..c97f715edbd2caabab79bb43e6e22b9b43ad09ba 100644 (file)
  * 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 (file)
index 1126bcf..0000000
+++ /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]
-);
index 1b8539f84ebb234c388dcacd120b1a571822ae5c..aee309845c2e5ead300d74a8abc911efa6b2f4d3 100644 (file)
@@ -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)
 );
index 8202664e4cc7747eac4da3459596c18a92091172..b56858fe641d3bb959598fb151da0c9d543bc585 100644 (file)
@@ -50,8 +50,7 @@
   border-color: @issueBorderColor !important;
 }
 
-.issue + .issue,
-.issue-container + .issue-container {
+.issue + .issue {
   margin-top: 5px;
 }
 
index 9a89d87959ac3a61410dda5b4ef362842e0f2023..18a2cfa5d1c2e418bcaca700c0b4b51cb1257c77 100644 (file)
   user-select: none;
 }
 
-.source-meta:focus {
-  outline: none;
-}
-
-.source-meta[role="button"] {
-  cursor: pointer;
-}
-
 .source-meta + .source-meta {
   border-left: 1px solid @barBackgroundColor;
 }
   color: @secondFontColor;
   text-align: right;
 
+  &[data-line-number] {
+    cursor: pointer;
+  }
+
   &:before {
     content: attr(data-line-number);
   }
 .source-line-scm {
   padding: 0 5px;
   background-color: @barBackgroundColor;
+
+  &[data-line-number] {
+    cursor: pointer;
+  }
 }
 
 .source-line-scm-inner {
   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;
 }
 
 
index 98bf375c2ea53e7c311ed2e169204015df2fda64..fd1ddd7431e2eddd5b56ef035a8bc8c5330d9b1d 100644 (file)
   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;
 }
 
index 760aec2845346ef7d05c250350f880c7d817cdc1..325bcad8177314540eabfc2db8b47454f6d0c4bd 100644 (file)
   cursor: pointer;
 }
 .highlighted {
-  background-color: #b3d4ff;
-  animation: highlightedFadeIn 0.3s forwards;
-}
-
-@keyframes highlightedFadeIn {
-  from { background-color: transparent; }
-  to { background-color: #b3d4ff; }
+  background-color: #B3D4FF;
 }