Procházet zdrojové kódy

SONAR-13990 Improve Hotspot status change button

tags/9.1.0.47736
Jeremy Davis před 2 roky
rodič
revize
d21c92c464

+ 2
- 2
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx Zobrazit soubor

@@ -113,7 +113,7 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) {
</div>
</div>

<div className="huge-spacer-bottom display-flex-row">
<div className="huge-spacer-bottom display-flex-row display-flex-space-between">
<div className="hotspot-information display-flex-column display-flex-space-between">
<div className="display-flex-center">
<span className="big-spacer-right">{translate('category')}</span>
@@ -138,7 +138,7 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) {
</div>
</div>
</div>
<div className="huge-spacer-left">
<div className="huge-spacer-left abs-width-400">
<Status hotspot={hotspot} onStatusChange={() => props.onUpdateHotspot(true)} />
</div>
</div>

+ 10
- 10
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap Zobrazit soubor

@@ -79,7 +79,7 @@ exports[`should render correctly 1`] = `
</div>
</div>
<div
className="huge-spacer-bottom display-flex-row"
className="huge-spacer-bottom display-flex-row display-flex-space-between"
>
<div
className="hotspot-information display-flex-column display-flex-space-between"
@@ -199,7 +199,7 @@ exports[`should render correctly 1`] = `
</div>
</div>
<div
className="huge-spacer-left"
className="huge-spacer-left abs-width-400"
>
<Connect(withCurrentUser(Status))
hotspot={
@@ -615,7 +615,7 @@ exports[`should render correctly: anonymous user 1`] = `
</div>
</div>
<div
className="huge-spacer-bottom display-flex-row"
className="huge-spacer-bottom display-flex-row display-flex-space-between"
>
<div
className="hotspot-information display-flex-column display-flex-space-between"
@@ -735,7 +735,7 @@ exports[`should render correctly: anonymous user 1`] = `
</div>
</div>
<div
className="huge-spacer-left"
className="huge-spacer-left abs-width-400"
>
<Connect(withCurrentUser(Status))
hotspot={
@@ -1151,7 +1151,7 @@ exports[`should render correctly: assignee without name 1`] = `
</div>
</div>
<div
className="huge-spacer-bottom display-flex-row"
className="huge-spacer-bottom display-flex-row display-flex-space-between"
>
<div
className="hotspot-information display-flex-column display-flex-space-between"
@@ -1271,7 +1271,7 @@ exports[`should render correctly: assignee without name 1`] = `
</div>
</div>
<div
className="huge-spacer-left"
className="huge-spacer-left abs-width-400"
>
<Connect(withCurrentUser(Status))
hotspot={
@@ -1687,7 +1687,7 @@ exports[`should render correctly: deleted assignee 1`] = `
</div>
</div>
<div
className="huge-spacer-bottom display-flex-row"
className="huge-spacer-bottom display-flex-row display-flex-space-between"
>
<div
className="hotspot-information display-flex-column display-flex-space-between"
@@ -1807,7 +1807,7 @@ exports[`should render correctly: deleted assignee 1`] = `
</div>
</div>
<div
className="huge-spacer-left"
className="huge-spacer-left abs-width-400"
>
<Connect(withCurrentUser(Status))
hotspot={
@@ -2230,7 +2230,7 @@ exports[`should render correctly: unassigned 1`] = `
</div>
</div>
<div
className="huge-spacer-bottom display-flex-row"
className="huge-spacer-bottom display-flex-row display-flex-space-between"
>
<div
className="hotspot-information display-flex-column display-flex-space-between"
@@ -2350,7 +2350,7 @@ exports[`should render correctly: unassigned 1`] = `
</div>
</div>
<div
className="huge-spacer-left"
className="huge-spacer-left abs-width-400"
>
<Connect(withCurrentUser(Status))
hotspot={

+ 0
- 43
server/sonar-web/src/main/js/apps/security-hotspots/components/status/Status.css Zobrazit soubor

@@ -1,43 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2021 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.
*/
#status-trigger,
#status-trigger + .popup {
width: 400px;
box-sizing: border-box;
}

#status-trigger {
height: 80px;
border-radius: 4px;
outline: none;
}

#status-trigger.readonly {
cursor: not-allowed;
}

#status-trigger:not(.readonly) {
cursor: pointer;
background-color: var(--darkBlue);
}

#status-trigger:not(.readonly) * {
color: white;
}

+ 36
- 52
server/sonar-web/src/main/js/apps/security-hotspots/components/status/Status.tsx Zobrazit soubor

@@ -19,17 +19,17 @@
*/
import * as classNames from 'classnames';
import * as React from 'react';
import { Button } from 'sonar-ui-common/components/controls/buttons';
import { DropdownOverlay } from 'sonar-ui-common/components/controls/Dropdown';
import Toggler from 'sonar-ui-common/components/controls/Toggler';
import Tooltip from 'sonar-ui-common/components/controls/Tooltip';
import ChevronDownIcon from 'sonar-ui-common/components/icons/ChevronDownIcon';
import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon';
import { PopupPlacement } from 'sonar-ui-common/components/ui/popups';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { withCurrentUser } from '../../../../components/hoc/withCurrentUser';
import { isLoggedIn } from '../../../../helpers/users';
import { Hotspot } from '../../../../types/security-hotspots';
import { getStatusOptionFromStatusAndResolution } from '../../utils';
import './Status.css';
import StatusDescription from './StatusDescription';
import StatusSelection from './StatusSelection';

@@ -47,58 +47,42 @@ export function Status(props: StatusProps) {
const statusOption = getStatusOptionFromStatusAndResolution(hotspot.status, hotspot.resolution);
const readonly = !hotspot.canChangeStatus || !isLoggedIn(currentUser);

const trigger = (
<div
aria-expanded={isOpen}
aria-haspopup={true}
className={classNames('padded bordered display-flex-column display-flex-justify-center', {
readonly
})}
id="status-trigger"
onClick={() => !readonly && setIsOpen(true)}
role="button"
tabIndex={0}>
<div className="display-flex-center display-flex-space-between">
{isOpen ? (
<span className="h3">{translate('hotspots.status.select_status')}</span>
) : (
<StatusDescription showTitle={true} statusOption={statusOption} />
)}
{!readonly && <ChevronDownIcon className="big-spacer-left" />}
</div>
</div>
);

const actionableTrigger = (
<Toggler
closeOnClickOutside={true}
closeOnEscape={true}
onRequestClose={() => setIsOpen(false)}
open={isOpen}
overlay={
<DropdownOverlay noPadding={true} placement={PopupPlacement.Bottom}>
<StatusSelection
hotspot={hotspot}
onStatusOptionChange={async () => {
await props.onStatusChange();
setIsOpen(false);
}}
/>
</DropdownOverlay>
}>
{trigger}
</Toggler>
);

return (
<div className="dropdown">
{readonly ? (
<Tooltip overlay={translate('hotspots.status.cannot_change_status')} placement="bottom">
{trigger}
<div>
<StatusDescription showTitle={true} statusOption={statusOption} />
<div className="spacer-top">
<Tooltip
overlay={readonly ? translate('hotspots.status.cannot_change_status') : null}
placement="bottom">
<div className="dropdown display-inline-block">
<Toggler
closeOnClickOutside={true}
closeOnEscape={true}
onRequestClose={() => setIsOpen(false)}
open={isOpen}
overlay={
<DropdownOverlay noPadding={true} placement={PopupPlacement.Bottom}>
<StatusSelection
hotspot={hotspot}
onStatusOptionChange={async () => {
await props.onStatusChange();
setIsOpen(false);
}}
/>
</DropdownOverlay>
}>
<Button
className={classNames('dropdown-toggle')}
id="status-trigger"
onClick={() => setIsOpen(true)}
disabled={readonly}>
<span>{translate('hotspots.status.select_status')}</span>
<DropdownIcon className="little-spacer-left" />
</Button>
</Toggler>
</div>
</Tooltip>
) : (
actionableTrigger
)}
</div>
</div>
);
}

+ 2
- 2
server/sonar-web/src/main/js/apps/security-hotspots/components/status/StatusSelectionRenderer.tsx Zobrazit soubor

@@ -54,7 +54,7 @@ export default function StatusSelectionRenderer(props: StatusSelectionRendererPr
};

return (
<>
<div className="abs-width-400">
<div className="big-padded">
{renderOption(HotspotStatusOption.TO_REVIEW)}
{renderOption(HotspotStatusOption.FIXED)}
@@ -85,6 +85,6 @@ export default function StatusSelectionRenderer(props: StatusSelectionRendererPr
{loading && <i className="spacer-left spinner" />}
</div>
</div>
</>
</div>
);
}

+ 341
- 235
server/sonar-web/src/main/js/apps/security-hotspots/components/status/__tests__/__snapshots__/Status-test.tsx.snap Zobrazit soubor

@@ -1,264 +1,370 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: closed 1`] = `
<div
className="dropdown"
>
<Toggler
closeOnClickOutside={true}
closeOnEscape={true}
onRequestClose={[Function]}
open={false}
overlay={
<DropdownOverlay
noPadding={true}
placement="bottom"
>
<StatusSelection
hotspot={
Object {
"assignee": "assignee",
"assigneeUser": Object {
"active": true,
"local": true,
"login": "assignee",
"name": "John Doe",
},
"author": "author",
"authorUser": Object {
"active": true,
"local": true,
"login": "author",
"name": "John Doe",
},
"canChangeStatus": true,
"changelog": Array [],
"comment": Array [],
"component": Object {
"key": "hotspot-component",
"longName": "Hotspot component long name",
"name": "Hotspot Component",
"path": "path/to/component",
"qualifier": "FIL",
},
"creationDate": "2013-05-13T17:55:41+0200",
"key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123",
"line": 142,
"message": "'3' is a magic number.",
"project": Object {
"key": "hotspot-component",
"longName": "Hotspot component long name",
"name": "Hotspot Component",
"path": "path/to/component",
"qualifier": "TRK",
},
"resolution": "FIXED",
"rule": Object {
"fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>",
"key": "squid:S2077",
"name": "That rule",
"riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>",
"securityCategory": "sql-injection",
"vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
"vulnerabilityProbability": "HIGH",
},
"status": "REVIEWED",
"textRange": Object {
"endLine": 142,
"endOffset": 83,
"startLine": 142,
"startOffset": 26,
},
"updateDate": "2013-05-13T17:55:42+0200",
"users": Array [
Object {
"active": true,
"local": true,
"login": "assignee",
"name": "John Doe",
},
Object {
"active": true,
"local": true,
"login": "author",
"name": "John Doe",
},
],
}
}
onStatusOptionChange={[Function]}
/>
</DropdownOverlay>
}
<div>
<StatusDescription
showTitle={true}
statusOption="FIXED"
/>
<div
className="spacer-top"
>
<div
aria-expanded={false}
aria-haspopup={true}
className="padded bordered display-flex-column display-flex-justify-center"
id="status-trigger"
onClick={[Function]}
role="button"
tabIndex={0}
<Tooltip
overlay={null}
placement="bottom"
>
<div
className="display-flex-center display-flex-space-between"
className="dropdown display-inline-block"
>
<StatusDescription
showTitle={true}
statusOption="FIXED"
/>
<ChevronDownIcon
className="big-spacer-left"
/>
<Toggler
closeOnClickOutside={true}
closeOnEscape={true}
onRequestClose={[Function]}
open={false}
overlay={
<DropdownOverlay
noPadding={true}
placement="bottom"
>
<StatusSelection
hotspot={
Object {
"assignee": "assignee",
"assigneeUser": Object {
"active": true,
"local": true,
"login": "assignee",
"name": "John Doe",
},
"author": "author",
"authorUser": Object {
"active": true,
"local": true,
"login": "author",
"name": "John Doe",
},
"canChangeStatus": true,
"changelog": Array [],
"comment": Array [],
"component": Object {
"key": "hotspot-component",
"longName": "Hotspot component long name",
"name": "Hotspot Component",
"path": "path/to/component",
"qualifier": "FIL",
},
"creationDate": "2013-05-13T17:55:41+0200",
"key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123",
"line": 142,
"message": "'3' is a magic number.",
"project": Object {
"key": "hotspot-component",
"longName": "Hotspot component long name",
"name": "Hotspot Component",
"path": "path/to/component",
"qualifier": "TRK",
},
"resolution": "FIXED",
"rule": Object {
"fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>",
"key": "squid:S2077",
"name": "That rule",
"riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>",
"securityCategory": "sql-injection",
"vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
"vulnerabilityProbability": "HIGH",
},
"status": "REVIEWED",
"textRange": Object {
"endLine": 142,
"endOffset": 83,
"startLine": 142,
"startOffset": 26,
},
"updateDate": "2013-05-13T17:55:42+0200",
"users": Array [
Object {
"active": true,
"local": true,
"login": "assignee",
"name": "John Doe",
},
Object {
"active": true,
"local": true,
"login": "author",
"name": "John Doe",
},
],
}
}
onStatusOptionChange={[Function]}
/>
</DropdownOverlay>
}
>
<Button
className="dropdown-toggle"
disabled={false}
id="status-trigger"
onClick={[Function]}
>
<span>
hotspots.status.select_status
</span>
<DropdownIcon
className="little-spacer-left"
/>
</Button>
</Toggler>
</div>
</div>
</Toggler>
</Tooltip>
</div>
</div>
`;

exports[`should render correctly: open 1`] = `
<div
className="dropdown"
>
<Toggler
closeOnClickOutside={true}
closeOnEscape={true}
onRequestClose={[Function]}
open={true}
overlay={
<DropdownOverlay
noPadding={true}
placement="bottom"
>
<StatusSelection
hotspot={
Object {
"assignee": "assignee",
"assigneeUser": Object {
"active": true,
"local": true,
"login": "assignee",
"name": "John Doe",
},
"author": "author",
"authorUser": Object {
"active": true,
"local": true,
"login": "author",
"name": "John Doe",
},
"canChangeStatus": true,
"changelog": Array [],
"comment": Array [],
"component": Object {
"key": "hotspot-component",
"longName": "Hotspot component long name",
"name": "Hotspot Component",
"path": "path/to/component",
"qualifier": "FIL",
},
"creationDate": "2013-05-13T17:55:41+0200",
"key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123",
"line": 142,
"message": "'3' is a magic number.",
"project": Object {
"key": "hotspot-component",
"longName": "Hotspot component long name",
"name": "Hotspot Component",
"path": "path/to/component",
"qualifier": "TRK",
},
"resolution": "FIXED",
"rule": Object {
"fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>",
"key": "squid:S2077",
"name": "That rule",
"riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>",
"securityCategory": "sql-injection",
"vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
"vulnerabilityProbability": "HIGH",
},
"status": "REVIEWED",
"textRange": Object {
"endLine": 142,
"endOffset": 83,
"startLine": 142,
"startOffset": 26,
},
"updateDate": "2013-05-13T17:55:42+0200",
"users": Array [
Object {
"active": true,
"local": true,
"login": "assignee",
"name": "John Doe",
},
Object {
"active": true,
"local": true,
"login": "author",
"name": "John Doe",
},
],
}
}
onStatusOptionChange={[Function]}
/>
</DropdownOverlay>
}
<div>
<StatusDescription
showTitle={true}
statusOption="FIXED"
/>
<div
className="spacer-top"
>
<div
aria-expanded={true}
aria-haspopup={true}
className="padded bordered display-flex-column display-flex-justify-center"
id="status-trigger"
onClick={[Function]}
role="button"
tabIndex={0}
<Tooltip
overlay={null}
placement="bottom"
>
<div
className="display-flex-center display-flex-space-between"
className="dropdown display-inline-block"
>
<span
className="h3"
<Toggler
closeOnClickOutside={true}
closeOnEscape={true}
onRequestClose={[Function]}
open={true}
overlay={
<DropdownOverlay
noPadding={true}
placement="bottom"
>
<StatusSelection
hotspot={
Object {
"assignee": "assignee",
"assigneeUser": Object {
"active": true,
"local": true,
"login": "assignee",
"name": "John Doe",
},
"author": "author",
"authorUser": Object {
"active": true,
"local": true,
"login": "author",
"name": "John Doe",
},
"canChangeStatus": true,
"changelog": Array [],
"comment": Array [],
"component": Object {
"key": "hotspot-component",
"longName": "Hotspot component long name",
"name": "Hotspot Component",
"path": "path/to/component",
"qualifier": "FIL",
},
"creationDate": "2013-05-13T17:55:41+0200",
"key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123",
"line": 142,
"message": "'3' is a magic number.",
"project": Object {
"key": "hotspot-component",
"longName": "Hotspot component long name",
"name": "Hotspot Component",
"path": "path/to/component",
"qualifier": "TRK",
},
"resolution": "FIXED",
"rule": Object {
"fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>",
"key": "squid:S2077",
"name": "That rule",
"riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>",
"securityCategory": "sql-injection",
"vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
"vulnerabilityProbability": "HIGH",
},
"status": "REVIEWED",
"textRange": Object {
"endLine": 142,
"endOffset": 83,
"startLine": 142,
"startOffset": 26,
},
"updateDate": "2013-05-13T17:55:42+0200",
"users": Array [
Object {
"active": true,
"local": true,
"login": "assignee",
"name": "John Doe",
},
Object {
"active": true,
"local": true,
"login": "author",
"name": "John Doe",
},
],
}
}
onStatusOptionChange={[Function]}
/>
</DropdownOverlay>
}
>
hotspots.status.select_status
</span>
<ChevronDownIcon
className="big-spacer-left"
/>
<Button
className="dropdown-toggle"
disabled={false}
id="status-trigger"
onClick={[Function]}
>
<span>
hotspots.status.select_status
</span>
<DropdownIcon
className="little-spacer-left"
/>
</Button>
</Toggler>
</div>
</div>
</Toggler>
</Tooltip>
</div>
</div>
`;

exports[`should render correctly: readonly 1`] = `
<div
className="dropdown"
>
<Tooltip
overlay="hotspots.status.cannot_change_status"
placement="bottom"
<div>
<StatusDescription
showTitle={true}
statusOption="FIXED"
/>
<div
className="spacer-top"
>
<div
aria-expanded={false}
aria-haspopup={true}
className="padded bordered display-flex-column display-flex-justify-center readonly"
id="status-trigger"
onClick={[Function]}
role="button"
tabIndex={0}
<Tooltip
overlay="hotspots.status.cannot_change_status"
placement="bottom"
>
<div
className="display-flex-center display-flex-space-between"
className="dropdown display-inline-block"
>
<StatusDescription
showTitle={true}
statusOption="FIXED"
/>
<Toggler
closeOnClickOutside={true}
closeOnEscape={true}
onRequestClose={[Function]}
open={false}
overlay={
<DropdownOverlay
noPadding={true}
placement="bottom"
>
<StatusSelection
hotspot={
Object {
"assignee": "assignee",
"assigneeUser": Object {
"active": true,
"local": true,
"login": "assignee",
"name": "John Doe",
},
"author": "author",
"authorUser": Object {
"active": true,
"local": true,
"login": "author",
"name": "John Doe",
},
"canChangeStatus": false,
"changelog": Array [],
"comment": Array [],
"component": Object {
"key": "hotspot-component",
"longName": "Hotspot component long name",
"name": "Hotspot Component",
"path": "path/to/component",
"qualifier": "FIL",
},
"creationDate": "2013-05-13T17:55:41+0200",
"key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123",
"line": 142,
"message": "'3' is a magic number.",
"project": Object {
"key": "hotspot-component",
"longName": "Hotspot component long name",
"name": "Hotspot Component",
"path": "path/to/component",
"qualifier": "TRK",
},
"resolution": "FIXED",
"rule": Object {
"fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>",
"key": "squid:S2077",
"name": "That rule",
"riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>",
"securityCategory": "sql-injection",
"vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
"vulnerabilityProbability": "HIGH",
},
"status": "REVIEWED",
"textRange": Object {
"endLine": 142,
"endOffset": 83,
"startLine": 142,
"startOffset": 26,
},
"updateDate": "2013-05-13T17:55:42+0200",
"users": Array [
Object {
"active": true,
"local": true,
"login": "assignee",
"name": "John Doe",
},
Object {
"active": true,
"local": true,
"login": "author",
"name": "John Doe",
},
],
}
}
onStatusOptionChange={[Function]}
/>
</DropdownOverlay>
}
>
<Button
className="dropdown-toggle"
disabled={true}
id="status-trigger"
onClick={[Function]}
>
<span>
hotspots.status.select_status
</span>
<DropdownIcon
className="little-spacer-left"
/>
</Button>
</Toggler>
</div>
</div>
</Tooltip>
</Tooltip>
</div>
</div>
`;

+ 8
- 4
server/sonar-web/src/main/js/apps/security-hotspots/components/status/__tests__/__snapshots__/StatusSelectionRenderer-test.tsx.snap Zobrazit soubor

@@ -1,7 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<Fragment>
<div
className="abs-width-400"
>
<div
className="big-padded"
>
@@ -65,11 +67,13 @@ exports[`should render correctly 1`] = `
</SubmitButton>
</div>
</div>
</Fragment>
</div>
`;

exports[`should render correctly: loading 1`] = `
<Fragment>
<div
className="abs-width-400"
>
<div
className="big-padded"
>
@@ -136,5 +140,5 @@ exports[`should render correctly: loading 1`] = `
/>
</div>
</div>
</Fragment>
</div>
`;

+ 1
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties Zobrazit soubor

@@ -754,7 +754,7 @@ hotspots.open_in_ide.failure=Unable to connect to your IDE to open the Security

hotspots.assignee.select_user=Select a user...
hotspots.status.cannot_change_status=Changing a hotspot's status requires permission.
hotspots.status.select_status=Select a status...
hotspots.status.select_status=Change status
hotspots.status.add_comment=Add a comment (Optional)
hotspots.status.change_status=Change status
hotspots.status_option.TO_REVIEW=To review

Načítá se…
Zrušit
Uložit