diff options
Diffstat (limited to 'server/sonar-web/src/main/js/apps/issues')
9 files changed, 495 insertions, 31 deletions
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx index 00a82856a42..08a9d12d6aa 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx @@ -23,7 +23,6 @@ import Issue from '../../../components/issue/Issue'; import SecondaryIssue from '../../../components/issue/SecondaryIssue'; import getCoverageStatus from '../../../components/SourceViewer/helpers/getCoverageStatus'; import { locationsByLine } from '../../../components/SourceViewer/helpers/indexing'; -import SourceViewerHeaderSlim from '../../../components/SourceViewer/SourceViewerHeaderSlim'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { BranchLike } from '../../../types/branch-like'; import { isFile } from '../../../types/component'; @@ -40,6 +39,7 @@ import { SourceLine, SourceViewerFile } from '../../../types/types'; +import IssueSourceViewerHeader from './IssueSourceViewerHeader'; import SnippetViewer from './SnippetViewer'; import { createSnippets, @@ -422,7 +422,7 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone return ( <div className="component-source-container" ref={this.rootNodeRef}> - <SourceViewerHeaderSlim + <IssueSourceViewerHeader branchLike={branchLike} expandable={!fullyShown && isFile(snippetGroup.component.q)} loading={loading} diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx index b3b93d314f3..0457ce290df 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx @@ -268,29 +268,35 @@ export default class CrossComponentSourceViewer extends React.PureComponent<Prop })} {locationsByComponent.length === 0 && ( - <ComponentSourceSnippetGroupViewer - branchLike={this.props.branchLike} - duplications={duplications} - duplicationsByLine={duplicationsByLine} - highlightedLocationMessage={this.props.highlightedLocationMessage} - issue={issue} - issuePopup={this.state.issuePopup} - issuesByLine={issuesByComponent[issue.component] || {}} - isLastOccurenceOfPrimaryComponent={true} - lastSnippetGroup={true} - loadDuplications={this.fetchDuplications} - locations={[]} - onIssueChange={this.props.onIssueChange} - onIssueSelect={this.props.onIssueSelect} - onIssuePopupToggle={this.handleIssuePopupToggle} - onLocationSelect={this.props.onLocationSelect} - renderDuplicationPopup={this.renderDuplicationPopup} - scroll={this.props.scroll} - snippetGroup={{ - locations: [getPrimaryLocation(issue)], - ...components[issue.component] - }} - /> + <SourceViewerContext.Provider + value={{ + branchLike: this.props.branchLike, + file: components[issue.component].component + }}> + <ComponentSourceSnippetGroupViewer + branchLike={this.props.branchLike} + duplications={duplications} + duplicationsByLine={duplicationsByLine} + highlightedLocationMessage={this.props.highlightedLocationMessage} + issue={issue} + issuePopup={this.state.issuePopup} + issuesByLine={issuesByComponent[issue.component] || {}} + isLastOccurenceOfPrimaryComponent={true} + lastSnippetGroup={true} + loadDuplications={this.fetchDuplications} + locations={[]} + onIssueChange={this.props.onIssueChange} + onIssueSelect={this.props.onIssueSelect} + onIssuePopupToggle={this.handleIssuePopupToggle} + onLocationSelect={this.props.onLocationSelect} + renderDuplicationPopup={this.renderDuplicationPopup} + scroll={this.props.scroll} + snippetGroup={{ + locations: [getPrimaryLocation(issue)], + ...components[issue.component] + }} + /> + </SourceViewerContext.Provider> )} </div> ); diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.css b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.css new file mode 100644 index 00000000000..4f70cddf426 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.css @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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. + */ +.source-viewer-header-slim { + padding: 4px 10px; + border: 1px solid var(--gray80); + background-color: var(--barBackgroundColor); + align-items: center; + min-height: 25px; +} diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx new file mode 100644 index 00000000000..69b4465cbe1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx @@ -0,0 +1,125 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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 classNames from 'classnames'; +import * as React from 'react'; +import { Link } from 'react-router-dom'; +import { ButtonIcon } from '../../../components/controls/buttons'; +import { ClipboardIconButton } from '../../../components/controls/clipboard'; +import ExpandSnippetIcon from '../../../components/icons/ExpandSnippetIcon'; +import QualifierIcon from '../../../components/icons/QualifierIcon'; +import DeferredSpinner from '../../../components/ui/DeferredSpinner'; +import { getBranchLikeQuery } from '../../../helpers/branch-like'; +import { translate } from '../../../helpers/l10n'; +import { collapsedDirFromPath, fileFromPath } from '../../../helpers/path'; +import { getBranchLikeUrl, getComponentIssuesUrl, getPathUrlAsString } from '../../../helpers/urls'; +import { BranchLike } from '../../../types/branch-like'; +import { ComponentQualifier } from '../../../types/component'; +import { SourceViewerFile } from '../../../types/types'; +import './IssueSourceViewerHeader.css'; + +export interface Props { + branchLike: BranchLike | undefined; + expandable?: boolean; + displayProjectName?: boolean; + linkToProject?: boolean; + loading?: boolean; + onExpand?: () => void; + sourceViewerFile: SourceViewerFile; +} + +export default function IssueSourceViewerHeader(props: Props) { + const { + branchLike, + expandable, + displayProjectName = true, + linkToProject = true, + loading, + onExpand, + sourceViewerFile + } = props; + const { measures, path, project, projectName, q } = sourceViewerFile; + + const projectNameLabel = ( + <> + <QualifierIcon qualifier={ComponentQualifier.Project} /> <span>{projectName}</span> + </> + ); + + const isProjectRoot = q === ComponentQualifier.Project; + + return ( + <div className="source-viewer-header-slim display-flex-row display-flex-space-between"> + <div className="display-flex-center flex-1"> + {displayProjectName && ( + <div className="spacer-right"> + {linkToProject ? ( + <a + className="link-with-icon" + href={getPathUrlAsString(getBranchLikeUrl(project, branchLike))}> + {projectNameLabel} + </a> + ) : ( + projectNameLabel + )} + </div> + )} + + {!isProjectRoot && ( + <> + <div className="spacer-right"> + <QualifierIcon qualifier={q} /> <span>{collapsedDirFromPath(path)}</span> + <span className="component-name-file">{fileFromPath(path)}</span> + </div> + + <div className="spacer-right"> + <ClipboardIconButton className="button-link link-no-underline" copyValue={path} /> + </div> + </> + )} + </div> + + {!isProjectRoot && measures.issues !== undefined && ( + <div + className={classNames('flex-0 big-spacer-left', { + 'little-spacer-right': !expandable || loading + })}> + <Link + to={getComponentIssuesUrl(project, { + ...getBranchLikeQuery(branchLike), + files: path, + resolved: 'false' + })}> + {translate('source_viewer.view_all_issues')} + </Link> + </div> + )} + + {expandable && ( + <DeferredSpinner className="little-spacer-right" loading={loading}> + <div className="flex-0 big-spacer-left"> + <ButtonIcon className="js-actions" onClick={onExpand}> + <ExpandSnippetIcon /> + </ButtonIcon> + </div> + </DeferredSpinner> + )} + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.css b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.css index 3f49652df25..b3b151ce897 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.css +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.css @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ .snippet { - margin: var(--gridSize); + margin: var(--gridSize) 0; border: 1px solid var(--gray80); overflow-x: auto; overflow-y: hidden; diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/IssueSourceViewerHeader-test.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/IssueSourceViewerHeader-test.tsx new file mode 100644 index 00000000000..06655111a37 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/IssueSourceViewerHeader-test.tsx @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; +import { mockSourceViewerFile } from '../../../../helpers/mocks/sources'; +import { ComponentQualifier } from '../../../../types/component'; +import IssueSourceViewerHeader, { Props } from '../IssueSourceViewerHeader'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); + expect(shallowRender({ linkToProject: false })).toMatchSnapshot('no link to project'); + expect(shallowRender({ displayProjectName: false })).toMatchSnapshot('no project name'); + expect( + shallowRender({ + sourceViewerFile: mockSourceViewerFile('foo/bar.ts', 'my-project', { + q: ComponentQualifier.Project + }) + }) + ).toMatchSnapshot('project root'); +}); + +function shallowRender(props: Partial<Props> = {}) { + return shallow( + <IssueSourceViewerHeader + branchLike={mockMainBranch()} + expandable={true} + onExpand={jest.fn()} + sourceViewerFile={mockSourceViewerFile('foo/bar.ts', 'my-project')} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/ComponentSourceSnippetGroupViewer-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/ComponentSourceSnippetGroupViewer-test.tsx.snap index 58f647c8012..9825cb22d5c 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/ComponentSourceSnippetGroupViewer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/ComponentSourceSnippetGroupViewer-test.tsx.snap @@ -4,7 +4,7 @@ exports[`should render correctly 1`] = ` <div className="component-source-container" > - <SourceViewerHeaderSlim + <IssueSourceViewerHeader branchLike={ Object { "analysisDate": "2018-01-01", diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/IssueSourceViewerHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/IssueSourceViewerHeader-test.tsx.snap new file mode 100644 index 00000000000..0b6e42a0fb3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/IssueSourceViewerHeader-test.tsx.snap @@ -0,0 +1,261 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<div + className="source-viewer-header-slim display-flex-row display-flex-space-between" +> + <div + className="display-flex-center flex-1" + > + <div + className="spacer-right" + > + <a + className="link-with-icon" + href="/dashboard?id=my-project" + > + <QualifierIcon + qualifier="TRK" + /> + + <span> + MyProject + </span> + </a> + </div> + <div + className="spacer-right" + > + <QualifierIcon + qualifier="FIL" + /> + + <span> + foo/ + </span> + <span + className="component-name-file" + > + bar.ts + </span> + </div> + <div + className="spacer-right" + > + <ClipboardIconButton + className="button-link link-no-underline" + copyValue="foo/bar.ts" + /> + </div> + </div> + <div + className="flex-0 big-spacer-left" + > + <Link + to={ + Object { + "hash": "", + "pathname": "/project/issues", + "search": "?files=foo%2Fbar.ts&resolved=false&id=my-project", + } + } + > + source_viewer.view_all_issues + </Link> + </div> + <DeferredSpinner + className="little-spacer-right" + > + <div + className="flex-0 big-spacer-left" + > + <ButtonIcon + className="js-actions" + onClick={[MockFunction]} + > + <ExpandSnippetIcon /> + </ButtonIcon> + </div> + </DeferredSpinner> +</div> +`; + +exports[`should render correctly: no link to project 1`] = ` +<div + className="source-viewer-header-slim display-flex-row display-flex-space-between" +> + <div + className="display-flex-center flex-1" + > + <div + className="spacer-right" + > + <QualifierIcon + qualifier="TRK" + /> + + <span> + MyProject + </span> + </div> + <div + className="spacer-right" + > + <QualifierIcon + qualifier="FIL" + /> + + <span> + foo/ + </span> + <span + className="component-name-file" + > + bar.ts + </span> + </div> + <div + className="spacer-right" + > + <ClipboardIconButton + className="button-link link-no-underline" + copyValue="foo/bar.ts" + /> + </div> + </div> + <div + className="flex-0 big-spacer-left" + > + <Link + to={ + Object { + "hash": "", + "pathname": "/project/issues", + "search": "?files=foo%2Fbar.ts&resolved=false&id=my-project", + } + } + > + source_viewer.view_all_issues + </Link> + </div> + <DeferredSpinner + className="little-spacer-right" + > + <div + className="flex-0 big-spacer-left" + > + <ButtonIcon + className="js-actions" + onClick={[MockFunction]} + > + <ExpandSnippetIcon /> + </ButtonIcon> + </div> + </DeferredSpinner> +</div> +`; + +exports[`should render correctly: no project name 1`] = ` +<div + className="source-viewer-header-slim display-flex-row display-flex-space-between" +> + <div + className="display-flex-center flex-1" + > + <div + className="spacer-right" + > + <QualifierIcon + qualifier="FIL" + /> + + <span> + foo/ + </span> + <span + className="component-name-file" + > + bar.ts + </span> + </div> + <div + className="spacer-right" + > + <ClipboardIconButton + className="button-link link-no-underline" + copyValue="foo/bar.ts" + /> + </div> + </div> + <div + className="flex-0 big-spacer-left" + > + <Link + to={ + Object { + "hash": "", + "pathname": "/project/issues", + "search": "?files=foo%2Fbar.ts&resolved=false&id=my-project", + } + } + > + source_viewer.view_all_issues + </Link> + </div> + <DeferredSpinner + className="little-spacer-right" + > + <div + className="flex-0 big-spacer-left" + > + <ButtonIcon + className="js-actions" + onClick={[MockFunction]} + > + <ExpandSnippetIcon /> + </ButtonIcon> + </div> + </DeferredSpinner> +</div> +`; + +exports[`should render correctly: project root 1`] = ` +<div + className="source-viewer-header-slim display-flex-row display-flex-space-between" +> + <div + className="display-flex-center flex-1" + > + <div + className="spacer-right" + > + <a + className="link-with-icon" + href="/dashboard?id=my-project" + > + <QualifierIcon + qualifier="TRK" + /> + + <span> + MyProject + </span> + </a> + </div> + </div> + <DeferredSpinner + className="little-spacer-right" + > + <div + className="flex-0 big-spacer-left" + > + <ButtonIcon + className="js-actions" + onClick={[MockFunction]} + > + <ExpandSnippetIcon /> + </ButtonIcon> + </div> + </DeferredSpinner> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/issues/styles.css b/server/sonar-web/src/main/js/apps/issues/styles.css index 0e5328c913b..44e4a054e81 100644 --- a/server/sonar-web/src/main/js/apps/issues/styles.css +++ b/server/sonar-web/src/main/js/apps/issues/styles.css @@ -137,10 +137,6 @@ color: white; } -.component-source-container { - border: 1px solid var(--gray80); -} - .component-source-container + .component-source-container { margin-top: var(--gridSize); } |