* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { groupBy } from 'lodash';
import * as React from 'react';
import { BranchLike } from '../../../types/branch-like';
import { Component, Issue } from '../../../types/types';
import { Query } from '../utils';
+import ComponentBreadcrumbs from './ComponentBreadcrumbs';
import ListItem from './ListItem';
interface Props {
}
}
+ renderIssueComponentList = (issues: Issue[], index: number) => {
+ const { branchLike, checked, component, openPopup, selectedIssue } = this.props;
+ return (
+ <React.Fragment key={index}>
+ <li>
+ <div className="issues-workspace-list-component note">
+ <ComponentBreadcrumbs component={component} issue={issues[0]} />
+ </div>
+ </li>
+ <ul>
+ {issues.map((issue) => (
+ <ListItem
+ branchLike={branchLike}
+ checked={checked.includes(issue.key)}
+ issue={issue}
+ key={issue.key}
+ onChange={this.props.onIssueChange}
+ onCheck={this.props.onIssueCheck}
+ onClick={this.props.onIssueClick}
+ onFilterChange={this.props.onFilterChange}
+ onPopupToggle={this.props.onPopupToggle}
+ openPopup={openPopup && openPopup.issue === issue.key ? openPopup.name : undefined}
+ selected={selectedIssue != null && selectedIssue.key === issue.key}
+ />
+ ))}
+ </ul>
+ </React.Fragment>
+ );
+ };
+
render() {
- const { branchLike, checked, component, issues, openPopup, selectedIssue } = this.props;
+ const { issues } = this.props;
const { prerender } = this.state;
if (prerender) {
);
}
- return (
- <ul>
- {issues.map((issue, index) => (
- <ListItem
- branchLike={branchLike}
- checked={checked.includes(issue.key)}
- component={component}
- issue={issue}
- key={issue.key}
- onChange={this.props.onIssueChange}
- onCheck={this.props.onIssueCheck}
- onClick={this.props.onIssueClick}
- onFilterChange={this.props.onFilterChange}
- onPopupToggle={this.props.onPopupToggle}
- openPopup={openPopup && openPopup.issue === issue.key ? openPopup.name : undefined}
- previousIssue={index > 0 ? issues[index - 1] : undefined}
- selected={selectedIssue != null && selectedIssue.key === issue.key}
- />
- ))}
- </ul>
- );
+ const issuesByComponent = groupBy(issues, (issue) => `(${issue.component} : ${issue.branch})`);
+
+ return <ul>{Object.values(issuesByComponent).map(this.renderIssueComponentList)}</ul>;
}
}
import * as React from 'react';
import Issue from '../../../components/issue/Issue';
import { BranchLike } from '../../../types/branch-like';
-import { Component, Issue as TypeIssue } from '../../../types/types';
+import { Issue as TypeIssue } from '../../../types/types';
import { Query } from '../utils';
-import ComponentBreadcrumbs from './ComponentBreadcrumbs';
interface Props {
branchLike: BranchLike | undefined;
checked: boolean;
- component: Component | undefined;
issue: TypeIssue;
onChange: (issue: TypeIssue) => void;
onCheck: ((issueKey: string) => void) | undefined;
onFilterChange: (changes: Partial<Query>) => void;
onPopupToggle: (issue: string, popupName: string, open?: boolean) => void;
openPopup: string | undefined;
- previousIssue: TypeIssue | undefined;
selected: boolean;
}
};
render() {
- const { branchLike, component, issue, previousIssue } = this.props;
-
- const displayComponent =
- !previousIssue ||
- previousIssue.component !== issue.component ||
- previousIssue.branch !== issue.branch;
+ const { branchLike, issue } = this.props;
return (
<li className="issues-workspace-list-item" ref={(node) => (this.nodeRef = node)}>
- {displayComponent && (
- <div className="issues-workspace-list-component note">
- <ComponentBreadcrumbs component={component} issue={this.props.issue} />
- </div>
- )}
<Issue
branchLike={branchLike}
checked={this.props.checked}
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockBranch } from '../../../../helpers/mocks/branch-like';
-import { mockComponent } from '../../../../helpers/mocks/component';
import { mockIssue } from '../../../../helpers/testMocks';
import ListItem from '../ListItem';
<ListItem
branchLike={mockBranch()}
checked={false}
- component={mockComponent()}
issue={mockIssue()}
onChange={jest.fn()}
onCheck={jest.fn()}
onFilterChange={jest.fn()}
onPopupToggle={jest.fn()}
openPopup={undefined}
- previousIssue={mockIssue(false, { branch: 'branch-8.7' })}
selected={false}
{...props}
/>
exports[`should render correctly 2`] = `
<ul>
- <ListItem
- checked={false}
- issue={
- {
- "actions": [],
- "component": "main.js",
- "componentEnabled": true,
- "componentLongName": "main.js",
- "componentQualifier": "FIL",
- "componentUuid": "foo1234",
- "creationDate": "2017-03-01T09:36:01+0100",
- "flows": [],
- "flowsWithType": [],
- "key": "AVsae-CQS-9G3txfbFN2",
- "line": 25,
- "message": "Reduce the number of conditional operators (4) used in the expression",
- "project": "myproject",
- "projectKey": "foo",
- "projectName": "Foo",
- "rule": "javascript:S1067",
- "ruleName": "foo",
- "secondaryLocations": [],
- "severity": "MAJOR",
- "status": "OPEN",
- "textRange": {
- "endLine": 26,
- "endOffset": 15,
- "startLine": 25,
- "startOffset": 0,
- },
- "transitions": [],
- "type": "BUG",
+ <li>
+ <div
+ className="issues-workspace-list-component note"
+ >
+ <ComponentBreadcrumbs
+ issue={
+ {
+ "actions": [],
+ "component": "main.js",
+ "componentEnabled": true,
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": [],
+ "flowsWithType": [],
+ "key": "AVsae-CQS-9G3txfbFN2",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": [],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": [],
+ "type": "BUG",
+ }
+ }
+ />
+ </div>
+ </li>
+ <ul>
+ <ListItem
+ checked={false}
+ issue={
+ {
+ "actions": [],
+ "component": "main.js",
+ "componentEnabled": true,
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": [],
+ "flowsWithType": [],
+ "key": "AVsae-CQS-9G3txfbFN2",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": [],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": [],
+ "type": "BUG",
+ }
}
- }
- key="AVsae-CQS-9G3txfbFN2"
- onChange={[MockFunction]}
- onCheck={[MockFunction]}
- onClick={[MockFunction]}
- onFilterChange={[MockFunction]}
- onPopupToggle={[MockFunction]}
- selected={false}
- />
- <ListItem
- checked={false}
- issue={
- {
- "actions": [],
- "component": "main.js",
- "componentEnabled": true,
- "componentLongName": "main.js",
- "componentQualifier": "FIL",
- "componentUuid": "foo1234",
- "creationDate": "2017-03-01T09:36:01+0100",
- "flows": [],
- "flowsWithType": [],
- "key": "AVsae-CQS-9G3txfbFN3",
- "line": 25,
- "message": "Reduce the number of conditional operators (4) used in the expression",
- "project": "myproject",
- "projectKey": "foo",
- "projectName": "Foo",
- "rule": "javascript:S1067",
- "ruleName": "foo",
- "secondaryLocations": [],
- "severity": "MAJOR",
- "status": "OPEN",
- "textRange": {
- "endLine": 26,
- "endOffset": 15,
- "startLine": 25,
- "startOffset": 0,
- },
- "transitions": [],
- "type": "BUG",
- }
- }
- key="AVsae-CQS-9G3txfbFN3"
- onChange={[MockFunction]}
- onCheck={[MockFunction]}
- onClick={[MockFunction]}
- onFilterChange={[MockFunction]}
- onPopupToggle={[MockFunction]}
- previousIssue={
- {
- "actions": [],
- "component": "main.js",
- "componentEnabled": true,
- "componentLongName": "main.js",
- "componentQualifier": "FIL",
- "componentUuid": "foo1234",
- "creationDate": "2017-03-01T09:36:01+0100",
- "flows": [],
- "flowsWithType": [],
- "key": "AVsae-CQS-9G3txfbFN2",
- "line": 25,
- "message": "Reduce the number of conditional operators (4) used in the expression",
- "project": "myproject",
- "projectKey": "foo",
- "projectName": "Foo",
- "rule": "javascript:S1067",
- "ruleName": "foo",
- "secondaryLocations": [],
- "severity": "MAJOR",
- "status": "OPEN",
- "textRange": {
- "endLine": 26,
- "endOffset": 15,
- "startLine": 25,
- "startOffset": 0,
- },
- "transitions": [],
- "type": "BUG",
+ key="AVsae-CQS-9G3txfbFN2"
+ onChange={[MockFunction]}
+ onCheck={[MockFunction]}
+ onClick={[MockFunction]}
+ onFilterChange={[MockFunction]}
+ onPopupToggle={[MockFunction]}
+ selected={false}
+ />
+ <ListItem
+ checked={false}
+ issue={
+ {
+ "actions": [],
+ "component": "main.js",
+ "componentEnabled": true,
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": [],
+ "flowsWithType": [],
+ "key": "AVsae-CQS-9G3txfbFN3",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": [],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": [],
+ "type": "BUG",
+ }
}
- }
- selected={false}
- />
+ key="AVsae-CQS-9G3txfbFN3"
+ onChange={[MockFunction]}
+ onCheck={[MockFunction]}
+ onClick={[MockFunction]}
+ onFilterChange={[MockFunction]}
+ onPopupToggle={[MockFunction]}
+ selected={false}
+ />
+ </ul>
</ul>
`;
<li
className="issues-workspace-list-item"
>
- <div
- className="issues-workspace-list-component note"
- >
- <ComponentBreadcrumbs
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- issue={
- {
- "actions": [],
- "component": "main.js",
- "componentEnabled": true,
- "componentLongName": "main.js",
- "componentQualifier": "FIL",
- "componentUuid": "foo1234",
- "creationDate": "2017-03-01T09:36:01+0100",
- "flows": [],
- "flowsWithType": [],
- "key": "AVsae-CQS-9G3txfbFN2",
- "line": 25,
- "message": "Reduce the number of conditional operators (4) used in the expression",
- "project": "myproject",
- "projectKey": "foo",
- "projectName": "Foo",
- "rule": "javascript:S1067",
- "ruleName": "foo",
- "secondaryLocations": [],
- "severity": "MAJOR",
- "status": "OPEN",
- "textRange": {
- "endLine": 26,
- "endOffset": 15,
- "startLine": 25,
- "startOffset": 0,
- },
- "transitions": [],
- "type": "BUG",
- }
- }
- />
- </div>
<Issue
branchLike={
{
}
.issues-workspace-list-component {
- padding: 10px 0 6px;
+ padding: 15px 0 6px;
}
.issues-workspace-list-item + .issues-workspace-list-item {
margin-top: 5px;
}
-.issues-workspace-list-component + .issues-workspace-list-item {
- margin-top: 10px;
-}
-
-.issues-workspace-list-item:first-child .issues-workspace-list-component {
+li:first-child .issues-workspace-list-component {
padding-top: 0;
}
-.issues-workspace-list-component + .issues-workspace-list-item {
- margin-top: 0;
-}
-
.issues-predefined-periods {
display: flex;
}
white-space: nowrap;
}
+.issue-message .button-plain {
+ line-height: 18px;
+ font-size: var(--baseFontSize);
+ font-weight: 600;
+ text-align: left;
+}
+
.issue-message {
flex: 1;
padding-left: var(--gridSize);
}
.issue-message-box.secondary-issue:hover,
-.issue:focus-within,
.issue:hover {
border: 2px dashed var(--blue);
outline: 0;
}
};
- handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
+ handleBoxClick = (event: React.MouseEvent<HTMLDivElement>) => {
if (!isClickable(event.target as HTMLElement) && this.props.onClick) {
event.preventDefault();
+ this.handleDetailClick();
+ }
+ };
+
+ handleDetailClick = () => {
+ if (this.props.onClick) {
this.props.onClick(this.props.issue.key);
}
};
return (
<div
className={issueClass}
- onClick={this.handleClick}
+ onClick={this.handleBoxClick}
role="region"
aria-label={issue.message}
>
)}
<IssueTitleBar
branchLike={branchLike}
+ onClick={this.handleDetailClick}
currentPopup={currentPopup}
displayLocationsCount={displayLocationsCount}
displayLocationsLink={displayLocationsLink}
"type": "SECURITY_HOTSPOT",
}
}
+ onClick={[Function]}
togglePopup={[MockFunction]}
/>
<IssueActionsBar
"type": "BUG",
}
}
+ onClick={[Function]}
togglePopup={[MockFunction]}
/>
<IssueActionsBar
import { RuleStatus } from '../../../types/rules';
import { Issue } from '../../../types/types';
import Link from '../../common/Link';
+import { ButtonPlain } from '../../controls/buttons';
import { IssueMessageHighlighting } from '../IssueMessageHighlighting';
import IssueMessageTags from './IssueMessageTags';
export interface IssueMessageProps {
+ onClick?: () => void;
issue: Issue;
branchLike?: BranchLike;
displayWhyIsThisAnIssue?: boolean;
return (
<>
<div className="display-inline-flex-center issue-message break-word">
- <span className="spacer-right">
- <IssueMessageHighlighting message={message} messageFormattings={messageFormattings} />
- </span>
+ {props.onClick ? (
+ <ButtonPlain preventDefault={true} className="spacer-right" onClick={props.onClick}>
+ <IssueMessageHighlighting message={message} messageFormattings={messageFormattings} />
+ </ButtonPlain>
+ ) : (
+ <span className="spacer-right">
+ <IssueMessageHighlighting message={message} messageFormattings={messageFormattings} />
+ </span>
+ )}
<IssueMessageTags
engine={externalRuleEngine}
quickFixAvailable={quickFixAvailable}
export interface IssueTitleBarProps {
branchLike?: BranchLike;
+ onClick?: () => void;
currentPopup?: string;
displayWhyIsThisAnIssue?: boolean;
displayLocationsCount?: boolean;
issue={issue}
branchLike={props.branchLike}
displayWhyIsThisAnIssue={displayWhyIsThisAnIssue}
+ onClick={props.onClick}
/>
<div className="issue-row-meta">
<div className="issue-meta-list">