Browse Source

SONAR-12797 Handle branch parameter

SONAR-12719:
* Prevent action button text from wrapping
* Conditional submit button label
tags/8.2.0.32929
Jeremy 4 years ago
parent
commit
c5ec1baf0f

+ 9
- 2
server/sonar-web/src/main/js/api/security-hotspots.ts View File

@@ -59,8 +59,15 @@ export function getSecurityHotspots(
return getJSON('/api/hotspots/search', data).catch(throwGlobalError);
}

export function getSecurityHotspotList(hotspotKeys: string[]): Promise<HotspotSearchResponse> {
return getJSON('/api/hotspots/search', { hotspots: hotspotKeys.join() }).catch(throwGlobalError);
export function getSecurityHotspotList(
hotspotKeys: string[],
data: {
projectKey: string;
} & BranchParameters
): Promise<HotspotSearchResponse> {
return getJSON('/api/hotspots/search', { ...data, hotspots: hotspotKeys.join() }).catch(
throwGlobalError
);
}

export function getSecurityHotspotDetails(securityHotspotKey: string): Promise<Hotspot> {

+ 4
- 1
server/sonar-web/src/main/js/apps/securityHotspots/SecurityHotspotsApp.tsx View File

@@ -157,7 +157,10 @@ export class SecurityHotspotsApp extends React.PureComponent<Props, State> {
this.setState({ hotspotKeys });

if (hotspotKeys && hotspotKeys.length > 0) {
return getSecurityHotspotList(hotspotKeys);
return getSecurityHotspotList(hotspotKeys, {
projectKey: component.key,
...getBranchLikeQuery(branchLike)
});
}

const status =

+ 4
- 1
server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsApp-test.tsx View File

@@ -113,7 +113,10 @@ it('should load data correctly when hotspot key list is forced', async () => {
});

await waitAndUpdate(wrapper);
expect(getSecurityHotspotList).toBeCalledWith(hotspotKeys);
expect(getSecurityHotspotList).toBeCalledWith(hotspotKeys, {
projectKey: 'my-project',
branch: 'branch-6.7'
});
expect(wrapper.state().hotspotKeys).toEqual(hotspotKeys);
expect(wrapper.find(SecurityHotspotsAppRenderer).props().isStaticListOfHotspots).toBeTruthy();


+ 2
- 2
server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotActions.tsx View File

@@ -53,7 +53,7 @@ export default function HotspotActions(props: HotspotActionsProps) {
});

return (
<div className="dropdown">
<div className="dropdown big-spacer-left flex-0">
<Button onClick={() => setOpen(!open)}>
{translate('hotspot.change_status', hotspot.status)}
<DropdownIcon className="little-spacer-left" />
@@ -63,7 +63,7 @@ export default function HotspotActions(props: HotspotActionsProps) {
<OutsideClickHandler onClickOutside={() => setOpen(false)}>
<DropdownOverlay placement={PopupPlacement.BottomRight}>
<HotspotActionsForm
hotspotKey={hotspot.key}
hotspot={hotspot}
onSubmit={data => {
setOpen(false);
props.onSubmit(data);

+ 8
- 7
server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotActionsForm.tsx View File

@@ -20,6 +20,7 @@
import * as React from 'react';
import { assignSecurityHotspot, setSecurityHotspotStatus } from '../../../api/security-hotspots';
import {
Hotspot,
HotspotResolution,
HotspotSetStatusRequest,
HotspotStatus,
@@ -29,7 +30,7 @@ import {
import HotspotActionsFormRenderer from './HotspotActionsFormRenderer';

interface Props {
hotspotKey: string;
hotspot: Hotspot;
onSubmit: (data: HotspotUpdateFields) => void;
}

@@ -62,7 +63,7 @@ export default class HotspotActionsForm extends React.Component<Props, State> {
handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();

const { hotspotKey } = this.props;
const { hotspot } = this.props;
const { comment, selectedOption, selectedUser } = this.state;

const status =
@@ -82,7 +83,7 @@ export default class HotspotActionsForm extends React.Component<Props, State> {
}

this.setState({ submitting: true });
return setSecurityHotspotStatus(hotspotKey, data)
return setSecurityHotspotStatus(hotspot.key, data)
.then(() => {
if (selectedOption === HotspotStatusOption.ADDITIONAL_REVIEW && selectedUser) {
return this.assignHotspot(selectedUser, comment);
@@ -98,22 +99,22 @@ export default class HotspotActionsForm extends React.Component<Props, State> {
};

assignHotspot = (assignee: T.UserActive, comment: string) => {
const { hotspotKey } = this.props;
const { hotspot } = this.props;

return assignSecurityHotspot(hotspotKey, {
return assignSecurityHotspot(hotspot.key, {
assignee: assignee.login,
comment
});
};

render() {
const { hotspotKey } = this.props;
const { hotspot } = this.props;
const { comment, selectedOption, selectedUser, submitting } = this.state;

return (
<HotspotActionsFormRenderer
comment={comment}
hotspotKey={hotspotKey}
hotspotStatus={hotspot.status}
onAssign={this.handleAssign}
onChangeComment={this.handleCommentChange}
onSelectOption={this.handleSelectOption}

+ 7
- 5
server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotActionsFormRenderer.tsx View File

@@ -22,12 +22,12 @@ import { SubmitButton } from 'sonar-ui-common/components/controls/buttons';
import Radio from 'sonar-ui-common/components/controls/Radio';
import { translate } from 'sonar-ui-common/helpers/l10n';
import MarkdownTips from '../../../components/common/MarkdownTips';
import { HotspotStatusOption } from '../../../types/security-hotspots';
import { HotspotStatus, HotspotStatusOption } from '../../../types/security-hotspots';
import HotspotAssigneeSelect from './HotspotAssigneeSelect';

export interface HotspotActionsFormRendererProps {
comment: string;
hotspotKey: string;
hotspotStatus: HotspotStatus;
onAssign: (user: T.UserActive) => void;
onChangeComment: (comment: string) => void;
onSelectOption: (option: HotspotStatusOption) => void;
@@ -38,7 +38,7 @@ export interface HotspotActionsFormRendererProps {
}

export default function HotspotActionsFormRenderer(props: HotspotActionsFormRendererProps) {
const { comment, selectedOption, submitting } = props;
const { comment, hotspotStatus, selectedOption, submitting } = props;

return (
<form className="abs-width-400 padded" onSubmit={props.onSubmit}>
@@ -69,8 +69,8 @@ export default function HotspotActionsFormRenderer(props: HotspotActionsFormRend
<div className="display-flex-column big-spacer-bottom">
<label className="little-spacer-bottom">{translate('hotspots.form.comment')}</label>
<textarea
className="form-field fixed-width spacer-bottom"
autoFocus={true}
className="form-field fixed-width spacer-bottom"
onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) =>
props.onChangeComment(event.currentTarget.value)
}
@@ -86,7 +86,9 @@ export default function HotspotActionsFormRenderer(props: HotspotActionsFormRend
</div>
<div className="text-right">
{submitting && <i className="spinner spacer-right" />}
<SubmitButton disabled={submitting}>{translate('hotspots.form.submit')}</SubmitButton>
<SubmitButton disabled={submitting}>
{translate('hotspots.form.submit', hotspotStatus)}
</SubmitButton>
</div>
</form>
);

+ 2
- 1
server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotActionsForm-test.tsx View File

@@ -21,6 +21,7 @@ import { shallow } from 'enzyme';
import * as React from 'react';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { assignSecurityHotspot, setSecurityHotspotStatus } from '../../../../api/security-hotspots';
import { mockHotspot } from '../../../../helpers/mocks/security-hotspots';
import { mockLoggedInUser } from '../../../../helpers/testMocks';
import {
HotspotResolution,
@@ -128,6 +129,6 @@ it('should handle submit failure', async () => {

function shallowRender(props: Partial<HotspotActionsForm['props']> = {}) {
return shallow<HotspotActionsForm>(
<HotspotActionsForm hotspotKey="key" onSubmit={jest.fn()} {...props} />
<HotspotActionsForm hotspot={mockHotspot({ key: 'key' })} onSubmit={jest.fn()} {...props} />
);
}

+ 2
- 2
server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotActionsFormRenderer-test.tsx View File

@@ -20,7 +20,7 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockLoggedInUser } from '../../../../helpers/testMocks';
import { HotspotStatusOption } from '../../../../types/security-hotspots';
import { HotspotStatus, HotspotStatusOption } from '../../../../types/security-hotspots';
import HotspotActionsForm from '../HotspotActionsForm';
import HotspotActionsFormRenderer, {
HotspotActionsFormRendererProps
@@ -44,7 +44,7 @@ function shallowRender(props: Partial<HotspotActionsFormRendererProps> = {}) {
return shallow<HotspotActionsForm>(
<HotspotActionsFormRenderer
comment="written comment"
hotspotKey="key"
hotspotStatus={HotspotStatus.TO_REVIEW}
onAssign={jest.fn()}
onChangeComment={jest.fn()}
onSelectOption={jest.fn()}

+ 299
- 8
server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotActions-test.tsx.snap View File

@@ -2,7 +2,7 @@

exports[`should open when clicked 1`] = `
<div
className="dropdown"
className="dropdown big-spacer-left flex-0"
>
<Button
onClick={[Function]}
@@ -19,7 +19,104 @@ exports[`should open when clicked 1`] = `
placement="bottom-right"
>
<HotspotActionsForm
hotspotKey="key"
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",
},
"changelog": Array [],
"comment": Array [],
"component": Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "FIL",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
},
"creationDate": "2013-05-13T17:55:41+0200",
"key": "key",
"line": 142,
"message": "'3' is a magic number.",
"project": Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
},
"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": "TO_REVIEW",
"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",
},
],
}
}
onSubmit={[Function]}
/>
</DropdownOverlay>
@@ -29,7 +126,7 @@ exports[`should open when clicked 1`] = `

exports[`should register an eventlistener: Dropdown closed 1`] = `
<div
className="dropdown"
className="dropdown big-spacer-left flex-0"
>
<Button
onClick={[Function]}
@@ -44,7 +141,7 @@ exports[`should register an eventlistener: Dropdown closed 1`] = `

exports[`should register an eventlistener: Dropdown open 1`] = `
<div
className="dropdown"
className="dropdown big-spacer-left flex-0"
>
<Button
onClick={[Function]}
@@ -61,7 +158,104 @@ exports[`should register an eventlistener: Dropdown open 1`] = `
placement="bottom-right"
>
<HotspotActionsForm
hotspotKey="key"
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",
},
"changelog": Array [],
"comment": Array [],
"component": Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "FIL",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
},
"creationDate": "2013-05-13T17:55:41+0200",
"key": "key",
"line": 142,
"message": "'3' is a magic number.",
"project": Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
},
"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": "TO_REVIEW",
"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",
},
],
}
}
onSubmit={[Function]}
/>
</DropdownOverlay>
@@ -71,7 +265,7 @@ exports[`should register an eventlistener: Dropdown open 1`] = `

exports[`should register an eventlistener: Dropdown still open 1`] = `
<div
className="dropdown"
className="dropdown big-spacer-left flex-0"
>
<Button
onClick={[Function]}
@@ -88,7 +282,104 @@ exports[`should register an eventlistener: Dropdown still open 1`] = `
placement="bottom-right"
>
<HotspotActionsForm
hotspotKey="key"
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",
},
"changelog": Array [],
"comment": Array [],
"component": Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "FIL",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
},
"creationDate": "2013-05-13T17:55:41+0200",
"key": "key",
"line": 142,
"message": "'3' is a magic number.",
"project": Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
},
"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": "TO_REVIEW",
"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",
},
],
}
}
onSubmit={[Function]}
/>
</DropdownOverlay>
@@ -98,7 +389,7 @@ exports[`should register an eventlistener: Dropdown still open 1`] = `

exports[`should render correctly 1`] = `
<div
className="dropdown"
className="dropdown big-spacer-left flex-0"
>
<Button
onClick={[Function]}

+ 1
- 1
server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotActionsForm-test.tsx.snap View File

@@ -3,7 +3,7 @@
exports[`should render correctly 1`] = `
<HotspotActionsFormRenderer
comment=""
hotspotKey="key"
hotspotStatus="REVIEWED"
onAssign={[Function]}
onChangeComment={[Function]}
onSelectOption={[Function]}

+ 4
- 4
server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotActionsFormRenderer-test.tsx.snap View File

@@ -90,7 +90,7 @@ exports[`should render correctly 1`] = `
<SubmitButton
disabled={false}
>
hotspots.form.submit
hotspots.form.submit.TO_REVIEW
</SubmitButton>
</div>
</form>
@@ -189,7 +189,7 @@ exports[`should render correctly: Submitting 1`] = `
<SubmitButton
disabled={true}
>
hotspots.form.submit
hotspots.form.submit.TO_REVIEW
</SubmitButton>
</div>
</form>
@@ -285,7 +285,7 @@ exports[`should render correctly: safe option selected 1`] = `
<SubmitButton
disabled={false}
>
hotspots.form.submit
hotspots.form.submit.TO_REVIEW
</SubmitButton>
</div>
</form>
@@ -391,7 +391,7 @@ exports[`should render correctly: user selected 1`] = `
<SubmitButton
disabled={false}
>
hotspots.form.submit
hotspots.form.submit.TO_REVIEW
</SubmitButton>
</div>
</form>

+ 1
- 1
server/sonar-web/src/main/js/types/security-hotspots.ts View File

@@ -84,7 +84,7 @@ export interface Hotspot {
project: T.Component;
resolution?: string;
rule: HotspotRule;
status: string;
status: HotspotStatus;
textRange: T.TextRange;
updateDate: string;
users: T.UserBase[];

+ 2
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -688,7 +688,8 @@ hotspots.form.assign_to=Assign to:
hotspots.form.select_user=Select a user...
hotspots.form.comment=Comment:
hotspots.form.comment.placeholder=This status requires justification
hotspots.form.submit=Apply changes
hotspots.form.submit.TO_REVIEW=Submit Review
hotspots.form.submit.REVIEWED=Apply changes

hotspots.status_option.FIXED=Fixed
hotspots.status_option.FIXED.description=The code has been modified to follow recommended secure coding practices.

Loading…
Cancel
Save