diff options
Diffstat (limited to 'server/sonar-web/src/main/js/components')
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> <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}} <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"> </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); } }); + |