aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/components
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main/js/components')
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewer.js47
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js499
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js222
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.js185
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerIssuesIndicator.js44
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerLine.js377
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewer.js47
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewerBase.js50
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/getCoverageStatus.js37
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.js115
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/indexing.js119
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/issueLocations.js59
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.js76
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/types.js40
-rw-r--r--server/sonar-web/src/main/js/components/common/popup.js22
-rw-r--r--server/sonar-web/src/main/js/components/issue/ConnectedIssue.js29
-rw-r--r--server/sonar-web/src/main/js/components/issue/Issue.js133
-rw-r--r--server/sonar-web/src/main/js/components/issue/issue-view.js55
-rw-r--r--server/sonar-web/src/main/js/components/issue/templates/issue.hbs15
-rw-r--r--server/sonar-web/src/main/js/components/issue/types.js40
-rw-r--r--server/sonar-web/src/main/js/components/shared/WithStore.js44
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/SourceViewer.js82
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/main.js17
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/measures-overlay.js3
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/more-actions.js5
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/popups/coverage-popup.js17
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/popups/duplication-popup.js20
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/popups/line-actions-popup.js6
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js8
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/source.js1
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-coverage-popup.hbs4
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-duplication-popup.hbs4
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-header.hbs2
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-measures.hbs22
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-scm-popup.hbs8
-rw-r--r--server/sonar-web/src/main/js/components/workspace/main.js3
-rw-r--r--server/sonar-web/src/main/js/components/workspace/models/item.js4
-rw-r--r--server/sonar-web/src/main/js/components/workspace/models/items.js7
-rw-r--r--server/sonar-web/src/main/js/components/workspace/views/viewer-view.js55
39 files changed, 181 insertions, 2342 deletions
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewer.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewer.js
deleted file mode 100644
index 2c6e5b594a6..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewer.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import { connect } from 'react-redux';
-import SourceViewerBase from './SourceViewerBase';
-import { receiveFavorites } from '../../store/favorites/duck';
-import { receiveIssues } from '../../store/issues/duck';
-
-const mapStateToProps = null;
-
-const onReceiveComponent = (component: { key: string, canMarkAsFavorite: boolean, fav: boolean }) => dispatch => {
- if (component.canMarkAsFavorite) {
- const favorites = [];
- const notFavorites = [];
- if (component.fav) {
- favorites.push({ key: component.key });
- } else {
- notFavorites.push({ key: component.key });
- }
- dispatch(receiveFavorites(favorites, notFavorites));
- }
-};
-
-const onReceiveIssues = (issues: Array<*>) => dispatch => {
- dispatch(receiveIssues(issues));
-};
-
-const mapDispatchToProps = { onReceiveComponent, onReceiveIssues };
-
-export default connect(mapStateToProps, mapDispatchToProps)(SourceViewerBase);
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js
deleted file mode 100644
index 2ad750e7120..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js
+++ /dev/null
@@ -1,499 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-import uniqBy from 'lodash/uniqBy';
-import SourceViewerHeader from './SourceViewerHeader';
-import SourceViewerCode from './SourceViewerCode';
-import CoveragePopupView from '../source-viewer/popups/coverage-popup';
-import DuplicationPopupView from '../source-viewer/popups/duplication-popup';
-import LineActionsPopupView from '../source-viewer/popups/line-actions-popup';
-import SCMPopupView from '../source-viewer/popups/scm-popup';
-import MeasuresOverlay from '../source-viewer/measures-overlay';
-import { TooltipsContainer } from '../mixins/tooltips-mixin';
-import Source from '../source-viewer/source';
-import loadIssues from './helpers/loadIssues';
-import getCoverageStatus from './helpers/getCoverageStatus';
-import {
- issuesByLine,
- locationsByLine,
- locationsByIssueAndLine,
- locationMessagesByIssueAndLine,
- duplicationsByLine,
- symbolsByLine
-} from './helpers/indexing';
-import { getComponentForSourceViewer, getSources, getDuplications, getTests } from '../../api/components';
-import { translate } from '../../helpers/l10n';
-import type { SourceLine } from './types';
-import type { Issue } from '../issue/types';
-
-// TODO react-virtualized
-
-type Props = {
- aroundLine?: number,
- component: string,
- displayAllIssues: boolean,
- filterLine?: (line: SourceLine) => boolean,
- highlightedLine?: number,
- loadComponent: (string) => Promise<*>,
- loadIssues: (string, number, number) => Promise<*>,
- loadSources: (string, number, number) => Promise<*>,
- onLoaded?: (component: Object, sources: Array<*>, issues: Array<*>) => void,
- onIssueSelect: (string) => void,
- onIssueUnselect: () => void,
- onReceiveComponent: ({ canMarkAsFavorite: boolean, fav: boolean, key: string }) => void,
- onReceiveIssues: (issues: Array<*>) => void,
- selectedIssue: string | null,
-};
-
-type State = {
- component?: Object,
- displayDuplications: boolean,
- duplications?: Array<{
- blocks: Array<{
- _ref: string,
- from: number,
- size: number
- }>
- }>,
- duplicationsByLine: { [number]: Array<number> },
- duplicatedFiles?: Array<{ key: string }>,
- hasSourcesAfter: boolean,
- highlightedLine: number | null,
- highlightedSymbol: string | null,
- issues?: Array<Issue>,
- issuesByLine: { [number]: Array<string> },
- issueLocationsByLine: { [number]: Array<{ from: number, to: number }> },
- issueSecondaryLocationsByIssueByLine: {
- [string]: {
- [number]: Array<{ from: number, to: number }>
- }
- },
- issueSecondaryLocationMessagesByIssueByLine: {
- [issueKey: string]: {
- [line: number]: Array<{ msg: string, index?: number }>
- }
- },
- loading: boolean,
- loadingSourcesAfter: boolean,
- loadingSourcesBefore: boolean,
- notAccessible: boolean,
- notExist: boolean,
- sources?: Array<SourceLine>,
- symbolsByLine: { [number]: Array<string> }
-};
-
-const LINES = 500;
-
-const loadComponent = (key: string): Promise<*> => {
- return getComponentForSourceViewer(key);
-};
-
-const loadSources = (key: string, from?: number, to?: number): Promise<Array<*>> => {
- return getSources(key, from, to);
-};
-
-export default class SourceViewerBase extends React.Component {
- mounted: boolean;
- node: HTMLElement;
- props: Props;
- state: State;
-
- static defaultProps = {
- displayAllIssues: false,
- onIssueSelect: () => { },
- onIssueUnselect: () => { },
- loadComponent,
- loadIssues,
- loadSources
- };
-
- constructor (props: Props) {
- super(props);
- this.state = {
- displayDuplications: false,
- duplicationsByLine: {},
- hasSourcesAfter: false,
- highlightedLine: props.highlightedLine || null,
- highlightedSymbol: null,
- issuesByLine: {},
- issueLocationsByLine: {},
- issueSecondaryLocationsByIssueByLine: {},
- issueSecondaryLocationMessagesByIssueByLine: {},
- loading: true,
- loadingSourcesAfter: false,
- loadingSourcesBefore: false,
- notAccessible: false,
- notExist: false,
- selectedIssue: props.defaultSelectedIssue || null,
- symbolsByLine: {}
- };
- }
-
- componentDidMount () {
- this.mounted = true;
- this.fetchComponent();
- }
-
- componentDidUpdate (prevProps: Props) {
- if (prevProps.component !== this.props.component) {
- this.fetchComponent();
- } else if (this.props.aroundLine != null && prevProps.aroundLine !== this.props.aroundLine &&
- this.isLineOutsideOfRange(this.props.aroundLine)) {
- this.fetchSources();
- }
- }
-
- componentWillUnmount () {
- this.mounted = false;
- }
-
- computeCoverageStatus (lines: Array<SourceLine>): Array<SourceLine> {
- return lines.map(line => ({ ...line, coverageStatus: getCoverageStatus(line) }));
- }
-
- isLineOutsideOfRange (lineNumber: number) {
- const { sources } = this.state;
- if (sources != null && sources.length > 0) {
- const firstLine = sources[0];
- const lastList = sources[sources.length - 1];
- return lineNumber < firstLine.line || lineNumber > lastList.line;
- } else {
- return true;
- }
- }
-
- fetchComponent () {
- this.setState({ loading: true });
-
- const loadIssues = (component, sources) => {
- this.props.loadIssues(this.props.component, 1, LINES).then(issues => {
- this.props.onReceiveIssues(issues);
- if (this.mounted) {
- const finalSources = sources.slice(0, LINES);
- this.setState({
- component,
- issues,
- issuesByLine: issuesByLine(issues),
- issueLocationsByLine: locationsByLine(issues),
- issueSecondaryLocationsByIssueByLine: locationsByIssueAndLine(issues),
- issueSecondaryLocationMessagesByIssueByLine: locationMessagesByIssueAndLine(issues),
- loading: false,
- hasSourcesAfter: sources.length > LINES,
- sources: this.computeCoverageStatus(finalSources),
- symbolsByLine: symbolsByLine(sources.slice(0, LINES))
- }, () => {
- if (this.props.onLoaded) {
- this.props.onLoaded(component, finalSources, issues);
- }
- });
- }
- });
- };
-
- const onFailLoadComponent = ({ response }) => {
- // TODO handle other statuses
- if (this.mounted && response.status === 404) {
- this.setState({ loading: false, notExist: true });
- }
- };
-
- const onFailLoadSources = (response, component) => {
- // TODO handle other statuses
- if (this.mounted) {
- if (response.status === 403) {
- this.setState({ component, loading: false, notAccessible: true });
- }
- }
- };
-
- const onResolve = component => {
- this.props.onReceiveComponent(component);
- this.loadSources().then(
- sources => loadIssues(component, sources),
- response => onFailLoadSources(response, component)
- );
- };
-
- this.props.loadComponent(this.props.component).then(onResolve, onFailLoadComponent);
- }
-
- fetchSources () {
- this.loadSources().then(sources => {
- if (this.mounted) {
- const finalSources = sources.slice(0, LINES);
- this.setState({
- sources: sources.slice(0, LINES),
- hasSourcesAfter: sources.length > LINES
- }, () => {
- if (this.props.onLoaded) {
- // $FlowFixMe
- this.props.onLoaded(this.state.component, finalSources, this.state.issues);
- }
- });
- }
- });
- }
-
- loadSources () {
- return new Promise((resolve, reject) => {
- const onFailLoadSources = ({ response }) => {
- // TODO handle other statuses
- if (this.mounted) {
- if (response.status === 403) {
- reject(response);
- } else if (response.status === 404) {
- resolve([]);
- }
- }
- };
-
- const from = this.props.aroundLine ? Math.max(1, this.props.aroundLine - LINES / 2 + 1) : 1;
- // request one additional line to define `hasSourcesAfter`
- const to = this.props.aroundLine ? this.props.aroundLine + LINES / 2 + 1 : LINES + 1;
-
- return this.props.loadSources(this.props.component, from, to).then(
- sources => resolve(sources),
- onFailLoadSources
- );
- });
- }
-
- loadSourcesBefore = () => {
- if (!this.state.sources) {
- return;
- }
- const firstSourceLine = this.state.sources[0];
- this.setState({ loadingSourcesBefore: true });
- const from = Math.max(1, firstSourceLine.line - LINES);
- this.props.loadSources(this.props.component, from, firstSourceLine.line - 1).then(sources => {
- this.props.loadIssues(this.props.component, from, firstSourceLine.line - 1).then(issues => {
- this.props.onReceiveIssues(issues);
- if (this.mounted) {
- this.setState(prevState => ({
- issues: uniqBy([...issues, ...prevState.issues], issue => issue.key),
- loadingSourcesBefore: false,
- sources: [...this.computeCoverageStatus(sources), ...prevState.sources],
- symbolsByLine: { ...prevState.symbolsByLine, ...symbolsByLine(sources) }
- }));
- }
- });
- });
- };
-
- loadSourcesAfter = () => {
- if (!this.state.sources) {
- return;
- }
- const lastSourceLine = this.state.sources[this.state.sources.length - 1];
- this.setState({ loadingSourcesAfter: true });
- const fromLine = lastSourceLine.line + 1;
- // request one additional line to define `hasSourcesAfter`
- const toLine = lastSourceLine.line + LINES + 1;
- this.props.loadSources(this.props.component, fromLine, toLine).then(sources => {
- this.props.loadIssues(this.props.component, fromLine, toLine).then(issues => {
- this.props.onReceiveIssues(issues);
- if (this.mounted) {
- this.setState(prevState => ({
- issues: uniqBy([...prevState.issues, ...issues], issue => issue.key),
- hasSourcesAfter: sources.length > LINES,
- loadingSourcesAfter: false,
- sources: [...prevState.sources, ...this.computeCoverageStatus(sources.slice(0, LINES))],
- symbolsByLine: { ...prevState.symbolsByLine, ...symbolsByLine(sources.slice(0, LINES)) }
- }));
- }
- });
- });
- };
-
- loadDuplications = (line: SourceLine, element: HTMLElement) => {
- getDuplications(this.props.component).then(r => {
- if (this.mounted) {
- this.setState({
- displayDuplications: true,
- duplications: r.duplications,
- duplicationsByLine: duplicationsByLine(r.duplications),
- duplicatedFiles: r.files
- }, () => {
- // immediately show dropdown popup if there is only one duplicated block
- if (r.duplications.length === 1) {
- this.handleDuplicationClick(0, line.line, element);
- }
- });
- }
- });
- };
-
- openNewWindow = () => {
- const { component } = this.state;
- if (component != null) {
- let query = 'id=' + encodeURIComponent(component.key);
- const windowParams = 'resizable=1,scrollbars=1,status=1';
- if (this.state.highlightedLine) {
- query = query + '&line=' + this.state.highlightedLine;
- }
- window.open(window.baseUrl + '/component/index?' + query, component.name, windowParams);
- }
- };
-
- showMeasures = () => {
- const model = new Source(this.state.component);
- const measuresOvervlay = new MeasuresOverlay({ model, large: true });
- measuresOvervlay.render();
- };
-
- handleCoverageClick = (line: SourceLine, element: HTMLElement) => {
- getTests(this.props.component, line.line).then(tests => {
- const popup = new CoveragePopupView({ line, tests, triggerEl: element });
- popup.render();
- });
- };
-
- handleDuplicationClick = (index: number, line: number) => {
- const duplication = this.state.duplications && this.state.duplications[index];
- let blocks = (duplication && duplication.blocks) || [];
- const inRemovedComponent = blocks.some(b => b._ref == null);
- let foundOne = false;
- blocks = blocks.filter(b => {
- const outOfBounds = b.from > line || b.from + b.size < line;
- const currentFile = b._ref === '1';
- const shouldDisplayForCurrentFile = outOfBounds || foundOne;
- const shouldDisplay = !currentFile || shouldDisplayForCurrentFile;
- const isOk = (b._ref != null) && shouldDisplay;
- if (b._ref === '1' && !outOfBounds) {
- foundOne = true;
- }
- return isOk;
- });
-
- const element = this.node.querySelector(`.source-line-duplications-extra[data-line-number="${line}"]`);
- if (element) {
- const popup = new DuplicationPopupView({
- blocks,
- inRemovedComponent,
- component: this.state.component,
- files: this.state.duplicatedFiles,
- triggerEl: element
- });
- popup.render();
- }
- };
-
- displayLinePopup (line: number, element: HTMLElement) {
- const popup = new LineActionsPopupView({
- line,
- triggerEl: element,
- component: this.state.component
- });
- popup.render();
- }
-
- handleLineClick = (line: number, element: HTMLElement) => {
- this.setState(prevState => ({
- highlightedLine: prevState.highlightedLine === line ? null : line
- }));
- this.displayLinePopup(line, element);
- };
-
- handleSymbolClick = (symbol: string) => {
- this.setState(prevState => ({
- highlightedSymbol: prevState.highlightedSymbol === symbol ? null : symbol
- }));
- };
-
- handleSCMClick = (line: SourceLine, element: HTMLElement) => {
- const popup = new SCMPopupView({ triggerEl: element, line });
- popup.render();
- };
-
- renderCode (sources: Array<SourceLine>) {
- const hasSourcesBefore = sources.length > 0 && sources[0].line > 1;
- return (
- <TooltipsContainer>
- <SourceViewerCode
- displayAllIssues={this.props.displayAllIssues}
- duplications={this.state.duplications}
- duplicationsByLine={this.state.duplicationsByLine}
- duplicatedFiles={this.state.duplicatedFiles}
- hasSourcesBefore={hasSourcesBefore}
- hasSourcesAfter={this.state.hasSourcesAfter}
- filterLine={this.props.filterLine}
- highlightedLine={this.state.highlightedLine}
- highlightedSymbol={this.state.highlightedSymbol}
- issues={this.state.issues}
- issuesByLine={this.state.issuesByLine}
- issueLocationsByLine={this.state.issueLocationsByLine}
- issueSecondaryLocationsByIssueByLine={this.state.issueSecondaryLocationsByIssueByLine}
- issueSecondaryLocationMessagesByIssueByLine={this.state.issueSecondaryLocationMessagesByIssueByLine}
- loadDuplications={this.loadDuplications}
- loadSourcesAfter={this.loadSourcesAfter}
- loadSourcesBefore={this.loadSourcesBefore}
- loadingSourcesAfter={this.state.loadingSourcesAfter}
- loadingSourcesBefore={this.state.loadingSourcesBefore}
- onCoverageClick={this.handleCoverageClick}
- onDuplicationClick={this.handleDuplicationClick}
- onIssueSelect={this.props.onIssueSelect}
- onIssueUnselect={this.props.onIssueUnselect}
- onLineClick={this.handleLineClick}
- onSCMClick={this.handleSCMClick}
- onSymbolClick={this.handleSymbolClick}
- selectedIssue={this.props.selectedIssue}
- sources={sources}
- symbolsByLine={this.state.symbolsByLine}/>
- </TooltipsContainer>
- );
- }
-
- render () {
- const { component, loading } = this.state;
-
- if (loading) {
- return null;
- }
-
- if (this.state.notExist) {
- return (
- <div className="alert alert-warning spacer-top">{translate('component_viewer.no_component')}</div>
- );
- }
-
- if (component == null) {
- return null;
- }
-
- const className = classNames('source-viewer', { 'source-duplications-expanded': this.state.displayDuplications });
-
- return (
- <div className={className} ref={node => this.node = node}>
- <SourceViewerHeader
- component={this.state.component}
- openNewWindow={this.openNewWindow}
- showMeasures={this.showMeasures}/>
- {this.state.notAccessible && (
- <div className="alert alert-warning spacer-top">
- {translate('code_viewer.no_source_code_displayed_due_to_security')}
- </div>
- )}
- {this.state.sources != null && this.renderCode(this.state.sources)}
- </div>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js
deleted file mode 100644
index 32092dd47c5..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import SourceViewerLine from './SourceViewerLine';
-import { translate } from '../../helpers/l10n';
-import type { Duplication, SourceLine } from './types';
-import type { Issue } from '../issue/types';
-
-const EMPTY_ARRAY = [];
-
-const ZERO_LINE = {
- code: '',
- duplicated: false,
- line: 0
-};
-
-export default class SourceViewerCode extends React.Component {
- props: {
- displayAllIssues: boolean,
- duplications?: Array<Duplication>,
- duplicationsByLine: { [number]: Array<number> },
- duplicatedFiles?: Array<{ key: string }>,
- filterLine?: (SourceLine) => boolean,
- hasSourcesAfter: boolean,
- hasSourcesBefore: boolean,
- highlightedLine: number | null,
- highlightedSymbol: string | null,
- issues: Array<Issue>,
- issuesByLine: { [number]: Array<string> },
- issueLocationsByLine: { [number]: Array<{ from: number, to: number }> },
- issueSecondaryLocationsByIssueByLine: {
- [string]: {
- [number]: Array<{ from: number, to: number }>
- }
- },
- issueSecondaryLocationMessagesByIssueByLine: {
- [issueKey: string]: {
- [line: number]: Array<{ msg: string, index?: number }>
- }
- },
- loadDuplications: (SourceLine, HTMLElement) => void,
- loadSourcesAfter: () => void,
- loadSourcesBefore: () => void,
- loadingSourcesAfter: boolean,
- loadingSourcesBefore: boolean,
- onCoverageClick: (SourceLine, HTMLElement) => void,
- onDuplicationClick: (number, number) => void,
- onIssueSelect: (string) => void,
- onIssueUnselect: () => void,
- onLineClick: (number, HTMLElement) => void,
- onSCMClick: (SourceLine, HTMLElement) => void,
- onSymbolClick: (string) => void,
- selectedIssue: string | null,
- sources: Array<SourceLine>,
- symbolsByLine: { [number]: Array<string> }
- };
-
- isSCMChanged (s: SourceLine, p: null | SourceLine) {
- let changed = true;
- if (p != null && s.scmAuthor != null && p.scmAuthor != null) {
- changed = (s.scmAuthor !== p.scmAuthor) || (s.scmDate !== p.scmDate);
- }
- return changed;
- }
-
- getDuplicationsForLine (line: SourceLine) {
- return this.props.duplicationsByLine[line.line] || EMPTY_ARRAY;
- }
-
- getIssuesForLine (line: SourceLine): Array<string> {
- return this.props.issuesByLine[line.line] || EMPTY_ARRAY;
- }
-
- getIssueLocationsForLine (line: SourceLine) {
- return this.props.issueLocationsByLine[line.line] || EMPTY_ARRAY;
- }
-
- getSecondaryIssueLocationsForLine (line: SourceLine, issueKey: string) {
- const index = this.props.issueSecondaryLocationsByIssueByLine;
- if (index[issueKey] == null) {
- return EMPTY_ARRAY;
- }
- return index[issueKey][line.line] || EMPTY_ARRAY;
- }
-
- getSecondaryIssueLocationMessagesForLine (line: SourceLine, issueKey: string) {
- return this.props.issueSecondaryLocationMessagesByIssueByLine[issueKey][line.line] || EMPTY_ARRAY;
- }
-
- renderLine = (
- line: SourceLine,
- index: number,
- displayCoverage: boolean,
- displayDuplications: boolean,
- displayFiltered: boolean,
- displayIssues: boolean
- ) => {
- const { filterLine, selectedIssue, sources } = this.props;
- const filtered = filterLine ? filterLine(line) : null;
- const secondaryIssueLocations = selectedIssue ?
- this.getSecondaryIssueLocationsForLine(line, selectedIssue) : EMPTY_ARRAY;
- const secondaryIssueLocationMessages = selectedIssue ?
- this.getSecondaryIssueLocationMessagesForLine(line, selectedIssue) : EMPTY_ARRAY;
-
- const duplicationsCount = this.props.duplications ? this.props.duplications.length : 0;
-
- const issuesForLine = this.getIssuesForLine(line);
-
- // for the following properties pass null if the line for sure is not impacted
- const symbolsForLine = this.props.symbolsByLine[line.line] || [];
- const { highlightedSymbol } = this.props;
- const optimizedHighlightedSymbol = highlightedSymbol != null && symbolsForLine.includes(highlightedSymbol) ?
- highlightedSymbol : null;
-
- const optimizedSelectedIssue = selectedIssue != null && issuesForLine.includes(selectedIssue) ?
- selectedIssue : null;
-
- return (
- <SourceViewerLine
- displayAllIssues={this.props.displayAllIssues}
- displayCoverage={displayCoverage}
- displayDuplications={displayDuplications}
- displayFiltered={displayFiltered}
- displayIssues={displayIssues}
- displaySCM={this.isSCMChanged(line, index > 0 ? sources[index - 1] : null)}
- duplications={this.getDuplicationsForLine(line)}
- duplicationsCount={duplicationsCount}
- filtered={filtered}
- highlighted={line.line === this.props.highlightedLine}
- highlightedSymbol={optimizedHighlightedSymbol}
- issueLocations={this.getIssueLocationsForLine(line)}
- issues={issuesForLine}
- key={line.line}
- line={line}
- loadDuplications={this.props.loadDuplications}
- onClick={this.props.onLineClick}
- onCoverageClick={this.props.onCoverageClick}
- onDuplicationClick={this.props.onDuplicationClick}
- onIssueSelect={this.props.onIssueSelect}
- onIssueUnselect={this.props.onIssueUnselect}
- onSCMClick={this.props.onSCMClick}
- onSymbolClick={this.props.onSymbolClick}
- secondaryIssueLocations={secondaryIssueLocations}
- secondaryIssueLocationMessages={secondaryIssueLocationMessages}
- selectedIssue={optimizedSelectedIssue}/>
- );
- };
-
- render () {
- const { sources } = this.props;
-
- const hasCoverage = sources.some(s => s.coverageStatus != null);
- const hasDuplications = sources.some(s => s.duplicated);
- const displayFiltered = this.props.filterLine != null;
- const hasIssues = this.props.issues.length > 0;
-
- const hasFileIssues = hasIssues && this.props.issues.some(issue => !issue.line);
-
- return (
- <div>
- {this.props.hasSourcesBefore && (
- <div className="source-viewer-more-code">
- {this.props.loadingSourcesBefore ? (
- <div className="js-component-viewer-loading-before">
- <i className="spinner"/>
- <span className="note spacer-left">{translate('source_viewer.loading_more_code')}</span>
- </div>
- ) : (
- <button className="js-component-viewer-source-before" onClick={this.props.loadSourcesBefore}>
- {translate('source_viewer.load_more_code')}
- </button>
- )}
- </div>
- )}
-
- <table className="source-table">
- <tbody>
- {hasFileIssues && (
- this.renderLine(ZERO_LINE, -1, hasCoverage, hasDuplications, displayFiltered, hasIssues)
- )}
- {sources.map((line, index) => (
- this.renderLine(line, index, hasCoverage, hasDuplications, displayFiltered, hasIssues)
- ))}
- </tbody>
- </table>
-
- {this.props.hasSourcesAfter && (
- <div className="source-viewer-more-code">
- {this.props.loadingSourcesAfter ? (
- <div className="js-component-viewer-loading-after">
- <i className="spinner"/>
- <span className="note spacer-left">{translate('source_viewer.loading_more_code')}</span>
- </div>
- ) : (
- <button className="js-component-viewer-source-after" onClick={this.props.loadSourcesAfter}>
- {translate('source_viewer.load_more_code')}
- </button>
- )}
- </div>
- )}
- </div>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.js
deleted file mode 100644
index 14dedd85572..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.js
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { Link } from 'react-router';
-import QualifierIcon from '../shared/qualifier-icon';
-import FavoriteContainer from '../controls/FavoriteContainer';
-import Workspace from '../workspace/main';
-import { getProjectUrl, getIssuesUrl } from '../../helpers/urls';
-import { collapsedDirFromPath, fileFromPath } from '../../helpers/path';
-import { translate } from '../../helpers/l10n';
-import { formatMeasure } from '../../helpers/measures';
-
-export default class SourceViewerHeader extends React.Component {
- props: {
- component: {
- canMarkAsFavorite: boolean,
- key: string,
- measures: {
- coverage?: string,
- duplicationDensity?: string,
- issues?: string,
- lines?: string,
- tests?: string
- },
- path: string,
- project: string,
- projectName: string,
- q: string,
- subProject?: string,
- subProjectName?: string
- },
- openNewWindow: () => void,
- showMeasures: () => void
- };
-
- showMeasures = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.props.showMeasures();
- };
-
- openNewWindow = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.props.openNewWindow();
- };
-
- openInWorkspace = (e: SyntheticInputEvent) => {
- e.preventDefault();
- const { key } = this.props.component;
- Workspace.openComponent({ key });
- };
-
- render () {
- const { key, measures, path, project, projectName, q, subProject, subProjectName } = this.props.component;
- const isUnitTest = q === 'UTS';
- // TODO check if source viewer is displayed inside workspace
- const workspace = false;
- const rawSourcesLink = `${window.baseUrl}/api/sources/raw?key=${encodeURIComponent(this.props.component.key)}`;
-
- // TODO favorite
- return (
- <div className="source-viewer-header">
- <div className="source-viewer-header-component">
- <div className="component-name">
- <div className="component-name-parent">
- <Link to={getProjectUrl(project)} className="link-with-icon">
- <QualifierIcon qualifier="TRK"/> <span>{projectName}</span>
- </Link>
- </div>
-
- {subProject != null && (
- <div className="component-name-parent">
- <Link to={getProjectUrl(subProject)} className="link-with-icon">
- <QualifierIcon qualifier="BRC"/> <span>{subProjectName}</span>
- </Link>
- </div>
- )}
-
- <div className="component-name-path">
- <QualifierIcon qualifier={q}/>
- {' '}
- <span>{collapsedDirFromPath(path)}</span>
- <span className="component-name-file">{fileFromPath(path)}</span>
-
- {this.props.component.canMarkAsFavorite && (
- <FavoriteContainer className="component-name-favorite" componentKey={key}/>
- )}
- </div>
- </div>
- </div>
-
- <div className="dropdown source-viewer-header-actions">
- <a className="js-actions icon-list dropdown-toggle"
- data-toggle="dropdown"
- title={translate('component_viewer.more_actions')}/>
- <ul className="dropdown-menu dropdown-menu-right">
- <li>
- <a className="js-measures" href="#" onClick={this.showMeasures}>
- {translate('component_viewer.show_details')}
- </a>
- </li>
- <li>
- <a className="js-new-window" href="#" onClick={this.openNewWindow}>
- {translate('component_viewer.new_window')}
- </a>
- </li>
- {!workspace && (
- <li>
- <a className="js-workspace" href="#" onClick={this.openInWorkspace}>
- {translate('component_viewer.open_in_workspace')}
- </a>
- </li>
- )}
- <li>
- <a className="js-raw-source" href={rawSourcesLink} target="_blank">
- {translate('component_viewer.show_raw_source')}
- </a>
- </li>
- </ul>
- </div>
-
- <div className="source-viewer-header-measures">
- {isUnitTest && (
- <div className="source-viewer-header-measure">
- <span className="source-viewer-header-measure-value">{formatMeasure(measures.tests, 'SHORT_INT')}</span>
- <span className="source-viewer-header-measure-label">{translate('metric.tests.name')}</span>
- </div>
- )}
-
- {!isUnitTest && (
- <div className="source-viewer-header-measure">
- <span className="source-viewer-header-measure-value">{formatMeasure(measures.lines, 'SHORT_INT')}</span>
- <span className="source-viewer-header-measure-label">{translate('metric.lines.name')}</span>
- </div>
- )}
-
- <div className="source-viewer-header-measure">
- <span className="source-viewer-header-measure-value">
- <Link to={getIssuesUrl({ resolved: 'false', componentKeys: key })}
- className="source-viewer-header-external-link" target="_blank">
- {measures.issues != null ? formatMeasure(measures.issues, 'SHORT_INT') : 0}
- {' '}
- <i className="icon-detach"/>
- </Link>
- </span>
- <span className="source-viewer-header-measure-label">{translate('metric.violations.name')}</span>
- </div>
-
- {measures.coverage != null && (
- <div className="source-viewer-header-measure">
- <span className="source-viewer-header-measure-value">{formatMeasure(measures.coverage, 'PERCENT')}</span>
- <span className="source-viewer-header-measure-label">{translate('metric.coverage.name')}</span>
- </div>
- )}
-
- {measures.duplicationDensity != null && (
- <div className="source-viewer-header-measure">
- <span className="source-viewer-header-measure-value">
- {formatMeasure(measures.duplicationDensity, 'PERCENT')}
- </span>
- <span className="source-viewer-header-measure-label">{translate('duplications')}</span>
- </div>
- )}
- </div>
- </div>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerIssuesIndicator.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerIssuesIndicator.js
deleted file mode 100644
index f6993949244..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerIssuesIndicator.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { connect } from 'react-redux';
-import SeverityIcon from '../shared/severity-icon';
-import { getIssueByKey } from '../../store/rootReducer';
-import { sortBySeverity } from '../../helpers/issues';
-
-class SourceViewerIssuesIndicator extends React.Component {
- props: {
- issue: { severity: string }
- };
-
- render () {
- return (
- <SeverityIcon severity={this.props.issue.severity}/>
- );
- }
-}
-
-const mapStateToProps = (state, ownProps: { issues: Array<string> }) => {
- const issues = ownProps.issues.map(issueKey => getIssueByKey(state, issueKey));
- return { issue: sortBySeverity(issues)[0] };
-};
-
-export default connect(mapStateToProps)(SourceViewerIssuesIndicator);
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerLine.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerLine.js
deleted file mode 100644
index 72cb0d5c053..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerLine.js
+++ /dev/null
@@ -1,377 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-import times from 'lodash/times';
-import ConnectedIssue from '../issue/ConnectedIssue';
-import SourceViewerIssuesIndicator from './SourceViewerIssuesIndicator';
-import { translate } from '../../helpers/l10n';
-import { splitByTokens, highlightSymbol, highlightIssueLocations, generateHTML } from './helpers/highlight';
-import type { SourceLine } from './types';
-
-type Props = {
- displayAllIssues: boolean,
- displayCoverage: boolean,
- displayDuplications: boolean,
- displayFiltered: boolean,
- displayIssues: boolean,
- displaySCM: boolean,
- duplications: Array<number>,
- duplicationsCount: number,
- filtered: boolean | null,
- highlighted: boolean,
- highlightedSymbol: string | null,
- issueLocations: Array<{ from: number, to: number }>,
- issues: Array<string>,
- line: SourceLine,
- loadDuplications: (SourceLine, HTMLElement) => void,
- onClick: (number, HTMLElement) => void,
- onCoverageClick: (SourceLine, HTMLElement) => void,
- onDuplicationClick: (number, number) => void,
- onIssueSelect: (string) => void,
- onIssueUnselect: () => void,
- onSCMClick: (SourceLine, HTMLElement) => void,
- onSymbolClick: (string) => void,
- selectedIssue: string | null,
- // $FlowFixMe
- secondaryIssueLocations: Array<{ from: number, to: number }>,
- // $FlowFixMe
- secondaryIssueLocationMessages: Array<{ msg: string, index?: number }>
-};
-
-type State = {
- issuesOpen: boolean
-};
-
-export default class SourceViewerLine extends React.PureComponent {
- codeNode: HTMLElement;
- props: Props;
- issueElements: { [string]: HTMLElement } = {};
- issueViews: { [string]: { destroy: () => void } } = {};
- state: State = { issuesOpen: false };
- symbols: NodeList<HTMLElement>;
-
- componentDidMount () {
- this.attachEvents();
- }
-
- componentWillUpdate () {
- this.detachEvents();
- }
-
- componentDidUpdate (prevProps: Props) {
- /* eslint-disable no-console */
- console.log('re-render line', this.props.line.line, 'because they are not equal:');
- Object.keys(this.props).forEach(prop => {
- if (this.props[prop] !== prevProps[prop]) {
- console.log(prop);
- }
- });
- console.log('');
-
- this.attachEvents();
- }
-
- componentWillUnmount () {
- this.detachEvents();
- }
-
- attachEvents () {
- this.symbols = this.codeNode.querySelectorAll('.sym');
- for (const symbol of this.symbols) {
- symbol.addEventListener('click', this.handleSymbolClick);
- }
- }
-
- detachEvents () {
- if (this.symbols) {
- for (const symbol of this.symbols) {
- symbol.removeEventListener('click', this.handleSymbolClick);
- }
- }
- }
-
- handleClick = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.props.onClick(this.props.line.line, e.target);
- };
-
- handleCoverageClick = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.props.onCoverageClick(this.props.line, e.target);
- };
-
- handleIssuesIndicatorClick = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.setState(prevState => {
- // TODO not sure if side effects allowed here
- if (!prevState.issuesOpen) {
- const { issues } = this.props;
- if (issues.length > 0) {
- this.props.onIssueSelect(issues[0]);
- }
- } else {
- this.props.onIssueUnselect();
- }
-
- return { issuesOpen: !prevState.issuesOpen };
- });
- }
-
- handleSCMClick = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.props.onSCMClick(this.props.line, e.target);
- }
-
- handleSymbolClick = (e: Object) => {
- e.preventDefault();
- const key = e.currentTarget.className.match(/sym-\d+/);
- if (key && key[0]) {
- this.props.onSymbolClick(key[0]);
- }
- };
-
- handleIssueSelect = (issueKey: string) => {
- this.props.onIssueSelect(issueKey);
- };
-
- renderLineNumber () {
- const { line } = this.props;
- return (
- <td className="source-meta source-line-number"
- // don't display 0
- data-line-number={line.line ? line.line : undefined}
- role={line.line ? 'button' : undefined}
- tabIndex={line.line ? 0 : undefined}
- onClick={line.line ? this.handleClick : undefined}/>
- );
- }
-
- renderSCM () {
- const { line } = this.props;
- const clickable = !!line.line;
- return (
- <td className="source-meta source-line-scm"
- data-line-number={line.line}
- role={clickable ? 'button' : undefined}
- tabIndex={clickable ? 0 : undefined}
- onClick={clickable ? this.handleSCMClick : undefined}>
- {this.props.displaySCM && (
- <div className="source-line-scm-inner" data-author={line.scmAuthor}/>
- )}
- </td>
- );
- }
-
- renderCoverage () {
- const { line } = this.props;
- const className = 'source-meta source-line-coverage' +
- (line.coverageStatus != null ? ` source-line-${line.coverageStatus}` : '');
- return (
- <td className={className}
- data-line-number={line.line}
- title={line.coverageStatus != null && translate('source_viewer.tooltip', line.coverageStatus)}
- data-placement={line.coverageStatus != null && 'right'}
- data-toggle={line.coverageStatus != null && 'tooltip'}
- role={line.coverageStatus != null ? 'button' : undefined}
- tabIndex={line.coverageStatus != null ? 0 : undefined}
- onClick={line.coverageStatus != null && this.handleCoverageClick}>
- <div className="source-line-bar"/>
- </td>
- );
- }
-
- renderDuplications () {
- const { line } = this.props;
- const className = classNames('source-meta', 'source-line-duplications', {
- 'source-line-duplicated': line.duplicated
- });
-
- const handleDuplicationClick = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.props.loadDuplications(this.props.line, e.target);
- };
-
- return (
- <td className={className}
- title={line.duplicated && translate('source_viewer.tooltip.duplicated_line')}
- data-placement={line.duplicated && 'right'}
- data-toggle={line.duplicated && 'tooltip'}
- role="button"
- tabIndex="0"
- onClick={handleDuplicationClick}>
- <div className="source-line-bar"/>
- </td>
- );
- }
-
- renderDuplicationsExtra () {
- const { duplications, duplicationsCount } = this.props;
- return times(duplicationsCount).map(index => this.renderDuplication(index, duplications.includes(index)));
- }
-
- renderDuplication = (index: number, duplicated: boolean) => {
- const className = classNames('source-meta', 'source-line-duplications-extra', {
- 'source-line-duplicated': duplicated
- });
-
- const handleDuplicationClick = (e: SyntheticInputEvent) => {
- e.preventDefault();
- this.props.onDuplicationClick(index, this.props.line.line);
- };
-
- return (
- <td key={index}
- className={className}
- data-line-number={this.props.line.line}
- data-index={index}
- title={duplicated ? translate('source_viewer.tooltip.duplicated_block') : undefined}
- data-placement={duplicated ? 'right' : undefined}
- data-toggle={duplicated ? 'tooltip' : undefined}
- role={duplicated ? 'button' : undefined}
- tabIndex={duplicated ? '0' : undefined}
- onClick={duplicated ? handleDuplicationClick : undefined}>
- <div className="source-line-bar"/>
- </td>
- );
- };
-
- renderIssuesIndicator () {
- const { issues } = this.props;
- const hasIssues = issues.length > 0;
- const className = classNames('source-meta', 'source-line-issues', { 'source-line-with-issues': hasIssues });
- const onClick = hasIssues ? this.handleIssuesIndicatorClick : undefined;
-
- return (
- <td className={className}
- data-line-number={this.props.line.line}
- role="button"
- tabIndex="0"
- onClick={onClick}>
- {hasIssues && (
- <SourceViewerIssuesIndicator issues={issues}/>
- )}
- {issues.length > 1 && (
- <span className="source-line-issues-counter">{issues.length}</span>
- )}
- </td>
- );
- }
-
- renderSecondaryIssueLocationMessages (locationMessages: Array<{ msg: string, index?: number }>) {
- const limitString = (str: string) => (
- str.length > 30 ? str.substr(0, 30) + '...' : str
- );
-
- return (
- <div className="source-line-issue-locations">
- {locationMessages.map((locationMessage, index) => (
- <div key={index} className="source-viewer-issue-location" title={locationMessage.msg}>
- {locationMessage.index && (
- <strong>{locationMessage.index}: </strong>
- )}
- {limitString(locationMessage.msg)}
- </div>
- ))}
- </div>
- );
- }
-
- renderCode () {
- const { line, highlightedSymbol, issueLocations, issues, secondaryIssueLocations } = this.props;
- const { secondaryIssueLocationMessages } = this.props;
- const className = classNames('source-line-code', 'code', { 'has-issues': issues.length > 0 });
-
- const code = line.code || '';
- let tokens = splitByTokens(code);
-
- if (highlightedSymbol) {
- tokens = highlightSymbol(tokens, highlightedSymbol);
- }
-
- if (issueLocations.length > 0) {
- tokens = highlightIssueLocations(tokens, issueLocations);
- }
-
- if (secondaryIssueLocations) {
- tokens = highlightIssueLocations(tokens, secondaryIssueLocations, 'source-line-code-secondary-issue');
- }
-
- const finalCode = generateHTML(tokens);
-
- const showIssues = (this.state.issuesOpen || this.props.displayAllIssues) && issues.length > 0;
-
- return (
- <td className={className} data-line-number={line.line}>
- <div className="source-line-code-inner">
- <pre ref={node => this.codeNode = node} dangerouslySetInnerHTML={{ __html: finalCode }}/>
- {secondaryIssueLocationMessages != null && secondaryIssueLocationMessages.length > 0 && (
- this.renderSecondaryIssueLocationMessages(secondaryIssueLocationMessages)
- )}
- </div>
- {showIssues && (
- <div className="issue-list">
- {issues.map(issue => (
- <ConnectedIssue
- key={issue}
- issueKey={issue}
- onClick={this.handleIssueSelect}
- selected={this.props.selectedIssue === issue}/>
- ))}
- </div>
- )}
- </td>
- );
- }
-
- render () {
- const { line, duplicationsCount, filtered } = this.props;
- const className = classNames('source-line', {
- 'source-line-highlighted': this.props.highlighted,
- 'source-line-shadowed': filtered === false,
- 'source-line-filtered': filtered === true
- });
-
- return (
- <tr className={className} data-line-number={line.line}>
- {this.renderLineNumber()}
-
- {this.renderSCM()}
-
- {this.props.displayCoverage && this.renderCoverage()}
-
- {this.props.displayDuplications && this.renderDuplications()}
-
- {duplicationsCount > 0 && this.renderDuplicationsExtra()}
-
- {this.props.displayIssues && !this.props.displayAllIssues && this.renderIssuesIndicator()}
-
- {this.props.displayFiltered && (
- <td className="source-meta source-line-filtered-container" data-line-number={line.line}>
- <div className="source-line-bar"/>
- </td>
- )}
-
- {this.renderCode()}
- </tr>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewer.js b/server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewer.js
deleted file mode 100644
index d673bd44dd0..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewer.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import { connect } from 'react-redux';
-import StandaloneSourceViewerBase from './StandaloneSourceViewerBase';
-import { receiveFavorites } from '../../store/favorites/duck';
-import { receiveIssues } from '../../store/issues/duck';
-
-const mapStateToProps = null;
-
-const onReceiveComponent = (component: { key: string, canMarkAsFavorite: boolean, fav: boolean }) => dispatch => {
- if (component.canMarkAsFavorite) {
- const favorites = [];
- const notFavorites = [];
- if (component.fav) {
- favorites.push({ key: component.key });
- } else {
- notFavorites.push({ key: component.key });
- }
- dispatch(receiveFavorites(favorites, notFavorites));
- }
-};
-
-const onReceiveIssues = (issues: Array<*>) => dispatch => {
- dispatch(receiveIssues(issues));
-};
-
-const mapDispatchToProps = { onReceiveComponent, onReceiveIssues };
-
-export default connect(mapStateToProps, mapDispatchToProps)(StandaloneSourceViewerBase);
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewerBase.js b/server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewerBase.js
deleted file mode 100644
index ea28e00b36f..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/StandaloneSourceViewerBase.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import SourceViewerBase from './SourceViewerBase';
-
-type State = {
- selectedIssue: string | null
-};
-
-export default class StandaloneSourceViewerBase extends React.Component {
- state: State = {
- selectedIssue: null
- };
-
- handleIssueSelect = (issue: string) => {
- this.setState({ selectedIssue: issue });
- };
-
- handleIssueUnselect = () => {
- this.setState({ selectedIssue: null });
- };
-
- render () {
- return (
- <SourceViewerBase
- {...this.props}
- onIssueSelect={this.handleIssueSelect}
- onIssueUnselect={this.handleIssueUnselect}
- selectedIssue={this.state.selectedIssue}/>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/getCoverageStatus.js b/server/sonar-web/src/main/js/components/SourceViewer/helpers/getCoverageStatus.js
deleted file mode 100644
index 2f99ed81675..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/getCoverageStatus.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import type { SourceLine } from '../types';
-
-const getCoverageStatus = (s: SourceLine): string | null => {
- let status = null;
- if (s.lineHits != null && s.lineHits > 0) {
- status = 'partially-covered';
- }
- if (s.lineHits != null && s.lineHits > 0 && s.conditions === s.coveredConditions) {
- status = 'covered';
- }
- if (s.lineHits === 0 || s.coveredConditions === 0) {
- status = 'uncovered';
- }
- return status;
-};
-
-export default getCoverageStatus;
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.js b/server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.js
deleted file mode 100644
index 0adc3f0d31f..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/highlight.js
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import escapeHtml from 'escape-html';
-
-type Token = { className: string, text: string };
-type Tokens = Array<Token>;
-
-const ISSUE_LOCATION_CLASS = 'source-line-code-issue';
-
-export const splitByTokens = (code: string, rootClassName: string = ''): Tokens => {
- const container = document.createElement('div');
- let tokens = [];
- container.innerHTML = code;
- [].forEach.call(container.childNodes, node => {
- if (node.nodeType === 1) {
- // ELEMENT NODE
- const fullClassName = rootClassName ? (rootClassName + ' ' + node.className) : node.className;
- const innerTokens = splitByTokens(node.innerHTML, fullClassName);
- tokens = tokens.concat(innerTokens);
- }
- if (node.nodeType === 3) {
- // TEXT NODE
- tokens.push({ className: rootClassName, text: node.nodeValue });
- }
- });
- return tokens;
-};
-
-export const highlightSymbol = (tokens: Tokens, symbol: string): Tokens => (
- tokens.map(token => token.className.includes(symbol) ?
- { ...token, className: `${token.className} highlighted` } :
- token
-));
-
-/**
- * Intersect two ranges
- * @param s1 Start position of the first range
- * @param e1 End position of the first range
- * @param s2 Start position of the second range
- * @param e2 End position of the second range
- */
-const intersect = (s1: number, e1: number, s2: number, e2: number): { from: number, to: number } => {
- return { from: Math.max(s1, s2), to: Math.min(e1, e2) };
-};
-
-/**
- * Get the substring of a string
- * @param str A string
- * @param from "From" offset
- * @param to "To" offset
- * @param acc Global offset to eliminate
- */
-const part = (str: string, from: number, to: number, acc: number): string => {
- // we do not want negative number as the first argument of `substr`
- return from >= acc ? str.substr(from - acc, to - from) : str.substr(0, to - from);
-};
-
-/**
- * Highlight issue locations in the list of tokens
- */
-export const highlightIssueLocations = (
- tokens: Tokens,
- issueLocations: Array<{ from: number, to: number }>,
- rootClassName: string = ISSUE_LOCATION_CLASS
-): Tokens => {
- issueLocations.forEach(location => {
- const nextTokens = [];
- let acc = 0;
- tokens.forEach(token => {
- const x = intersect(acc, acc + token.text.length, location.from, location.to);
- const p1 = part(token.text, acc, x.from, acc);
- const p2 = part(token.text, x.from, x.to, acc);
- const p3 = part(token.text, x.to, acc + token.text.length, acc);
- if (p1.length) {
- nextTokens.push({ className: token.className, text: p1 });
- }
- if (p2.length) {
- const newClassName = token.className.indexOf(rootClassName) === -1 ?
- `${token.className} ${rootClassName}` :
- token.className;
- nextTokens.push({ className: newClassName, text: p2 });
- }
- if (p3.length) {
- nextTokens.push({ className: token.className, text: p3 });
- }
- acc += token.text.length;
- });
- tokens = nextTokens.slice();
- });
- return tokens;
-};
-
-export const generateHTML = (tokens: Tokens): string => {
- return tokens.map(token => (
- `<span class="${token.className}">${escapeHtml(token.text)}</span>`
- )).join('');
-};
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/indexing.js b/server/sonar-web/src/main/js/components/SourceViewer/helpers/indexing.js
deleted file mode 100644
index a9016ef0c7c..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/indexing.js
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import { splitByTokens } from './highlight';
-import { getLinearLocations, getIssueLocations } from './issueLocations';
-import type { Issue } from '../../issue/types';
-import type { SourceLine } from '../types';
-
-export const issuesByLine = (issues: Array<Issue>) => {
- const index = {};
- issues.forEach(issue => {
- const line = issue.line || 0;
- if (!(line in index)) {
- index[line] = [];
- }
- index[line].push(issue.key);
- });
- return index;
-};
-
-export const locationsByLine = (issues: Array<Issue>) => {
- const index = {};
- issues.forEach(issue => {
- getLinearLocations(issue.textRange).forEach(location => {
- if (!(location.line in index)) {
- index[location.line] = [];
- }
- index[location.line].push(location);
- });
- });
- return index;
-};
-
-export const locationsByIssueAndLine = (issues: Array<Issue>) => {
- const index = {};
- issues.forEach(issue => {
- const byLine = {};
- getIssueLocations(issue).forEach(location => {
- getLinearLocations(location.textRange).forEach(linearLocation => {
- if (!(linearLocation.line in byLine)) {
- byLine[linearLocation.line] = [];
- }
- byLine[linearLocation.line].push({ from: linearLocation.from, to: linearLocation.to });
- });
- });
- index[issue.key] = byLine;
- });
- return index;
-};
-
-export const locationMessagesByIssueAndLine = (issues: Array<Issue>) => {
- const index = {};
- issues.forEach(issue => {
- const byLine = {};
- getIssueLocations(issue).forEach(location => {
- const line = location.textRange ? location.textRange.startLine : 0;
- if (!(line in byLine)) {
- byLine[line] = [];
- }
- byLine[line].push({ msg: location.msg, index: location.index });
- });
- index[issue.key] = byLine;
- });
- return index;
-};
-
-export const duplicationsByLine = (duplications: Array<*> | null) => {
- if (duplications == null) {
- return {};
- }
-
- const duplicationsByLine = {};
-
- duplications.forEach(({ blocks }, duplicationIndex) => {
- blocks.forEach(block => {
- if (block._ref === '1') {
- for (let line = block.from; line < block.from + block.size; line++) {
- if (!(line in duplicationsByLine)) {
- duplicationsByLine[line] = [];
- }
- duplicationsByLine[line].push(duplicationIndex);
- }
- }
- });
- });
-
- return duplicationsByLine;
-};
-
-export const symbolsByLine = (sources: Array<SourceLine>) => {
- const index = {};
- sources.forEach(line => {
- const tokens = splitByTokens(line.code);
- index[line.line] = tokens
- .map(token => {
- const key = token.className.match(/sym-\d+/);
- return key && key[0];
- })
- .filter(key => key);
- });
- return index;
-};
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/issueLocations.js b/server/sonar-web/src/main/js/components/SourceViewer/helpers/issueLocations.js
deleted file mode 100644
index d2c8991fc3c..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/issueLocations.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import type { TextRange, Issue } from '../../issue/types';
-
-export const getLinearLocations = (textRange?: TextRange): Array<{ line: number, from: number, to: number }> => {
- if (!textRange) {
- return [];
- }
- const locations = [];
-
- // go through all lines of the `textRange`
- for (let line = textRange.startLine; line <= textRange.endLine; line++) {
- // TODO fix 999999
- const from = line === textRange.startLine ? textRange.startOffset : 0;
- const to = line === textRange.endLine ? textRange.endOffset : 999999;
- locations.push({ line, from, to });
- }
- return locations;
-};
-
-export const getIssueLocations = (issue: Issue): Array<{ msg: string, textRange: TextRange, index?: number }> => {
- const primaryLocation = {
- msg: issue.message,
- textRange: issue.textRange
- };
- const allLocations = [primaryLocation];
- issue.flows.forEach(({ locations }) => {
- if (locations) {
- const locationsCount = locations.length;
- locations.forEach((location, index) => {
- const flowLocation = {
- ...location,
- // set index only for real flows, do not set for just secondary locations
- index: locationsCount > 1 ? locationsCount - index : undefined
- };
- allLocations.push(flowLocation);
- });
- }
- });
- return allLocations;
-};
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.js b/server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.js
deleted file mode 100644
index ddc2963c0e7..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.js
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import { searchIssues } from '../../../api/issues';
-import { parseIssueFromResponse } from '../../../helpers/issues';
-
-export type Query = { [string]: string };
-
-export type Issues = Array<*>;
-
-// maximum possible value
-const PAGE_SIZE = 500;
-
-const buildQuery = (component: string): Query => ({
- additionalFields: '_all',
- resolved: 'false',
- componentKeys: component,
- s: 'FILE_LINE'
-});
-
-export const loadPage = (query: Query, page: number, pageSize: number = PAGE_SIZE): Promise<Issues> => {
- return searchIssues({ ...query, p: page, ps: pageSize }).then(r => (
- r.issues.map(issue => parseIssueFromResponse(issue, r.components, r.users, r.rules))
- ));
-};
-
-export const loadPageAndNext = (
- query: Query,
- toLine: number,
- page: number,
- pageSize: number = PAGE_SIZE
-): Promise<Issues> => {
- return loadPage(query, page).then(issues => {
- if (issues.length === 0) {
- return [];
- }
-
- const lastIssue = issues[issues.length - 1];
-
- if ((lastIssue.line != null && lastIssue.line > toLine) || issues.length < pageSize) {
- return issues;
- }
-
- return loadPageAndNext(query, toLine, page + 1, pageSize).then(nextIssues => {
- return [...issues, ...nextIssues];
- });
- });
-};
-
-const loadIssues = (component: string, fromLine: number, toLine: number): Promise<Issues> => {
- const query = buildQuery(component);
- return new Promise(resolve => {
- loadPageAndNext(query, toLine, 1).then(issues => {
- resolve(issues);
- });
- });
-};
-
-export default loadIssues;
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/types.js b/server/sonar-web/src/main/js/components/SourceViewer/types.js
deleted file mode 100644
index 3dd00eeb553..00000000000
--- a/server/sonar-web/src/main/js/components/SourceViewer/types.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-export type SourceLine = {
- code: string,
- conditions?: number,
- coverageStatus?: string | null,
- coveredConditions?: number,
- duplicated: boolean,
- line: number,
- lineHits?: number,
- scmAuthor?: string,
- scmDate?: string,
- scmRevision?: string
-};
-
-export type Duplication = {
- blocks: Array<{
- _ref: string,
- from: number,
- size: number
- }>
-};
diff --git a/server/sonar-web/src/main/js/components/common/popup.js b/server/sonar-web/src/main/js/components/common/popup.js
index af7d22f632c..363f0bdf72b 100644
--- a/server/sonar-web/src/main/js/components/common/popup.js
+++ b/server/sonar-web/src/main/js/components/common/popup.js
@@ -25,23 +25,22 @@ export default Marionette.ItemView.extend({
onRender () {
this.$el.detach().appendTo($('body'));
- const triggerEl = $(this.options.triggerEl);
if (this.options.bottom) {
this.$el.addClass('bubble-popup-bottom');
this.$el.css({
- top: triggerEl.offset().top + triggerEl.outerHeight(),
- left: triggerEl.offset().left
+ top: this.options.triggerEl.offset().top + this.options.triggerEl.outerHeight(),
+ left: this.options.triggerEl.offset().left
});
} else if (this.options.bottomRight) {
this.$el.addClass('bubble-popup-bottom-right');
this.$el.css({
- top: triggerEl.offset().top + triggerEl.outerHeight(),
- right: $(window).width() - triggerEl.offset().left - triggerEl.outerWidth()
+ top: this.options.triggerEl.offset().top + this.options.triggerEl.outerHeight(),
+ right: $(window).width() - this.options.triggerEl.offset().left - this.options.triggerEl.outerWidth()
});
} else {
this.$el.css({
- top: triggerEl.offset().top,
- left: triggerEl.offset().left + triggerEl.outerWidth()
+ top: this.options.triggerEl.offset().top,
+ left: this.options.triggerEl.offset().left + this.options.triggerEl.outerWidth()
});
}
this.attachCloseEvents();
@@ -49,7 +48,6 @@ export default Marionette.ItemView.extend({
attachCloseEvents () {
const that = this;
- const triggerEl = $(this.options.triggerEl);
key('escape', () => {
that.destroy();
});
@@ -57,8 +55,8 @@ export default Marionette.ItemView.extend({
$('body').off('click.bubble-popup');
that.destroy();
});
- triggerEl.on('click.bubble-popup', e => {
- triggerEl.off('click.bubble-popup');
+ this.options.triggerEl.on('click.bubble-popup', e => {
+ that.options.triggerEl.off('click.bubble-popup');
e.stopPropagation();
that.destroy();
});
@@ -66,7 +64,7 @@ export default Marionette.ItemView.extend({
onDestroy () {
$('body').off('click.bubble-popup');
- const triggerEl = $(this.options.triggerEl);
- triggerEl.off('click.bubble-popup');
+ this.options.triggerEl.off('click.bubble-popup');
}
});
+
diff --git a/server/sonar-web/src/main/js/components/issue/ConnectedIssue.js b/server/sonar-web/src/main/js/components/issue/ConnectedIssue.js
deleted file mode 100644
index 28be4c7ba4b..00000000000
--- a/server/sonar-web/src/main/js/components/issue/ConnectedIssue.js
+++ /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
index c437b8f41af..00000000000
--- a/server/sonar-web/src/main/js/components/issue/Issue.js
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { connect } from 'react-redux';
-import IssueView from './issue-view';
-import IssueModel from './models/issue';
-import { receiveIssues } from '../../store/issues/duck';
-import type { Issue as IssueType } from './types';
-
-type Model = { toJSON: () => {} };
-
-type Props = {
- checked?: boolean,
- issue: IssueType | Model,
- onCheck?: () => void,
- onClick: () => void,
- onFilterClick?: () => void,
- onIssueChange: ({}) => void,
- selected: boolean
-};
-
-class Issue extends React.PureComponent {
- issueView: Object;
- node: HTMLElement;
- props: Props;
-
- componentDidMount () {
- this.renderIssueView();
- if (this.props.selected) {
- this.bindShortcuts();
- }
- }
-
- componentWillUpdate (nextProps: Props) {
- if (!nextProps.selected && this.props.selected) {
- this.unbindShortcuts();
- }
- this.destroyIssueView();
- }
-
- componentDidUpdate (prevProps: Props) {
- this.renderIssueView();
- if (!prevProps.selected && this.props.selected) {
- this.bindShortcuts();
- }
- }
-
- componentWillUnmount () {
- if (this.props.selected) {
- this.unbindShortcuts();
- }
- this.destroyIssueView();
- }
-
- bindShortcuts () {
- document.addEventListener('keypress', this.handleKeyPress);
- }
-
- unbindShortcuts () {
- document.removeEventListener('keypress', this.handleKeyPress);
- }
-
- doIssueAction (action: string) {
- this.issueView.$('.js-issue-' + action).click();
- }
-
- handleKeyPress = (e: Object) => {
- const tagName = e.target.tagName.toUpperCase();
- const shouldHandle = tagName !== 'INPUT' && tagName !== 'TEXTAREA' && tagName !== 'BUTTON';
-
- if (shouldHandle) {
- switch (e.key) {
- case 'f': return this.doIssueAction('transition');
- case 'a': return this.doIssueAction('assign');
- case 'm': return this.doIssueAction('assign-to-me');
- case 'p': return this.doIssueAction('plan');
- case 'i': return this.doIssueAction('set-severity');
- case 'c': return this.doIssueAction('comment');
- case 't': return this.doIssueAction('edit-tags');
- }
- }
- };
-
- renderIssueView () {
- const model = this.props.issue.toJSON ? this.props.issue : new IssueModel(this.props.issue);
- this.issueView = new IssueView({
- model,
- checked: this.props.checked,
- onCheck: this.props.onCheck,
- onClick: this.props.onClick,
- onFilterClick: this.props.onFilterClick,
- onIssueChange: this.props.onIssueChange
- });
- this.issueView.render().$el.appendTo(this.node);
- if (this.props.selected) {
- this.issueView.select();
- }
- }
-
- destroyIssueView () {
- this.issueView.destroy();
- }
-
- render () {
- return <div className="issue-container" ref={node => this.node = node}/>;
- }
-}
-
-const onIssueChange = issue => dispatch => {
- dispatch(receiveIssues([issue]));
-};
-
-const mapDispatchToProps = { onIssueChange };
-
-export default connect(null, mapDispatchToProps)(Issue);
diff --git a/server/sonar-web/src/main/js/components/issue/issue-view.js b/server/sonar-web/src/main/js/components/issue/issue-view.js
index a4a691fa955..71ced0ff47a 100644
--- a/server/sonar-web/src/main/js/components/issue/issue-view.js
+++ b/server/sonar-web/src/main/js/components/issue/issue-view.js
@@ -34,21 +34,16 @@ import Template from './templates/issue.hbs';
import getCurrentUserFromStore from '../../app/utils/getCurrentUserFromStore';
export default Marionette.ItemView.extend({
+ className: 'issue',
template: Template,
modelEvents: {
- 'change': 'notifyAndRender',
+ 'change': 'render',
'transition': 'onTransition'
},
- className () {
- const hasCheckbox = this.options.onCheck != null;
- return hasCheckbox ? 'issue issue-with-checkbox' : 'issue';
- },
-
events () {
return {
- 'click': 'handleClick',
'click .js-issue-comment': 'onComment',
'click .js-issue-comment-edit': 'editComment',
'click .js-issue-comment-delete': 'deleteComment',
@@ -61,24 +56,10 @@ export default Marionette.ItemView.extend({
'click .js-issue-show-changelog': 'showChangeLog',
'click .js-issue-rule': 'showRule',
'click .js-issue-edit-tags': 'editTags',
- 'click .js-issue-locations': 'showLocations',
- 'click .js-issue-filter': 'filterSimilarIssues',
- 'click .js-toggle': 'onIssueCheck'
+ 'click .js-issue-locations': 'showLocations'
};
},
- notifyAndRender () {
- const { onIssueChange } = this.options;
- if (onIssueChange) {
- onIssueChange(this.model.toJSON());
- }
-
- // if ConnectedIssue is used, this view can be destroyed just after onIssueChange()
- if (!this.isDestroyed) {
- this.render();
- }
- },
-
onRender () {
this.$el.attr('data-key', this.model.get('key'));
},
@@ -262,45 +243,19 @@ export default Marionette.ItemView.extend({
this.model.trigger('locations', this.model);
},
- select () {
- this.$el.addClass('selected');
- },
-
- unselect () {
- this.$el.removeClass('selected');
- },
-
onTransition (transition) {
if (transition === 'falsepositive' || transition === 'wontfix') {
this.comment({ fromTransition: true });
}
},
- handleClick (e) {
- e.preventDefault();
- const { onClick } = this.options;
- if (onClick) {
- onClick(this.model.get('key'));
- }
- },
-
- filterSimilarIssues (e) {
- this.options.onFilterClick(e);
- },
-
- onIssueCheck (e) {
- this.options.onCheck(e);
- },
-
serializeData () {
const issueKey = encodeURIComponent(this.model.get('key'));
return {
...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
permalink: window.baseUrl + '/issues/search#issues=' + issueKey,
- hasSecondaryLocations: this.model.get('flows').length,
- hasSimilarIssuesFilter: this.options.onFilterClick != null,
- hasCheckbox: this.options.onCheck != null,
- checked: this.options.checked
+ hasSecondaryLocations: this.model.get('flows').length
};
}
});
+
diff --git a/server/sonar-web/src/main/js/components/issue/templates/issue.hbs b/server/sonar-web/src/main/js/components/issue/templates/issue.hbs
index f951a40c0c4..a828ecf5e3e 100644
--- a/server/sonar-web/src/main/js/components/issue/templates/issue.hbs
+++ b/server/sonar-web/src/main/js/components/issue/templates/issue.hbs
@@ -35,15 +35,6 @@
<li class="issue-meta">
<a class="js-issue-permalink icon-link" href="{{permalink}}" target="_blank"></a>
</li>
-
- {{#if hasSimilarIssuesFilter}}
- <li class="issue-meta">
- <button class="button-link issue-action issue-action-with-options js-issue-filter"
- aria-label="{{t "issue.filter_similar_issues"}}">
- <i class="icon-filter icon-half-transparent"></i>&nbsp;<i class="icon-dropdown"></i>
- </button>
- </li>
- {{/if}}
</ul>
</td>
</tr>
@@ -174,9 +165,3 @@
<i class="issue-navigate-to-left icon-chevron-left"></i>
<i class="issue-navigate-to-right icon-chevron-right"></i>
</a>
-
-{{#if hasCheckbox}}
- <div class="js-toggle issue-checkbox-container">
- <i class="issue-checkbox icon-checkbox {{#if checked}}icon-checkbox-checked{{/if}}"></i>
- </div>
-{{/if}}
diff --git a/server/sonar-web/src/main/js/components/issue/types.js b/server/sonar-web/src/main/js/components/issue/types.js
deleted file mode 100644
index dd0bbc1d2e6..00000000000
--- a/server/sonar-web/src/main/js/components/issue/types.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-export type TextRange = {
- startLine: number,
- startOffset: number,
- endLine: number,
- endOffset: number
-};
-
-export type Issue = {
- key: string,
- flows: Array<{
- locations?: Array<{
- msg: string,
- textRange?: TextRange
- }>
- }>,
- line?: number,
- message: string,
- severity: string,
- textRange: TextRange
-};
diff --git a/server/sonar-web/src/main/js/components/shared/WithStore.js b/server/sonar-web/src/main/js/components/shared/WithStore.js
deleted file mode 100644
index f3cb83233e2..00000000000
--- a/server/sonar-web/src/main/js/components/shared/WithStore.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import getStore from '../../app/utils/getStore';
-
-export default class WithStore extends React.Component {
- store: {};
- props: { children: Object };
-
- static childContextTypes = {
- store: React.PropTypes.object
- };
-
- constructor (props: { children: Object }) {
- super(props);
- this.store = getStore();
- }
-
- getChildContext () {
- return { store: this.store };
- }
-
- render () {
- return this.props.children;
- }
-}
diff --git a/server/sonar-web/src/main/js/components/source-viewer/SourceViewer.js b/server/sonar-web/src/main/js/components/source-viewer/SourceViewer.js
new file mode 100644
index 00000000000..0589bcd0643
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/source-viewer/SourceViewer.js
@@ -0,0 +1,82 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import BaseSourceViewer from './main';
+import { getPeriodDate, getPeriodLabel } from '../../helpers/periods';
+
+export default class SourceViewer extends React.Component {
+ static propTypes = {
+ component: React.PropTypes.shape({
+ id: React.PropTypes.string.isRequired
+ }).isRequired,
+ period: React.PropTypes.object,
+ line: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string])
+ };
+
+ componentDidMount () {
+ this.renderSourceViewer();
+ }
+
+ shouldComponentUpdate (nextProps) {
+ return nextProps.component.id !== this.props.component.id;
+ }
+
+ componentWillUpdate () {
+ this.destroySourceViewer();
+ }
+
+ componentDidUpdate () {
+ this.renderSourceViewer();
+ }
+
+ componentWillUnmount () {
+ this.destroySourceViewer();
+ }
+
+ renderSourceViewer () {
+ this.sourceViewer = new BaseSourceViewer();
+ this.sourceViewer.render().$el.appendTo(this.refs.container);
+ this.sourceViewer.open(this.props.component.id);
+ this.sourceViewer.on('loaded', this.handleLoad.bind(this));
+ }
+
+ destroySourceViewer () {
+ this.sourceViewer.destroy();
+ }
+
+ handleLoad () {
+ const { period, line } = this.props;
+
+ if (period) {
+ const periodDate = getPeriodDate(period);
+ const periodLabel = getPeriodLabel(period);
+ this.sourceViewer.filterLinesByDate(periodDate, periodLabel);
+ }
+
+ if (line) {
+ this.sourceViewer.highlightLine(line);
+ this.sourceViewer.scrollToLine(line);
+ }
+ }
+
+ render () {
+ return <div ref="container"/>;
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/source-viewer/main.js b/server/sonar-web/src/main/js/components/source-viewer/main.js
index 8b1725d09f3..58bfb9ce4f8 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/main.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/main.js
@@ -21,6 +21,7 @@ import $ from 'jquery';
import moment from 'moment';
import sortBy from 'lodash/sortBy';
import toPairs from 'lodash/toPairs';
+import Backbone from 'backbone';
import Marionette from 'backbone.marionette';
import Source from './source';
import Issues from '../issue/collections/issues';
@@ -402,7 +403,7 @@ export default Marionette.LayoutView.extend({
const row = this.model.get('source').find(row => row.line === line);
const popup = new SCMPopupView({
triggerEl: $(e.currentTarget),
- line: row
+ model: new Backbone.Model(row)
});
popup.render();
},
@@ -421,8 +422,8 @@ export default Marionette.LayoutView.extend({
};
return $.get(url, options).done(data => {
const popup = new CoveragePopupView({
- line: row,
- tests: data.tests,
+ row,
+ collection: new Backbone.Collection(data.tests),
triggerEl: $(e.currentTarget)
});
popup.render();
@@ -467,11 +468,10 @@ export default Marionette.LayoutView.extend({
return isOk;
});
const popup = new DuplicationPopupView({
- blocks,
inRemovedComponent,
- component: this.model.toJSON(),
- files: this.model.get('duplicationFiles'),
- triggerEl: $(e.currentTarget)
+ triggerEl: $(e.currentTarget),
+ model: this.model,
+ collection: new Backbone.Collection(blocks)
});
popup.render();
},
@@ -498,7 +498,8 @@ export default Marionette.LayoutView.extend({
const popup = new LineActionsPopupView({
line,
triggerEl: $(e.currentTarget),
- component: this.model.toJSON()
+ model: this.model,
+ row: $(e.currentTarget).closest('.source-line')
});
popup.render();
},
diff --git a/server/sonar-web/src/main/js/components/source-viewer/measures-overlay.js b/server/sonar-web/src/main/js/components/source-viewer/measures-overlay.js
index 4baf170a2e8..a01d69b0f85 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/measures-overlay.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/measures-overlay.js
@@ -34,7 +34,7 @@ export default ModalView.extend({
initialize () {
this.testsScroll = 0;
const requests = [this.requestMeasures(), this.requestIssues()];
- if (this.model.get('q') === 'UTS') {
+ if (this.model.get('isUnitTest')) {
requests.push(this.requestTests());
}
Promise.all(requests).then(() => this.render());
@@ -282,3 +282,4 @@ export default ModalView.extend({
};
}
});
+
diff --git a/server/sonar-web/src/main/js/components/source-viewer/more-actions.js b/server/sonar-web/src/main/js/components/source-viewer/more-actions.js
index aba02a8e1de..9b7181a6463 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/more-actions.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/more-actions.js
@@ -50,8 +50,8 @@ export default Marionette.ItemView.extend({
},
openInWorkspace () {
- const key = this.options.parent.model.get('key');
- Workspace.openComponent({ key });
+ const uuid = this.options.parent.model.id;
+ Workspace.openComponent({ uuid });
},
showRawSource () {
@@ -66,3 +66,4 @@ export default Marionette.ItemView.extend({
};
}
});
+
diff --git a/server/sonar-web/src/main/js/components/source-viewer/popups/coverage-popup.js b/server/sonar-web/src/main/js/components/source-viewer/popups/coverage-popup.js
index 68fd0ccc388..1440241e42a 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/popups/coverage-popup.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/popups/coverage-popup.js
@@ -27,7 +27,7 @@ export default Popup.extend({
template: Template,
events: {
- 'click a[data-key]': 'goToFile'
+ 'click a[data-id]': 'goToFile'
},
onRender () {
@@ -37,19 +37,19 @@ export default Popup.extend({
goToFile (e) {
e.stopPropagation();
- const key = $(e.currentTarget).data('key');
- Workspace.openComponent({ key });
+ const id = $(e.currentTarget).data('id');
+ Workspace.openComponent({ uuid: id });
},
serializeData () {
- const row = this.options.line || {};
- const tests = groupBy(this.options.tests, 'fileKey');
- const testFiles = Object.keys(tests).map(fileKey => {
- const testSet = tests[fileKey];
+ const row = this.options.row || {};
+ const tests = groupBy(this.collection.toJSON(), 'fileId');
+ const testFiles = Object.keys(tests).map(fileId => {
+ const testSet = tests[fileId];
const test = testSet[0];
return {
file: {
- key: test.fileKey,
+ id: test.fileId,
longName: test.fileName
},
tests: testSet
@@ -58,3 +58,4 @@ export default Popup.extend({
return { testFiles, row };
}
});
+
diff --git a/server/sonar-web/src/main/js/components/source-viewer/popups/duplication-popup.js b/server/sonar-web/src/main/js/components/source-viewer/popups/duplication-popup.js
index da542333a30..24ad94fe254 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/popups/duplication-popup.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/popups/duplication-popup.js
@@ -28,35 +28,37 @@ export default Popup.extend({
template: Template,
events: {
- 'click a[data-key]': 'goToFile'
+ 'click a[data-uuid]': 'goToFile'
},
goToFile (e) {
e.stopPropagation();
- const key = $(e.currentTarget).data('key');
+ const uuid = $(e.currentTarget).data('uuid');
const line = $(e.currentTarget).data('line');
- Workspace.openComponent({ key, line });
+ Workspace.openComponent({ uuid, line });
},
serializeData () {
const that = this;
- const groupedBlocks = groupBy(this.options.blocks, '_ref');
+ const files = this.model.get('duplicationFiles');
+ const groupedBlocks = groupBy(this.collection.toJSON(), '_ref');
let duplications = Object.keys(groupedBlocks).map(fileRef => {
return {
blocks: groupedBlocks[fileRef],
- file: this.options.files[fileRef]
+ file: files[fileRef]
};
});
duplications = sortBy(duplications, d => {
- const a = d.file.projectName !== that.options.component.projectName;
- const b = d.file.subProjectName !== that.options.component.subProjectName;
- const c = d.file.key !== that.options.component.key;
+ const a = d.file.projectName !== that.model.get('projectName');
+ const b = d.file.subProjectName !== that.model.get('subProjectName');
+ const c = d.file.key !== that.model.get('key');
return '' + a + b + c;
});
return {
duplications,
- component: this.options.component,
+ component: this.model.toJSON(),
inRemovedComponent: this.options.inRemovedComponent
};
}
});
+
diff --git a/server/sonar-web/src/main/js/components/source-viewer/popups/line-actions-popup.js b/server/sonar-web/src/main/js/components/source-viewer/popups/line-actions-popup.js
index a2d94f568b8..aa89585bc44 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/popups/line-actions-popup.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/popups/line-actions-popup.js
@@ -29,9 +29,9 @@ export default Popup.extend({
getPermalink (e) {
e.preventDefault();
- const { component, line } = this.options;
- const url = `${window.baseUrl}/component/index?id=${encodeURIComponent(component.key)}&line=${line}`;
+ const url =
+ `${window.baseUrl}/component/index?id=${encodeURIComponent(this.model.key())}&line=${this.options.line}`;
const windowParams = 'resizable=1,scrollbars=1,status=1';
- window.open(url, component.name, windowParams);
+ window.open(url, this.model.get('name'), windowParams);
}
});
diff --git a/server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js b/server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js
index f140e37c56b..755a866baec 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/popups/scm-popup.js
@@ -34,12 +34,6 @@ export default Popup.extend({
onClick (e) {
e.stopPropagation();
- },
-
- serializeData () {
- return {
- ...Popup.prototype.serializeData.apply(this, arguments),
- line: this.options.line
- };
}
});
+
diff --git a/server/sonar-web/src/main/js/components/source-viewer/source.js b/server/sonar-web/src/main/js/components/source-viewer/source.js
index 3cb1198e328..43009061a22 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/source.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/source.js
@@ -96,3 +96,4 @@ export default Backbone.Model.extend({
return source.some(line => line.coverageStatus != null);
}
});
+
diff --git a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-coverage-popup.hbs b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-coverage-popup.hbs
index 57c6301119e..a0e7b62896e 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-coverage-popup.hbs
+++ b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-coverage-popup.hbs
@@ -15,7 +15,7 @@
{{#each testFiles}}
<div class="bubble-popup-section">
- <a class="component-viewer-popup-test-file link-action" data-key="{{file.key}}" title="{{file.longName}}">
+ <a class="component-viewer-popup-test-file link-action" data-id="{{file.id}}" title="{{file.longName}}">
<span>{{collapsePath file.longName}}</span>
</a>
<ul class="bubble-popup-list">
@@ -24,7 +24,7 @@
<i class="component-viewer-popup-test-status {{testStatusIconClass status}}"></i>
<span class="component-viewer-popup-test-name">
<a class="component-viewer-popup-test-file link-action" title="{{name}}"
- data-key="{{../file.key}}" data-method="{{name}}">
+ data-id="{{../file.id}}" data-method="{{name}}">
{{name}}
</a>
</span>
diff --git a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-duplication-popup.hbs b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-duplication-popup.hbs
index ea8fc2b2349..9b0783c6655 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-duplication-popup.hbs
+++ b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-duplication-popup.hbs
@@ -21,7 +21,7 @@
{{#notEq file.key ../component.key}}
<div class="component-name-path">
- <a class="link-action" data-key="{{file.key}}" title="{{file.name}}">
+ <a class="link-action" data-uuid="{{file.uuid}}" title="{{file.name}}">
<span>{{collapsedDirFromPath file.name}}</span><span
class="component-name-file">{{fileFromPath file.name}}</span>
</a>
@@ -31,7 +31,7 @@
<div class="component-name-path">
Lines:
{{#joinEach blocks ','}}
- <a class="link-action" data-key="{{../file.key}}" data-line="{{this.from}}">
+ <a class="link-action" data-uuid="{{../file.uuid}}" data-line="{{this.from}}">
{{this.from}} – {{sum from size -1}}
</a>
{{/joinEach}}
diff --git a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-header.hbs b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-header.hbs
index a4532354481..e276c7e938b 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-header.hbs
+++ b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-header.hbs
@@ -16,7 +16,7 @@
<div class="component-name-path">
{{qualifierIcon q}}&nbsp;<span>{{collapsedDirFromPath path}}</span><span class="component-name-file">{{fileFromPath path}}</span>
- {{#if canMarkAsFavorite}}
+ {{#if canMarkAsFavourite}}
<a class="js-favorite component-name-favorite {{#if fav}}icon-favorite{{else}}icon-not-favorite{{/if}}"
title="{{#if fav}}{{t 'click_to_remove_from_favorites'}}{{else}}{{t 'click_to_add_to_favorites'}}{{/if}}">
</a>
diff --git a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-measures.hbs b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-measures.hbs
index 0df076390c9..a3f4df55605 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-measures.hbs
+++ b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-measures.hbs
@@ -19,16 +19,7 @@
{{/unless}}
</div>
- {{#eq q 'UTS'}}
- <div class="source-viewer-measures">
- <div class="source-viewer-measures-section">
- {{> 'measures/_source-viewer-measures-tests'}}
- </div>
- </div>
- <div class="source-viewer-measures">
- {{> 'measures/_source-viewer-measures-test-cases'}}
- </div>
- {{else}}
+ {{#unless isUnitTest}}
<div class="source-viewer-measures">
<div class="source-viewer-measures-section">
<div class="source-viewer-measures-card">
@@ -52,7 +43,16 @@
{{> 'measures/_source-viewer-measures-duplications'}}
</div>
</div>
- {{/eq}}
+ {{else}}
+ <div class="source-viewer-measures">
+ <div class="source-viewer-measures-section">
+ {{> 'measures/_source-viewer-measures-tests'}}
+ </div>
+ </div>
+ <div class="source-viewer-measures">
+ {{> 'measures/_source-viewer-measures-test-cases'}}
+ </div>
+ {{/unless}}
<div class="spacer-bottom">&nbsp;</div>
diff --git a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-scm-popup.hbs b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-scm-popup.hbs
index dd82aca528c..768ea72341d 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-scm-popup.hbs
+++ b/server/sonar-web/src/main/js/components/source-viewer/templates/source-viewer-scm-popup.hbs
@@ -1,13 +1,13 @@
<div class="bubble-popup-container">
<div class="bubble-popup-section">
- {{line.scmAuthor}}
+ {{scmAuthor}}
</div>
<div class="bubble-popup-section">
- {{dt line.scmDate}}
+ {{dt scmDate}}
</div>
- {{#if line.scmRevision}}
+ {{#if scmRevision}}
<div class="bubble-popup-section">
- {{line.scmRevision}}
+ {{scmRevision}}
</div>
{{/if}}
</div>
diff --git a/server/sonar-web/src/main/js/components/workspace/main.js b/server/sonar-web/src/main/js/components/workspace/main.js
index 4e1170bac38..30082332e4b 100644
--- a/server/sonar-web/src/main/js/components/workspace/main.js
+++ b/server/sonar-web/src/main/js/components/workspace/main.js
@@ -99,8 +99,7 @@ Workspace.prototype = {
that.closeComponentViewer();
m.destroy();
});
- this.viewerView.$el.appendTo(document.body);
- this.viewerView.render();
+ this.viewerView.render().$el.appendTo(document.body);
},
showComponentViewer (model) {
diff --git a/server/sonar-web/src/main/js/components/workspace/models/item.js b/server/sonar-web/src/main/js/components/workspace/models/item.js
index 1dd6daf7fc5..0ecbef4ac33 100644
--- a/server/sonar-web/src/main/js/components/workspace/models/item.js
+++ b/server/sonar-web/src/main/js/components/workspace/models/item.js
@@ -25,8 +25,8 @@ export default Backbone.Model.extend({
if (!this.has('__type__')) {
return 'type is missing';
}
- if (this.get('__type__') === 'component' && !this.has('key')) {
- return 'key is missing';
+ if (this.get('__type__') === 'component' && !this.has('uuid')) {
+ return 'uuid is missing';
}
if (this.get('__type__') === 'rule' && !this.has('key')) {
return 'key is missing';
diff --git a/server/sonar-web/src/main/js/components/workspace/models/items.js b/server/sonar-web/src/main/js/components/workspace/models/items.js
index 97ff41e2267..5d015e037ea 100644
--- a/server/sonar-web/src/main/js/components/workspace/models/items.js
+++ b/server/sonar-web/src/main/js/components/workspace/models/items.js
@@ -47,13 +47,16 @@ export default Backbone.Collection.extend({
},
has (model) {
- const forComponent = model.isComponent() && this.findWhere({ key: model.get('key') }) != null;
+ const forComponent = model.isComponent() && this.findWhere({ uuid: model.get('uuid') }) != null;
const forRule = model.isRule() && this.findWhere({ key: model.get('key') }) != null;
return forComponent || forRule;
},
add2 (model) {
- const tryModel = this.findWhere({ key: model.get('key') });
+ const tryModel = model.isComponent() ?
+ this.findWhere({ uuid: model.get('uuid') }) :
+ this.findWhere({ key: model.get('key') });
return tryModel != null ? tryModel : this.add(model);
}
});
+
diff --git a/server/sonar-web/src/main/js/components/workspace/views/viewer-view.js b/server/sonar-web/src/main/js/components/workspace/views/viewer-view.js
index 7ab96e7c683..924ea80ad7f 100644
--- a/server/sonar-web/src/main/js/components/workspace/views/viewer-view.js
+++ b/server/sonar-web/src/main/js/components/workspace/views/viewer-view.js
@@ -17,13 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import $ from 'jquery';
-import React from 'react';
-import { render } from 'react-dom';
import BaseView from './base-viewer-view';
-import SourceViewer from '../../SourceViewer/StandaloneSourceViewer';
+import SourceViewer from '../../source-viewer/main';
import Template from '../templates/workspace-viewer.hbs';
-import WithStore from '../../shared/WithStore';
export default BaseView.extend({
template: Template,
@@ -33,39 +29,22 @@ export default BaseView.extend({
this.showViewer();
},
- scrollToLine (line) {
- const row = this.$el.find(`.source-line[data-line-number="${line}"]`);
- if (row.length > 0) {
- const sourceViewer = this.$el.find('.source-viewer');
- let p = sourceViewer.scrollParent();
- if (p.is(document) || p.is('body')) {
- p = $(window);
- }
- const pTopOffset = p.offset() != null ? p.offset().top : 0;
- const pHeight = p.height();
- const goal = row.offset().top - pHeight / 3 - pTopOffset;
- p.scrollTop(goal);
- }
- },
-
showViewer () {
- const { key, line } = this.model.toJSON();
-
- const el = document.querySelector(this.viewerRegion.el);
-
- render((
- <WithStore>
- <SourceViewer
- component={key}
- fromWorkspace={true}
- highlightedLine={line}
- onLoaded={component => {
- this.model.set({ name: component.name, q: component.q });
- if (line) {
- this.scrollToLine(line);
- }
- }}/>
- </WithStore>
- ), el);
+ const that = this;
+ const viewer = new SourceViewer();
+ const options = this.model.toJSON();
+ viewer.open(this.model.get('uuid'), { workspace: true });
+ viewer.on('loaded', () => {
+ that.model.set({
+ name: viewer.model.get('name'),
+ q: viewer.model.get('q')
+ });
+ if (options.line != null) {
+ viewer.highlightLine(options.line);
+ viewer.scrollToLine(options.line);
+ }
+ });
+ this.viewerRegion.show(viewer);
}
});
+