From 570316af531be1479cb7fa504c9ba9777babbd03 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Wed, 3 Apr 2019 14:07:18 +0200 Subject: SONAR-11897 Increase sidebar issue box usability --- server/sonar-web/src/main/js/app/theme.js | 3 + .../src/main/js/apps/issues/components/App.tsx | 24 -- .../LocationNavigationKeyboardShortcuts.tsx | 40 ++ .../apps/issues/components/__tests__/App-test.tsx | 41 +- .../LocationNavigationKeyboardShortcuts-test.tsx | 38 ++ ...cationNavigationKeyboardShortcuts-test.tsx.snap | 23 + .../issues/conciseIssuesList/ConciseIssueBox.tsx | 39 +- .../conciseIssuesList/ConciseIssueLocations.tsx | 28 +- .../ConciseIssueLocationsNavigator.tsx | 4 +- .../ConciseIssueLocationsNavigatorLocation.tsx | 23 +- .../CrossFileLocationsNavigator.tsx | 4 +- .../__tests__/ConciseIssueBox-test.tsx | 48 +++ .../ConciseIssueLocationsNavigator-test.tsx | 24 +- ...ConciseIssueLocationsNavigatorLocation-test.tsx | 46 ++ .../__tests__/CrossFileLocationsNavigator-test.tsx | 8 +- .../__snapshots__/ConciseIssueBox-test.tsx.snap | 476 +++++++++++++++++++++ .../ConciseIssueLocations-test.tsx.snap | 42 +- .../ConciseIssueLocationsNavigator-test.tsx.snap | 37 ++ ...seIssueLocationsNavigatorLocation-test.tsx.snap | 89 ++++ .../CrossFileLocationsNavigator-test.tsx.snap | 4 + .../sonar-web/src/main/js/apps/issues/styles.css | 40 +- server/sonar-web/src/main/js/helpers/testMocks.ts | 20 +- 22 files changed, 989 insertions(+), 112 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/issues/components/LocationNavigationKeyboardShortcuts.tsx create mode 100644 server/sonar-web/src/main/js/apps/issues/components/__tests__/LocationNavigationKeyboardShortcuts-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/LocationNavigationKeyboardShortcuts-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueBox-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationsNavigatorLocation-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationsNavigatorLocation-test.tsx.snap (limited to 'server/sonar-web/src') 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 { ); } - renderShortcutsForLocations() { - const { openIssue } = this.state; - if (!openIssue || (!openIssue.secondaryLocations.length && !openIssue.flows.length)) { - return null; - } - const hasSeveralFlows = openIssue.flows.length > 1; - return ( -
- alt - {'+'} - - - {hasSeveralFlows && ( - - - - - )} - {translate('issues.to_navigate_issue_locations')} -
- ); - } - renderPage() { const { checkAll, loading, openIssue, paging } = this.state; return ( @@ -1134,7 +1111,6 @@ export class App extends React.PureComponent { selectedIndex={selectedIndex} /> )} - {this.renderShortcutsForLocations()} 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 | undefined; +} + +export default function LocationNavigationKeyboardShortcuts({ issue }: Props) { + if (!issue || (!issue.secondaryLocations.length && !issue.flows.length)) { + return null; + } + const hasSeveralFlows = issue.flows.length > 1; + return ( +
+ + alt + ↑ ↓ {hasSeveralFlows && <>←→} + {translate('issues.to_navigate_issue_locations')} + +
+ ); +} 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 = {}) => { + return shallow(); +}; 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`] = ` +
+ + alt + ↑ ↓ + issues.to_navigate_issue_locations + +
+`; + +exports[`should render correctly 2`] = ` +
+ + alt + ↑ ↓ + issues.to_navigate_issue_locations + +
+`; 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 { } } + handleClick = (event: React.MouseEvent) => { + 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 { } }; - handleClick = (event: React.MouseEvent) => { - 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 { {issue.message}
- - + { />
{selected && ( - + <> + + + )} ); 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; @@ -30,21 +31,22 @@ interface State { collapsed: boolean; } -const LIMIT = 3; +const LIMIT = 8; export default class ConciseIssueLocations extends React.PureComponent { state: State = { collapsed: true }; - handleExpandClick = (event: React.MouseEvent) => { - event.preventDefault(); + handleExpandClick = () => { this.setState({ collapsed: false }); }; renderExpandButton() { return ( - + ); } @@ -74,13 +76,15 @@ export default class ConciseIssueLocations extends React.PureComponent - {badges.slice(0, LIMIT)} - {badges.length > LIMIT && this.renderExpandButton()} - - ) : ( -
{badges}
+ 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; + issue: Pick; 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) => ( ))} 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 { @@ -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 ( ); 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; + issue: Pick; locations: T.FlowLocation[]; onLocationSelect: (index: number) => void; scroll: (element: Element) => void; @@ -111,11 +111,13 @@ export default class CrossFileLocationsNavigator extends React.PureComponent ); }; 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 = {}) => { + return shallow( + + ); +}; 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( { }); it('should render flow locations in the same file', () => { - const issue = { + const issue = mockIssue(false, { component: 'foo', key: '', flows: [[location1, location2]], secondaryLocations: [] - }; + }); expect( shallow( { }); 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( { }); it('should render flow locations in different file', () => { - const issue = { + const issue = mockIssue(false, { component: 'foo', key: '', flows: [[location1, location3]], secondaryLocations: [] - }; + }); expect( shallow( { }); it('should not render locations', () => { - const issue = { component: 'foo', key: '', flows: [], secondaryLocations: [] }; + const issue = mockIssue(false, { + component: 'foo', + key: '', + flows: [], + secondaryLocations: [] + }); const wrapper = shallow( { + 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 = {}) => { + return shallow( + + ); +}; 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( { it('should render all locations', () => { const wrapper = shallow( { it('should expand all locations', () => { const wrapper = shallow( { it('should collapse locations when issue changes', () => { const wrapper = shallow( +
+ Reduce the number of conditional operators (4) used in the expression +
+
+ + +
+ + + +`; + +exports[`should render correctly 2`] = ` +
+
+ Reduce the number of conditional operators (4) used in the expression +
+
+ + +
+ + +
+`; 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`] = ` -
- -
+ `; exports[`should render secondary locations 1`] = ` -
- -
+ `; exports[`should render several flows 1`] = ` -
+Array [ + />, + />, -
+ />, +] `; 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`] = ` `; @@ -95,19 +124,23 @@ exports[`should render secondary locations in the same file 1`] = ` > `; @@ -118,19 +151,23 @@ exports[`should render selected flow locations in the same file 1`] = ` > `; 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`] = ` + +`; + +exports[`should render vulnerabilities correctly 1`] = ` + +`; + +exports[`should render vulnerabilities correctly 2`] = ` + +`; + +exports[`should render vulnerabilities correctly 3`] = ` + +`; 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`] = ` > @@ -64,11 +66,13 @@ exports[`should render 1`] = ` > 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 = { 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 { ...overrides }; } + +export function mockFlowLocation(overrides: Partial = {}): T.FlowLocation { + return { + component: 'main.js', + textRange: { + startLine: 1, + startOffset: 1, + endLine: 2, + endOffset: 2 + }, + ...overrides + }; +} -- cgit v1.2.3