diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2019-04-03 14:07:18 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2019-05-06 11:01:14 +0200 |
commit | 570316af531be1479cb7fa504c9ba9777babbd03 (patch) | |
tree | 6834118303f4292851c4fd667f71a659527a25d6 /server/sonar-web/src | |
parent | a9cb1fca0c0b6ec2fa1f11a6a9068469fe955173 (diff) | |
download | sonarqube-570316af531be1479cb7fa504c9ba9777babbd03.tar.gz sonarqube-570316af531be1479cb7fa504c9ba9777babbd03.zip |
SONAR-11897 Increase sidebar issue box usability
Diffstat (limited to 'server/sonar-web/src')
22 files changed, 989 insertions, 112 deletions
diff --git a/server/sonar-web/src/main/js/app/theme.js b/server/sonar-web/src/main/js/app/theme.js index 62053ce4d4d..b4371f19e0a 100644 --- a/server/sonar-web/src/main/js/app/theme.js +++ b/server/sonar-web/src/main/js/app/theme.js @@ -37,6 +37,8 @@ module.exports = { lineCoverageRed: '#a4030f', purple: '#9139d4', + conciseIssueRed: '#d18582', + gray94: '#efefef', gray80: '#cdcdcd', gray71: '#b4b4b4', @@ -45,6 +47,7 @@ module.exports = { gray40: '#404040', transparentWhite: 'rgba(255,255,255,0.62)', + transparentGray: 'rgba(200, 200, 200, 0.5)', disableGrayText: '#bbb', disableGrayBorder: '#ddd', diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.tsx b/server/sonar-web/src/main/js/apps/issues/components/App.tsx index 159bff63ae6..0b7307f2ce5 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/App.tsx @@ -1036,29 +1036,6 @@ export class App extends React.PureComponent<Props, State> { ); } - renderShortcutsForLocations() { - const { openIssue } = this.state; - if (!openIssue || (!openIssue.secondaryLocations.length && !openIssue.flows.length)) { - return null; - } - const hasSeveralFlows = openIssue.flows.length > 1; - return ( - <div className="pull-right note"> - <span className="shortcut-button little-spacer-right">alt</span> - <span className="little-spacer-right">{'+'}</span> - <span className="shortcut-button little-spacer-right">↑</span> - <span className="shortcut-button little-spacer-right">↓</span> - {hasSeveralFlows && ( - <span> - <span className="shortcut-button little-spacer-right">←</span> - <span className="shortcut-button little-spacer-right">→</span> - </span> - )} - {translate('issues.to_navigate_issue_locations')} - </div> - ); - } - renderPage() { const { checkAll, loading, openIssue, paging } = this.state; return ( @@ -1134,7 +1111,6 @@ export class App extends React.PureComponent<Props, State> { selectedIndex={selectedIndex} /> )} - {this.renderShortcutsForLocations()} </div> </div> </div> diff --git a/server/sonar-web/src/main/js/apps/issues/components/LocationNavigationKeyboardShortcuts.tsx b/server/sonar-web/src/main/js/apps/issues/components/LocationNavigationKeyboardShortcuts.tsx new file mode 100644 index 00000000000..069ff623c27 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/components/LocationNavigationKeyboardShortcuts.tsx @@ -0,0 +1,40 @@ +/* + * 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 { translate } from '../../../helpers/l10n'; + +export interface Props { + issue: Pick<T.Issue, 'flows' | 'secondaryLocations'> | undefined; +} + +export default function LocationNavigationKeyboardShortcuts({ issue }: Props) { + if (!issue || (!issue.secondaryLocations.length && !issue.flows.length)) { + return null; + } + const hasSeveralFlows = issue.flows.length > 1; + return ( + <div className="navigation-keyboard-shortcuts big-spacer-top text-center"> + <span> + alt + ↑ ↓ {hasSeveralFlows && <>←→</>} + {translate('issues.to_navigate_issue_locations')} + </span> + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx index da45c568f93..2b473a7f88e 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx @@ -24,9 +24,17 @@ import { mockLoggedInUser, mockRouter, mockIssue, - mockLocation + mockLocation, + mockEvent } from '../../../../helpers/testMocks'; import { waitAndUpdate } from '../../../../helpers/testUtils'; +import { + enableLocationsNavigator, + selectNextLocation, + selectPreviousLocation, + selectNextFlow, + selectPreviousFlow +} from '../../actions'; const ISSUES = [ { key: 'foo' } as T.Issue, @@ -181,6 +189,37 @@ it('should fetch issues until defined', async () => { expect(result.paging.pageIndex).toBe(3); }); +describe('keydown event handler', () => { + const wrapper = shallowRender(); + const instance = wrapper.instance(); + jest.spyOn(instance, 'setState'); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should handle alt', () => { + instance.handleKeyDown(mockEvent({ keyCode: 18 })); + expect(instance.setState).toHaveBeenCalledWith(enableLocationsNavigator); + }); + it('should handle alt+↓', () => { + instance.handleKeyDown(mockEvent({ altKey: true, keyCode: 40 })); + expect(instance.setState).toHaveBeenCalledWith(selectNextLocation); + }); + it('should handle alt+↑', () => { + instance.handleKeyDown(mockEvent({ altKey: true, keyCode: 38 })); + expect(instance.setState).toHaveBeenCalledWith(selectPreviousLocation); + }); + it('should handle alt+←', () => { + instance.handleKeyDown(mockEvent({ altKey: true, keyCode: 37 })); + expect(instance.setState).toHaveBeenCalledWith(selectPreviousFlow); + }); + it('should handle alt+→', () => { + instance.handleKeyDown(mockEvent({ altKey: true, keyCode: 39 })); + expect(instance.setState).toHaveBeenCalledWith(selectNextFlow); + }); +}); + function fetchIssuesMockFactory(keyCount = 0, lineCount = 1) { return jest.fn().mockImplementation(({ p }: any) => Promise.resolve({ diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/LocationNavigationKeyboardShortcuts-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/LocationNavigationKeyboardShortcuts-test.tsx new file mode 100644 index 00000000000..75d37689718 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/LocationNavigationKeyboardShortcuts-test.tsx @@ -0,0 +1,38 @@ +/* + * 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 LocationNavigationKeyboardShortcuts, { Props } from '../LocationNavigationKeyboardShortcuts'; +import { mockFlowLocation } from '../../../../helpers/testMocks'; + +it('should render correctly', () => { + expect(shallowRender().type()).toBeNull(); + expect(shallowRender({ issue: { flows: [], secondaryLocations: [] } }).type()).toBeNull(); + expect( + shallowRender({ issue: { flows: [], secondaryLocations: [mockFlowLocation()] } }) + ).toMatchSnapshot(); + expect( + shallowRender({ issue: { flows: [[mockFlowLocation()]], secondaryLocations: [] } }) + ).toMatchSnapshot(); +}); + +const shallowRender = (props: Partial<Props> = {}) => { + return shallow(<LocationNavigationKeyboardShortcuts issue={undefined} {...props} />); +}; diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/LocationNavigationKeyboardShortcuts-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/LocationNavigationKeyboardShortcuts-test.tsx.snap new file mode 100644 index 00000000000..e99259298a2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/LocationNavigationKeyboardShortcuts-test.tsx.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<div + className="navigation-keyboard-shortcuts big-spacer-top text-center" +> + <span> + alt + ↑ ↓ + issues.to_navigate_issue_locations + </span> +</div> +`; + +exports[`should render correctly 2`] = ` +<div + className="navigation-keyboard-shortcuts big-spacer-top text-center" +> + <span> + alt + ↑ ↓ + issues.to_navigate_issue_locations + </span> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx index be24582def8..a2f93116784 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import * as classNames from 'classnames'; import ConciseIssueLocations from './ConciseIssueLocations'; import ConciseIssueLocationsNavigator from './ConciseIssueLocationsNavigator'; -import SeverityHelper from '../../../components/shared/SeverityHelper'; +import LocationNavigationKeyboardShortcuts from '../components/LocationNavigationKeyboardShortcuts'; import TypeHelper from '../../../components/shared/TypeHelper'; interface Props { @@ -51,16 +51,16 @@ export default class ConciseIssueBox extends React.PureComponent<Props> { } } + handleClick = (event: React.MouseEvent<HTMLElement>) => { + event.preventDefault(); + this.props.onClick(this.props.issue.key); + }; + handleScroll = () => { const { selectedFlowIndex } = this.props; const { flows, secondaryLocations } = this.props.issue; - const locations = - selectedFlowIndex !== undefined - ? flows[selectedFlowIndex] - : flows.length > 0 - ? flows[0] - : secondaryLocations; + const locations = flows.length > 0 ? flows[selectedFlowIndex || 0] : secondaryLocations; if (!locations || locations.length < 15) { // if there are no locations, or there are just few @@ -72,11 +72,6 @@ export default class ConciseIssueBox extends React.PureComponent<Props> { } }; - handleClick = (event: React.MouseEvent<HTMLElement>) => { - event.preventDefault(); - this.props.onClick(this.props.issue.key); - }; - render() { const { issue, selected } = this.props; @@ -100,8 +95,7 @@ export default class ConciseIssueBox extends React.PureComponent<Props> { {issue.message} </div> <div className="concise-issue-box-attributes"> - <TypeHelper type={issue.type} /> - <SeverityHelper className="big-spacer-left" severity={issue.severity} /> + <TypeHelper className="display-block little-spacer-right" type={issue.type} /> <ConciseIssueLocations issue={issue} onFlowSelect={this.props.onFlowSelect} @@ -109,13 +103,16 @@ export default class ConciseIssueBox extends React.PureComponent<Props> { /> </div> {selected && ( - <ConciseIssueLocationsNavigator - issue={issue} - onLocationSelect={this.props.onLocationSelect} - scroll={this.props.scroll} - selectedFlowIndex={this.props.selectedFlowIndex} - selectedLocationIndex={this.props.selectedLocationIndex} - /> + <> + <ConciseIssueLocationsNavigator + issue={issue} + onLocationSelect={this.props.onLocationSelect} + scroll={this.props.scroll} + selectedFlowIndex={this.props.selectedFlowIndex} + selectedLocationIndex={this.props.selectedLocationIndex} + /> + <LocationNavigationKeyboardShortcuts issue={issue} /> + </> )} </div> ); diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocations.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocations.tsx index cb9675a30cc..ff99a7558f7 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocations.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocations.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import ConciseIssueLocationBadge from './ConciseIssueLocationBadge'; +import { Button } from '../../../components/ui/buttons'; interface Props { issue: Pick<T.Issue, 'flows' | 'secondaryLocations'>; @@ -30,21 +31,22 @@ interface State { collapsed: boolean; } -const LIMIT = 3; +const LIMIT = 8; export default class ConciseIssueLocations extends React.PureComponent<Props, State> { state: State = { collapsed: true }; - handleExpandClick = (event: React.MouseEvent<HTMLAnchorElement>) => { - event.preventDefault(); + handleExpandClick = () => { this.setState({ collapsed: false }); }; renderExpandButton() { return ( - <a className="little-spacer-left link-no-underline" href="#" onClick={this.handleExpandClick}> + <Button + className="concise-issue-expand location-index link-no-underline" + onClick={this.handleExpandClick}> ... - </a> + </Button> ); } @@ -74,13 +76,15 @@ export default class ConciseIssueLocations extends React.PureComponent<Props, St ); }); - return this.state.collapsed ? ( - <div className="concise-issue-locations pull-right"> - {badges.slice(0, LIMIT)} - {badges.length > LIMIT && this.renderExpandButton()} - </div> - ) : ( - <div className="concise-issue-locations spacer-top">{badges}</div> + if (!this.state.collapsed || badges.length <= LIMIT) { + return badges; + } + + return ( + <> + {badges.slice(0, LIMIT - 1)} + {this.renderExpandButton()} + </> ); } } diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationsNavigator.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationsNavigator.tsx index eb1b76f8356..0def805f2fa 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationsNavigator.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationsNavigator.tsx @@ -24,7 +24,7 @@ import CrossFileLocationsNavigator from './CrossFileLocationsNavigator'; import { getLocations } from '../utils'; interface Props { - issue: Pick<T.Issue, 'component' | 'key' | 'flows' | 'secondaryLocations'>; + issue: Pick<T.Issue, 'component' | 'key' | 'flows' | 'secondaryLocations' | 'type'>; onLocationSelect: (index: number) => void; scroll: (element: Element) => void; selectedFlowIndex: number | undefined; @@ -61,11 +61,13 @@ export default class ConciseIssueLocationsNavigator extends React.PureComponent< {locations.map((location, index) => ( <ConciseIssueLocationsNavigatorLocation index={index} + issueType={this.props.issue.type} key={index} message={location.msg} onClick={this.props.onLocationSelect} scroll={this.props.scroll} selected={index === this.props.selectedLocationIndex} + totalCount={locations.length} /> ))} </div> diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationsNavigatorLocation.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationsNavigatorLocation.tsx index ce2cb55cedd..af3539ecc15 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationsNavigatorLocation.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationsNavigatorLocation.tsx @@ -23,10 +23,12 @@ import LocationMessage from '../../../components/common/LocationMessage'; interface Props { index: number; + issueType: T.IssueType; message: string | undefined; onClick: (index: number) => void; scroll: (element: Element) => void; selected: boolean; + totalCount: number; } export default class ConciseIssueLocationsNavigatorLocation extends React.PureComponent<Props> { @@ -49,15 +51,32 @@ export default class ConciseIssueLocationsNavigatorLocation extends React.PureCo this.props.onClick(this.props.index); }; + prefixMessage(index: number, message = '', totalCount: number) { + switch (index) { + case 0: + return 'source: ' + message; + case totalCount - 1: + return 'sink: ' + message; + default: + return message; + } + } + render() { + const { index, issueType, message, selected, totalCount } = this.props; + return ( <div className="little-spacer-top" ref={node => (this.node = node)}> <a className="concise-issue-locations-navigator-location" href="#" onClick={this.handleClick}> - <LocationIndex selected={this.props.selected}>{this.props.index + 1}</LocationIndex> - <LocationMessage selected={this.props.selected}>{this.props.message}</LocationMessage> + <LocationIndex selected={selected}>{index + 1}</LocationIndex> + <LocationMessage selected={selected}> + {issueType === 'VULNERABILITY' + ? this.prefixMessage(index, message, totalCount) + : message} + </LocationMessage> </a> </div> ); diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/CrossFileLocationsNavigator.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/CrossFileLocationsNavigator.tsx index 912a94f1aa4..268c9f3b51a 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/CrossFileLocationsNavigator.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/CrossFileLocationsNavigator.tsx @@ -23,7 +23,7 @@ import { translateWithParameters } from '../../../helpers/l10n'; import { collapsePath } from '../../../helpers/path'; interface Props { - issue: Pick<T.Issue, 'key'>; + issue: Pick<T.Issue, 'key' | 'type'>; locations: T.FlowLocation[]; onLocationSelect: (index: number) => void; scroll: (element: Element) => void; @@ -111,11 +111,13 @@ export default class CrossFileLocationsNavigator extends React.PureComponent<Pro return ( <ConciseIssueLocationsNavigatorLocation index={index} + issueType={this.props.issue.type} key={index} message={message} onClick={this.props.onLocationSelect} scroll={this.props.scroll} selected={index === this.props.selectedLocationIndex} + totalCount={this.props.locations.length} /> ); }; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueBox-test.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueBox-test.tsx new file mode 100644 index 00000000000..b7f18bfdfc5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueBox-test.tsx @@ -0,0 +1,48 @@ +/* + * 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 ConciseIssueBox from '../ConciseIssueBox'; +import { mockIssue } from '../../../../helpers/testMocks'; +import { waitAndUpdate } from '../../../../helpers/testUtils'; + +it('should render correctly', async () => { + const wrapper = shallowRender(); + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); + + expect(shallowRender({ issue: mockIssue(true) })).toMatchSnapshot(); +}); + +const shallowRender = (props: Partial<ConciseIssueBox['props']> = {}) => { + return shallow( + <ConciseIssueBox + issue={mockIssue()} + onClick={jest.fn()} + onFlowSelect={jest.fn()} + onLocationSelect={jest.fn()} + scroll={jest.fn()} + selected={true} + selectedFlowIndex={0} + selectedLocationIndex={0} + {...props} + /> + ); +}; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationsNavigator-test.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationsNavigator-test.tsx index 0e037e9e912..28da9ead72b 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationsNavigator-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationsNavigator-test.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import ConciseIssueLocationsNavigator from '../ConciseIssueLocationsNavigator'; +import { mockIssue } from '../../../../helpers/testMocks'; const location1: T.FlowLocation = { component: 'foo', @@ -43,12 +44,12 @@ const location3: T.FlowLocation = { }; it('should render secondary locations in the same file', () => { - const issue = { + const issue = mockIssue(false, { component: 'foo', key: '', flows: [], secondaryLocations: [location1, location2] - }; + }); expect( shallow( <ConciseIssueLocationsNavigator @@ -63,12 +64,12 @@ it('should render secondary locations in the same file', () => { }); it('should render flow locations in the same file', () => { - const issue = { + const issue = mockIssue(false, { component: 'foo', key: '', flows: [[location1, location2]], secondaryLocations: [] - }; + }); expect( shallow( <ConciseIssueLocationsNavigator @@ -83,12 +84,12 @@ it('should render flow locations in the same file', () => { }); it('should render selected flow locations in the same file', () => { - const issue = { + const issue = mockIssue(false, { component: 'foo', key: '', flows: [[location1, location2]], secondaryLocations: [location1] - }; + }); expect( shallow( <ConciseIssueLocationsNavigator @@ -103,12 +104,12 @@ it('should render selected flow locations in the same file', () => { }); it('should render flow locations in different file', () => { - const issue = { + const issue = mockIssue(false, { component: 'foo', key: '', flows: [[location1, location3]], secondaryLocations: [] - }; + }); expect( shallow( <ConciseIssueLocationsNavigator @@ -123,7 +124,12 @@ it('should render flow locations in different file', () => { }); it('should not render locations', () => { - const issue = { component: 'foo', key: '', flows: [], secondaryLocations: [] }; + const issue = mockIssue(false, { + component: 'foo', + key: '', + flows: [], + secondaryLocations: [] + }); const wrapper = shallow( <ConciseIssueLocationsNavigator issue={issue} diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationsNavigatorLocation-test.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationsNavigatorLocation-test.tsx new file mode 100644 index 00000000000..e65843ee726 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationsNavigatorLocation-test.tsx @@ -0,0 +1,46 @@ +/* + * 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 ConciseIssueLocationsNavigatorLocation from '../ConciseIssueLocationsNavigatorLocation'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); +}); +it('should render vulnerabilities correctly', () => { + expect(shallowRender({ index: 0, issueType: 'VULNERABILITY', totalCount: 4 })).toMatchSnapshot(); + expect(shallowRender({ index: 1, issueType: 'VULNERABILITY', totalCount: 4 })).toMatchSnapshot(); + expect(shallowRender({ index: 3, issueType: 'VULNERABILITY', totalCount: 4 })).toMatchSnapshot(); +}); + +const shallowRender = (props: Partial<ConciseIssueLocationsNavigatorLocation['props']> = {}) => { + return shallow( + <ConciseIssueLocationsNavigatorLocation + index={0} + issueType={'BUG'} + message={''} + onClick={jest.fn()} + scroll={jest.fn()} + selected={true} + totalCount={5} + {...props} + /> + ); +}; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/CrossFileLocationsNavigator-test.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/CrossFileLocationsNavigator-test.tsx index bf4e4f9a698..2705d36ae5f 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/CrossFileLocationsNavigator-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/CrossFileLocationsNavigator-test.tsx @@ -46,7 +46,7 @@ const location3: T.FlowLocation = { it('should render', () => { const wrapper = shallow( <CrossFileLocationsNavigator - issue={{ key: 'abcd' }} + issue={{ key: 'abcd', type: 'BUG' }} locations={[location1, location2, location3]} onLocationSelect={jest.fn()} scroll={jest.fn()} @@ -63,7 +63,7 @@ it('should render', () => { it('should render all locations', () => { const wrapper = shallow( <CrossFileLocationsNavigator - issue={{ key: 'abcd' }} + issue={{ key: 'abcd', type: 'BUG' }} locations={[location1, location2]} onLocationSelect={jest.fn()} scroll={jest.fn()} @@ -76,7 +76,7 @@ it('should render all locations', () => { it('should expand all locations', () => { const wrapper = shallow( <CrossFileLocationsNavigator - issue={{ key: 'abcd' }} + issue={{ key: 'abcd', type: 'BUG' }} locations={[location1, location2, location3]} onLocationSelect={jest.fn()} scroll={jest.fn()} @@ -92,7 +92,7 @@ it('should expand all locations', () => { it('should collapse locations when issue changes', () => { const wrapper = shallow( <CrossFileLocationsNavigator - issue={{ key: 'abcd' }} + issue={{ key: 'abcd', type: 'BUG' }} locations={[location1, location2, location3]} onLocationSelect={jest.fn()} scroll={jest.fn()} diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap new file mode 100644 index 00000000000..4acba10db82 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap @@ -0,0 +1,476 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<div + className="concise-issue-box clearfix selected" +> + <div + className="concise-issue-box-message" + onClick={[Function]} + role="listitem" + tabIndex={0} + > + Reduce the number of conditional operators (4) used in the expression + </div> + <div + className="concise-issue-box-attributes" + > + <TypeHelper + className="display-block little-spacer-right" + type="BUG" + /> + <ConciseIssueLocations + issue={ + Object { + "actions": Array [], + "component": "main.js", + "componentLongName": "main.js", + "componentQualifier": "FIL", + "componentUuid": "foo1234", + "creationDate": "2017-03-01T09:36:01+0100", + "flows": Array [], + "fromHotspot": false, + "key": "AVsae-CQS-9G3txfbFN2", + "line": 25, + "message": "Reduce the number of conditional operators (4) used in the expression", + "organization": "myorg", + "project": "myproject", + "projectKey": "foo", + "projectName": "Foo", + "projectOrganization": "org", + "rule": "javascript:S1067", + "ruleName": "foo", + "secondaryLocations": Array [], + "severity": "MAJOR", + "status": "OPEN", + "textRange": Object { + "endLine": 26, + "endOffset": 15, + "startLine": 25, + "startOffset": 0, + }, + "transitions": Array [], + "type": "BUG", + } + } + onFlowSelect={[MockFunction]} + selectedFlowIndex={0} + /> + </div> + <ConciseIssueLocationsNavigator + issue={ + Object { + "actions": Array [], + "component": "main.js", + "componentLongName": "main.js", + "componentQualifier": "FIL", + "componentUuid": "foo1234", + "creationDate": "2017-03-01T09:36:01+0100", + "flows": Array [], + "fromHotspot": false, + "key": "AVsae-CQS-9G3txfbFN2", + "line": 25, + "message": "Reduce the number of conditional operators (4) used in the expression", + "organization": "myorg", + "project": "myproject", + "projectKey": "foo", + "projectName": "Foo", + "projectOrganization": "org", + "rule": "javascript:S1067", + "ruleName": "foo", + "secondaryLocations": Array [], + "severity": "MAJOR", + "status": "OPEN", + "textRange": Object { + "endLine": 26, + "endOffset": 15, + "startLine": 25, + "startOffset": 0, + }, + "transitions": Array [], + "type": "BUG", + } + } + onLocationSelect={[MockFunction]} + scroll={[MockFunction]} + selectedFlowIndex={0} + selectedLocationIndex={0} + /> + <LocationNavigationKeyboardShortcuts + issue={ + Object { + "actions": Array [], + "component": "main.js", + "componentLongName": "main.js", + "componentQualifier": "FIL", + "componentUuid": "foo1234", + "creationDate": "2017-03-01T09:36:01+0100", + "flows": Array [], + "fromHotspot": false, + "key": "AVsae-CQS-9G3txfbFN2", + "line": 25, + "message": "Reduce the number of conditional operators (4) used in the expression", + "organization": "myorg", + "project": "myproject", + "projectKey": "foo", + "projectName": "Foo", + "projectOrganization": "org", + "rule": "javascript:S1067", + "ruleName": "foo", + "secondaryLocations": Array [], + "severity": "MAJOR", + "status": "OPEN", + "textRange": Object { + "endLine": 26, + "endOffset": 15, + "startLine": 25, + "startOffset": 0, + }, + "transitions": Array [], + "type": "BUG", + } + } + /> +</div> +`; + +exports[`should render correctly 2`] = ` +<div + className="concise-issue-box clearfix selected" +> + <div + className="concise-issue-box-message" + onClick={[Function]} + role="listitem" + tabIndex={0} + > + Reduce the number of conditional operators (4) used in the expression + </div> + <div + className="concise-issue-box-attributes" + > + <TypeHelper + className="display-block little-spacer-right" + type="BUG" + /> + <ConciseIssueLocations + issue={ + Object { + "actions": Array [], + "component": "main.js", + "componentLongName": "main.js", + "componentQualifier": "FIL", + "componentUuid": "foo1234", + "creationDate": "2017-03-01T09:36:01+0100", + "flows": Array [ + Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + ], + "fromHotspot": false, + "key": "AVsae-CQS-9G3txfbFN2", + "line": 25, + "message": "Reduce the number of conditional operators (4) used in the expression", + "organization": "myorg", + "project": "myproject", + "projectKey": "foo", + "projectName": "Foo", + "projectOrganization": "org", + "rule": "javascript:S1067", + "ruleName": "foo", + "secondaryLocations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + "severity": "MAJOR", + "status": "OPEN", + "textRange": Object { + "endLine": 26, + "endOffset": 15, + "startLine": 25, + "startOffset": 0, + }, + "transitions": Array [], + "type": "BUG", + } + } + onFlowSelect={[MockFunction]} + selectedFlowIndex={0} + /> + </div> + <ConciseIssueLocationsNavigator + issue={ + Object { + "actions": Array [], + "component": "main.js", + "componentLongName": "main.js", + "componentQualifier": "FIL", + "componentUuid": "foo1234", + "creationDate": "2017-03-01T09:36:01+0100", + "flows": Array [ + Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + ], + "fromHotspot": false, + "key": "AVsae-CQS-9G3txfbFN2", + "line": 25, + "message": "Reduce the number of conditional operators (4) used in the expression", + "organization": "myorg", + "project": "myproject", + "projectKey": "foo", + "projectName": "Foo", + "projectOrganization": "org", + "rule": "javascript:S1067", + "ruleName": "foo", + "secondaryLocations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + "severity": "MAJOR", + "status": "OPEN", + "textRange": Object { + "endLine": 26, + "endOffset": 15, + "startLine": 25, + "startOffset": 0, + }, + "transitions": Array [], + "type": "BUG", + } + } + onLocationSelect={[MockFunction]} + scroll={[MockFunction]} + selectedFlowIndex={0} + selectedLocationIndex={0} + /> + <LocationNavigationKeyboardShortcuts + issue={ + Object { + "actions": Array [], + "component": "main.js", + "componentLongName": "main.js", + "componentQualifier": "FIL", + "componentUuid": "foo1234", + "creationDate": "2017-03-01T09:36:01+0100", + "flows": Array [ + Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + ], + "fromHotspot": false, + "key": "AVsae-CQS-9G3txfbFN2", + "line": 25, + "message": "Reduce the number of conditional operators (4) used in the expression", + "organization": "myorg", + "project": "myproject", + "projectKey": "foo", + "projectName": "Foo", + "projectOrganization": "org", + "rule": "javascript:S1067", + "ruleName": "foo", + "secondaryLocations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + "severity": "MAJOR", + "status": "OPEN", + "textRange": Object { + "endLine": 26, + "endOffset": 15, + "startLine": 25, + "startOffset": 0, + }, + "transitions": Array [], + "type": "BUG", + } + } + /> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocations-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocations-test.tsx.snap index f9973b06e0c..cd78e9dfdaf 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocations-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocations-test.tsx.snap @@ -1,51 +1,41 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should render one flow 1`] = ` -<div - className="concise-issue-locations pull-right" -> - <ConciseIssueLocationBadge - count={3} - key="0" - onClick={[Function]} - selected={false} - /> -</div> +<ConciseIssueLocationBadge + count={3} + key="0" + onClick={[Function]} + selected={false} +/> `; exports[`should render secondary locations 1`] = ` -<div - className="concise-issue-locations pull-right" -> - <ConciseIssueLocationBadge - count={3} - key="-1" - selected={true} - /> -</div> +<ConciseIssueLocationBadge + count={3} + key="-1" + selected={true} +/> `; exports[`should render several flows 1`] = ` -<div - className="concise-issue-locations pull-right" -> +Array [ <ConciseIssueLocationBadge count={3} key="0" onClick={[Function]} selected={false} - /> + />, <ConciseIssueLocationBadge count={2} key="1" onClick={[Function]} selected={false} - /> + />, <ConciseIssueLocationBadge count={3} key="2" onClick={[Function]} selected={false} - /> -</div> + />, +] `; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationsNavigator-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationsNavigator-test.tsx.snap index f2512176f96..6a9dc041d25 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationsNavigator-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationsNavigator-test.tsx.snap @@ -4,7 +4,12 @@ exports[`should render flow locations in different file 1`] = ` <CrossFileLocationsNavigator issue={ Object { + "actions": Array [], "component": "foo", + "componentLongName": "main.js", + "componentQualifier": "FIL", + "componentUuid": "foo1234", + "creationDate": "2017-03-01T09:36:01+0100", "flows": Array [ Array [ Object { @@ -31,8 +36,28 @@ exports[`should render flow locations in different file 1`] = ` }, ], ], + "fromHotspot": false, "key": "", + "line": 25, + "message": "Reduce the number of conditional operators (4) used in the expression", + "organization": "myorg", + "project": "myproject", + "projectKey": "foo", + "projectName": "Foo", + "projectOrganization": "org", + "rule": "javascript:S1067", + "ruleName": "foo", "secondaryLocations": Array [], + "severity": "MAJOR", + "status": "OPEN", + "textRange": Object { + "endLine": 26, + "endOffset": 15, + "startLine": 25, + "startOffset": 0, + }, + "transitions": Array [], + "type": "BUG", } } locations={ @@ -72,19 +97,23 @@ exports[`should render flow locations in the same file 1`] = ` > <ConciseIssueLocationsNavigatorLocation index={0} + issueType="BUG" key="0" message="Do not use foo" onClick={[MockFunction]} scroll={[MockFunction]} selected={false} + totalCount={2} /> <ConciseIssueLocationsNavigatorLocation index={1} + issueType="BUG" key="1" message="Do not use foo" onClick={[MockFunction]} scroll={[MockFunction]} selected={false} + totalCount={2} /> </div> `; @@ -95,19 +124,23 @@ exports[`should render secondary locations in the same file 1`] = ` > <ConciseIssueLocationsNavigatorLocation index={0} + issueType="BUG" key="0" message="Do not use foo" onClick={[MockFunction]} scroll={[MockFunction]} selected={false} + totalCount={2} /> <ConciseIssueLocationsNavigatorLocation index={1} + issueType="BUG" key="1" message="Do not use foo" onClick={[MockFunction]} scroll={[MockFunction]} selected={false} + totalCount={2} /> </div> `; @@ -118,19 +151,23 @@ exports[`should render selected flow locations in the same file 1`] = ` > <ConciseIssueLocationsNavigatorLocation index={0} + issueType="BUG" key="0" message="Do not use foo" onClick={[MockFunction]} scroll={[MockFunction]} selected={false} + totalCount={2} /> <ConciseIssueLocationsNavigatorLocation index={1} + issueType="BUG" key="1" message="Do not use foo" onClick={[MockFunction]} scroll={[MockFunction]} selected={false} + totalCount={2} /> </div> `; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationsNavigatorLocation-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationsNavigatorLocation-test.tsx.snap new file mode 100644 index 00000000000..9ca42296767 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationsNavigatorLocation-test.tsx.snap @@ -0,0 +1,89 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<div + className="little-spacer-top" +> + <a + className="concise-issue-locations-navigator-location" + href="#" + onClick={[Function]} + > + <LocationIndex + selected={true} + > + 1 + </LocationIndex> + <LocationMessage + selected={true} + /> + </a> +</div> +`; + +exports[`should render vulnerabilities correctly 1`] = ` +<div + className="little-spacer-top" +> + <a + className="concise-issue-locations-navigator-location" + href="#" + onClick={[Function]} + > + <LocationIndex + selected={true} + > + 1 + </LocationIndex> + <LocationMessage + selected={true} + > + source: + </LocationMessage> + </a> +</div> +`; + +exports[`should render vulnerabilities correctly 2`] = ` +<div + className="little-spacer-top" +> + <a + className="concise-issue-locations-navigator-location" + href="#" + onClick={[Function]} + > + <LocationIndex + selected={true} + > + 2 + </LocationIndex> + <LocationMessage + selected={true} + /> + </a> +</div> +`; + +exports[`should render vulnerabilities correctly 3`] = ` +<div + className="little-spacer-top" +> + <a + className="concise-issue-locations-navigator-location" + href="#" + onClick={[Function]} + > + <LocationIndex + selected={true} + > + 4 + </LocationIndex> + <LocationMessage + selected={true} + > + sink: + </LocationMessage> + </a> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/CrossFileLocationsNavigator-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/CrossFileLocationsNavigator-test.tsx.snap index e1b734ee3e5..86cb38f4c5f 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/CrossFileLocationsNavigator-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/CrossFileLocationsNavigator-test.tsx.snap @@ -21,11 +21,13 @@ exports[`should render 1`] = ` > <ConciseIssueLocationsNavigatorLocation index={0} + issueType="BUG" key="0" message="Do not use foo" onClick={[MockFunction]} scroll={[MockFunction]} selected={false} + totalCount={3} /> </div> </div> @@ -64,11 +66,13 @@ exports[`should render 1`] = ` > <ConciseIssueLocationsNavigatorLocation index={2} + issueType="BUG" key="2" message="Do not use bar" onClick={[MockFunction]} scroll={[MockFunction]} selected={false} + totalCount={3} /> </div> </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 a6687a9e5b0..1f2dba6d6a5 100644 --- a/server/sonar-web/src/main/js/apps/issues/styles.css +++ b/server/sonar-web/src/main/js/apps/issues/styles.css @@ -99,6 +99,10 @@ margin-top: var(--gridSize); line-height: 16px; font-size: var(--smallFontSize); + display: flex; + align-items: flex-start; + flex-wrap: wrap; + justify-content: flex-start; } .concise-issue-box:not(.selected) .location-index { @@ -106,13 +110,30 @@ } .concise-issue-locations { - margin-right: -4px; + display: inline-block; margin-bottom: -4px; + margin-left: var(--gridSize); } -.concise-issue-locations .location-index { - margin-right: 4px; +.concise-issue-box-attributes > .location-index { margin-bottom: 4px; + margin-right: 4px; +} + +.concise-issue-box-attributes > .concise-issue-expand { + background-color: transparent; + border: 1px solid var(--conciseIssueRed); + height: 16px; + color: var(--conciseIssueRed); + font-weight: bold; + font-size: 16px; + line-height: 8px; + padding-bottom: 6px; +} + +.concise-issue-box-attributes > .concise-issue-expand:hover { + background-color: var(--conciseIssueRed); + color: white; } .concise-issue-locations-navigator-location { @@ -139,7 +160,7 @@ top: 13px; bottom: calc(-2 * var(--gridSize)); left: 4px; - border-left: 1px dotted #d18582; + border-left: 1px dotted var(--conciseIssueRed); content: ''; } @@ -162,7 +183,7 @@ display: inline-block; width: calc(1px + var(--gridSize)); height: calc(1px + var(--gridSize)); - border: 1px solid #d18582; + border: 1px solid var(--conciseIssueRed); border-radius: 100%; box-sizing: border-box; background-color: var(--issueBgColor); @@ -278,3 +299,12 @@ .bulk-change-radio-button:hover { background-color: var(--barBackgroundColor); } + +.navigation-keyboard-shortcuts > span { + background-color: var(--transparentGray); + border-radius: 16px; + display: inline-block; + font-size: var(--smallFontSize); + height: 16px; + padding: calc(var(--gridSize) / 2) var(--gridSize); +} diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts index dd21ec1fec2..cc30abeb85e 100644 --- a/server/sonar-web/src/main/js/helpers/testMocks.ts +++ b/server/sonar-web/src/main/js/helpers/testMocks.ts @@ -211,12 +211,7 @@ export function mockIssue(withLocations = false, overrides: Partial<T.Issue> = { type: 'BUG' }; - function loc(): T.FlowLocation { - return { - component: 'main.js', - textRange: { startLine: 1, startOffset: 1, endLine: 2, endOffset: 2 } - }; - } + const loc = mockFlowLocation; if (withLocations) { issue.flows = [[loc(), loc(), loc()], [loc(), loc()]]; @@ -517,3 +512,16 @@ export function mockLanguage(overrides: Partial<T.Language> = {}): T.Language { ...overrides }; } + +export function mockFlowLocation(overrides: Partial<T.FlowLocation> = {}): T.FlowLocation { + return { + component: 'main.js', + textRange: { + startLine: 1, + startOffset: 1, + endLine: 2, + endOffset: 2 + }, + ...overrides + }; +} |