import backgroundTasksRoutes from '../../apps/background-tasks/routes';
import codeRoutes from '../../apps/code/routes';
import codingRulesRoutes from '../../apps/coding-rules/routes';
-import componentRoutes from '../../apps/component/routes';
import componentMeasuresRoutes from '../../apps/component-measures/routes';
import customMeasuresRoutes from '../../apps/custom-measures/routes';
import groupsRoutes from '../../apps/groups/routes';
{!isSonarCloud() && (
<RouteWithChildRoutes path="coding_rules" childRoutes={codingRulesRoutes} />
)}
- <RouteWithChildRoutes path="component" childRoutes={componentRoutes} />
<RouteWithChildRoutes path="documentation" childRoutes={documentationRoutes} />
<Route path="explore" component={Explore}>
<Route path="issues" component={ExploreIssues} />
import * as classNames from 'classnames';
import { connect } from 'react-redux';
import Helmet from 'react-helmet';
+import { Location } from 'history';
import Components from './Components';
import Breadcrumbs from './Breadcrumbs';
import Search from './Search';
+import SourceViewerWrapper from './SourceViewerWrapper';
import { addComponent, addComponentBreadcrumbs, clearBucket } from '../bucket';
import { retrieveComponentChildren, retrieveComponent, loadMoreChildren } from '../utils';
import ListFooter from '../../../components/controls/ListFooter';
-import SourceViewer from '../../../components/SourceViewer/SourceViewer';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import { fetchMetrics } from '../../../store/rootActions';
import { getMetrics } from '../../../store/rootReducer';
interface OwnProps {
branchLike?: T.BranchLike;
component: T.Component;
- location: { query: { [x: string]: string } };
+ location: Pick<Location, 'query'>;
}
type Props = StateToProps & DispatchToProps & OwnProps;
};
render() {
- const { branchLike, component } = this.props;
+ const { branchLike, component, location } = this.props;
const { loading, baseComponent, components, breadcrumbs, total, sourceViewer } = this.state;
const shouldShowBreadcrumbs = breadcrumbs.length > 1;
{sourceViewer !== undefined && (
<div className="spacer-top">
- <SourceViewer branchLike={branchLike} component={sourceViewer.key} />
+ <SourceViewerWrapper
+ branchLike={branchLike}
+ component={sourceViewer.key}
+ location={location}
+ />
</div>
)}
</div>
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 * as React from 'react';
+import { Location } from 'history';
+import SourceViewer from '../../../components/SourceViewer/SourceViewer';
+import { scrollToElement } from '../../../helpers/scrolling';
+
+interface Props {
+ branchLike?: T.BranchLike;
+ component: string;
+ location: Pick<Location, 'query'>;
+}
+
+export default function SourceViewerWrapper({ branchLike, component, location }: Props) {
+ const { line } = location.query;
+
+ const scrollToLine = () => {
+ if (line) {
+ const row = document.querySelector(`.source-line[data-line-number="${line}"]`);
+ if (row) {
+ scrollToElement(row, { smooth: false, bottomOffset: window.innerHeight / 2 - 60 });
+ }
+ }
+ };
+
+ const finalLine = line ? Number(line) : undefined;
+
+ return (
+ <SourceViewer
+ aroundLine={finalLine}
+ branchLike={branchLike}
+ component={component}
+ highlightedLine={finalLine}
+ onLoaded={scrollToLine}
+ />
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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 * as React from 'react';
-import SourceViewer from '../../../components/SourceViewer/SourceViewer';
-import { fillBranchLike } from '../../../helpers/branches';
-
-interface Props {
- location: {
- query: {
- branch?: string;
- id: string;
- line?: string;
- pullRequest?: string;
- };
- };
-}
-
-export default class App extends React.PureComponent<Props> {
- scrollToLine = () => {
- const { line } = this.props.location.query;
- if (line) {
- const row = document.querySelector(`.source-line[data-line-number="${line}"]`);
- if (row) {
- const rect = row.getBoundingClientRect();
- const topOffset = window.innerHeight / 2 - 60;
- const goal = rect.top - topOffset;
- window.scrollTo(0, goal);
- }
- }
- };
-
- render() {
- const { branch, id, line, pullRequest } = this.props.location.query;
-
- const finalLine = line ? Number(line) : undefined;
-
- // TODO find a way to avoid creating this fakeBranchLike
- // probably the best way would be to drop this page completely
- // and redirect to the Code page
- const fakeBranchLike = fillBranchLike(branch, pullRequest);
-
- return (
- <div className="page page-limited">
- <SourceViewer
- aroundLine={finalLine}
- branchLike={fakeBranchLike}
- component={id}
- highlightedLine={finalLine}
- onLoaded={this.scrollToLine}
- />
- </div>
- );
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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 * as React from 'react';
-import { shallow } from 'enzyme';
-import App from '../App';
-
-it('renders', () => {
- expect(
- shallow(<App location={{ query: { branch: 'b', id: 'foo', line: '7' } }} />)
- ).toMatchSnapshot();
-});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders 1`] = `
-<div
- className="page page-limited"
->
- <LazyLoader
- aroundLine={7}
- branchLike={
- Object {
- "isMain": false,
- "mergeBranch": "",
- "name": "b",
- "type": "SHORT",
- }
- }
- component="foo"
- highlightedLine={7}
- onLoaded={[Function]}
- />
-</div>
-`;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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 { lazyLoad } from '../../components/lazyLoad';
-
-const routes = [
- {
- indexRoute: { component: lazyLoad(() => import('./components/App')) }
- }
-];
-
-export default routes;
"pathname": "/code",
"query": Object {
"id": "proj",
+ "line": undefined,
"selected": "comp",
},
}
"query": Object {
"branch": "feature",
"id": "proj",
+ "line": undefined,
"selected": "comp",
},
}
"pathname": "/code",
"query": Object {
"id": "proj",
+ "line": undefined,
"selected": "comp",
},
}
import { intersection, uniqBy } from 'lodash';
import SourceViewerHeader from './SourceViewerHeader';
import SourceViewerCode from './SourceViewerCode';
+import { SourceViewerContext } from './SourceViewerContext';
import DuplicationPopup from './components/DuplicationPopup';
import defaultLoadIssues from './helpers/loadIssues';
import getCoverageStatus from './helpers/getCoverageStatus';
});
return (
- <div className={className} ref={node => (this.node = node)}>
- <WorkspaceContext.Consumer>
- {({ openComponent }) => (
- <SourceViewerHeader
- branchLike={this.props.branchLike}
- openComponent={openComponent}
- sourceViewerFile={component}
- />
+ <SourceViewerContext.Provider value={{ branchLike: this.props.branchLike, file: component }}>
+ <div className={className} ref={node => (this.node = node)}>
+ <WorkspaceContext.Consumer>
+ {({ openComponent }) => (
+ <SourceViewerHeader
+ branchLike={this.props.branchLike}
+ openComponent={openComponent}
+ sourceViewerFile={component}
+ />
+ )}
+ </WorkspaceContext.Consumer>
+ {sourceRemoved && (
+ <Alert className="spacer-top" variant="warning">
+ {translate('code_viewer.no_source_code_displayed_due_to_source_removed')}
+ </Alert>
)}
- </WorkspaceContext.Consumer>
- {sourceRemoved && (
- <Alert className="spacer-top" variant="warning">
- {translate('code_viewer.no_source_code_displayed_due_to_source_removed')}
- </Alert>
- )}
- {!sourceRemoved && sources !== undefined && this.renderCode(sources)}
- </div>
+ {!sourceRemoved && sources !== undefined && this.renderCode(sources)}
+ </div>
+ </SourceViewerContext.Provider>
);
}
}
return (
<Line
branchLike={this.props.branchLike}
- componentKey={this.props.componentKey}
displayAllIssues={this.props.displayAllIssues}
displayCoverage={displayCoverage}
displayDuplications={displayDuplications}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 * as React from 'react';
+
+interface SourceViewerContextShape {
+ branchLike?: T.BranchLike;
+ file: T.SourceViewerFile;
+}
+
+export const SourceViewerContext = React.createContext({
+ branchLike: {},
+ file: {}
+}) as React.Context<SourceViewerContextShape>;
getPathUrlAsString,
getBranchLikeUrl,
getComponentIssuesUrl,
- getBaseUrl
+ getBaseUrl,
+ getCodeUrl
} from '../../helpers/urls';
import { collapsedDirFromPath, fileFromPath } from '../../helpers/path';
import { translate } from '../../helpers/l10n';
</a>
</li>
<li>
- <a
+ <Link
className="js-new-window"
- href={getPathUrlAsString({
- pathname: '/component',
- query: { id: key, ...getBranchLikeQuery(this.props.branchLike) }
- })}
- target="_blank">
+ rel="noopener noreferrer"
+ target="_blank"
+ to={getCodeUrl(this.props.sourceViewerFile.project, this.props.branchLike, key)}>
{translate('component_viewer.new_window')}
- </a>
+ </Link>
</li>
{!workspace && (
<li>
</li>
)}
<li>
- <a className="js-raw-source" href={rawSourcesLink} target="_blank">
+ <a
+ className="js-raw-source"
+ href={rawSourcesLink}
+ rel="noopener noreferrer"
+ target="_blank">
{translate('component_viewer.show_raw_source')}
</a>
</li>
interface Props {
branchLike: T.BranchLike | undefined;
- componentKey: string;
displayAllIssues?: boolean;
displayCoverage: boolean;
displayDuplications: boolean;
return (
<tr className={className} data-line-number={line.line}>
<LineNumber
- branchLike={this.props.branchLike}
- componentKey={this.props.componentKey}
line={line}
onPopupToggle={this.props.onLinePopupToggle}
popupOpen={this.isPopupOpen('line-number')}
import Toggler from '../../controls/Toggler';
interface Props {
- branchLike: T.BranchLike | undefined;
- componentKey: string;
line: T.SourceLine;
onPopupToggle: (x: { index?: number; line: number; name: string; open?: boolean }) => void;
popupOpen: boolean;
};
render() {
- const { branchLike, componentKey, line, popupOpen } = this.props;
+ const { line, popupOpen } = this.props;
const { line: lineNumber } = line;
const hasLineNumber = !!lineNumber;
return hasLineNumber ? (
<Toggler
onRequestClose={this.closePopup}
open={popupOpen}
- overlay={
- <LineOptionsPopup branchLike={branchLike} componentKey={componentKey} line={line} />
- }
+ overlay={<LineOptionsPopup line={line} />}
/>
</td>
) : (
import { DropdownOverlay } from '../../controls/Dropdown';
import { PopupPlacement } from '../../ui/popups';
import { translate } from '../../../helpers/l10n';
-import { getBranchLikeQuery } from '../../../helpers/branches';
+import { getCodeUrl } from '../../../helpers/urls';
+import { SourceViewerContext } from '../SourceViewerContext';
interface Props {
- branchLike: T.BranchLike | undefined;
- componentKey: string;
line: T.SourceLine;
}
-export default function LineOptionsPopup({ branchLike, componentKey, line }: Props) {
- const permalink = {
- pathname: '/component',
- query: { id: componentKey, line: line.line, ...getBranchLikeQuery(branchLike) }
- };
+export default function LineOptionsPopup({ line }: Props) {
return (
- <DropdownOverlay placement={PopupPlacement.RightTop}>
- <div className="source-viewer-bubble-popup nowrap">
- <Link className="js-get-permalink" to={permalink}>
- {translate('component_viewer.get_permalink')}
- </Link>
- </div>
- </DropdownOverlay>
+ <SourceViewerContext.Consumer>
+ {({ branchLike, file }) => (
+ <DropdownOverlay placement={PopupPlacement.RightTop}>
+ <div className="source-viewer-bubble-popup nowrap">
+ <Link
+ className="js-get-permalink"
+ onClick={event => {
+ event.stopPropagation();
+ }}
+ rel="noopener noreferrer"
+ target="_blank"
+ to={getCodeUrl(file.project, branchLike, file.key, line.line)}>
+ {translate('component_viewer.get_permalink')}
+ </Link>
+ </div>
+ </DropdownOverlay>
+ )}
+ </SourceViewerContext.Consumer>
);
}
it('render line 3', () => {
const line = { line: 3 };
- const wrapper = shallow(
- <LineNumber
- branchLike={undefined}
- componentKey="foo"
- line={line}
- onPopupToggle={jest.fn()}
- popupOpen={false}
- />
- );
+ const wrapper = shallow(<LineNumber line={line} onPopupToggle={jest.fn()} popupOpen={false} />);
expect(wrapper).toMatchSnapshot();
click(wrapper);
});
it('render line 0', () => {
const line = { line: 0 };
- const wrapper = shallow(
- <LineNumber
- branchLike={undefined}
- componentKey="foo"
- line={line}
- onPopupToggle={jest.fn()}
- popupOpen={false}
- />
- );
+ const wrapper = shallow(<LineNumber line={line} onPopupToggle={jest.fn()} popupOpen={false} />);
expect(wrapper).toMatchSnapshot();
});
import { shallow } from 'enzyme';
import LineOptionsPopup from '../LineOptionsPopup';
+jest.mock('../../SourceViewerContext', () => ({
+ SourceViewerContext: {
+ Consumer: (props: any) =>
+ props.children({
+ branchLike: { isMain: false, name: 'feature', type: 'SHORT' },
+ file: { project: 'prj', key: 'foo' }
+ })
+ }
+}));
+
it('should render', () => {
const line = { line: 3 };
- const branch: T.ShortLivingBranch = {
- isMain: false,
- mergeBranch: 'master',
- name: 'feature',
- type: 'SHORT'
- };
- const wrapper = shallow(<LineOptionsPopup branchLike={branch} componentKey="foo" line={line} />);
+ const wrapper = shallow(<LineOptionsPopup line={line} />).dive();
expect(wrapper).toMatchSnapshot();
});
open={false}
overlay={
<LineOptionsPopup
- componentKey="foo"
line={
Object {
"line": 3,
>
<Link
className="js-get-permalink"
+ onClick={[Function]}
onlyActiveOnIndex={false}
+ rel="noopener noreferrer"
style={Object {}}
+ target="_blank"
to={
Object {
- "pathname": "/component",
+ "pathname": "/code",
"query": Object {
"branch": "feature",
- "id": "foo",
+ "id": "prj",
"line": 3,
+ "selected": "foo",
},
}
}
return getBaseUrl() + '/markdown/help';
}
-export function getCodeUrl(project: string, branchLike?: T.BranchLike, selected?: string) {
- return { pathname: '/code', query: { id: project, ...getBranchLikeQuery(branchLike), selected } };
+export function getCodeUrl(
+ project: string,
+ branchLike?: T.BranchLike,
+ selected?: string,
+ line?: number
+) {
+ return {
+ pathname: '/code',
+ query: { id: project, ...getBranchLikeQuery(branchLike), selected, line }
+ };
}
export function getOrganizationUrl(organization: string) {