Bläddra i källkod

SONAR-12797 Handle branch parameter

SONAR-12719:
* Prevent action button text from wrapping
* Conditional submit button label
tags/8.2.0.32929
Jeremy 4 år sedan
förälder
incheckning
c5ec1baf0f

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

return getJSON('/api/hotspots/search', data).catch(throwGlobalError); 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> { export function getSecurityHotspotDetails(securityHotspotKey: string): Promise<Hotspot> {

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

this.setState({ hotspotKeys }); this.setState({ hotspotKeys });


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


const status = const status =

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

}); });


await waitAndUpdate(wrapper); 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.state().hotspotKeys).toEqual(hotspotKeys);
expect(wrapper.find(SecurityHotspotsAppRenderer).props().isStaticListOfHotspots).toBeTruthy(); expect(wrapper.find(SecurityHotspotsAppRenderer).props().isStaticListOfHotspots).toBeTruthy();



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

}); });


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

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

import * as React from 'react'; import * as React from 'react';
import { assignSecurityHotspot, setSecurityHotspotStatus } from '../../../api/security-hotspots'; import { assignSecurityHotspot, setSecurityHotspotStatus } from '../../../api/security-hotspots';
import { import {
Hotspot,
HotspotResolution, HotspotResolution,
HotspotSetStatusRequest, HotspotSetStatusRequest,
HotspotStatus, HotspotStatus,
import HotspotActionsFormRenderer from './HotspotActionsFormRenderer'; import HotspotActionsFormRenderer from './HotspotActionsFormRenderer';


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


handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();


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


const status = const status =
} }


this.setState({ submitting: true }); this.setState({ submitting: true });
return setSecurityHotspotStatus(hotspotKey, data)
return setSecurityHotspotStatus(hotspot.key, data)
.then(() => { .then(() => {
if (selectedOption === HotspotStatusOption.ADDITIONAL_REVIEW && selectedUser) { if (selectedOption === HotspotStatusOption.ADDITIONAL_REVIEW && selectedUser) {
return this.assignHotspot(selectedUser, comment); return this.assignHotspot(selectedUser, comment);
}; };


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


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


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


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

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

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


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


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


return ( return (
<form className="abs-width-400 padded" onSubmit={props.onSubmit}> <form className="abs-width-400 padded" onSubmit={props.onSubmit}>
<div className="display-flex-column big-spacer-bottom"> <div className="display-flex-column big-spacer-bottom">
<label className="little-spacer-bottom">{translate('hotspots.form.comment')}</label> <label className="little-spacer-bottom">{translate('hotspots.form.comment')}</label>
<textarea <textarea
className="form-field fixed-width spacer-bottom"
autoFocus={true} autoFocus={true}
className="form-field fixed-width spacer-bottom"
onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) => onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) =>
props.onChangeComment(event.currentTarget.value) props.onChangeComment(event.currentTarget.value)
} }
</div> </div>
<div className="text-right"> <div className="text-right">
{submitting && <i className="spinner spacer-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> </div>
</form> </form>
); );

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

import * as React from 'react'; import * as React from 'react';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { assignSecurityHotspot, setSecurityHotspotStatus } from '../../../../api/security-hotspots'; import { assignSecurityHotspot, setSecurityHotspotStatus } from '../../../../api/security-hotspots';
import { mockHotspot } from '../../../../helpers/mocks/security-hotspots';
import { mockLoggedInUser } from '../../../../helpers/testMocks'; import { mockLoggedInUser } from '../../../../helpers/testMocks';
import { import {
HotspotResolution, HotspotResolution,


function shallowRender(props: Partial<HotspotActionsForm['props']> = {}) { function shallowRender(props: Partial<HotspotActionsForm['props']> = {}) {
return shallow<HotspotActionsForm>( 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 Visa fil

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

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



exports[`should open when clicked 1`] = ` exports[`should open when clicked 1`] = `
<div <div
className="dropdown"
className="dropdown big-spacer-left flex-0"
> >
<Button <Button
onClick={[Function]} onClick={[Function]}
placement="bottom-right" placement="bottom-right"
> >
<HotspotActionsForm <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]} onSubmit={[Function]}
/> />
</DropdownOverlay> </DropdownOverlay>


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


exports[`should register an eventlistener: Dropdown open 1`] = ` exports[`should register an eventlistener: Dropdown open 1`] = `
<div <div
className="dropdown"
className="dropdown big-spacer-left flex-0"
> >
<Button <Button
onClick={[Function]} onClick={[Function]}
placement="bottom-right" placement="bottom-right"
> >
<HotspotActionsForm <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]} onSubmit={[Function]}
/> />
</DropdownOverlay> </DropdownOverlay>


exports[`should register an eventlistener: Dropdown still open 1`] = ` exports[`should register an eventlistener: Dropdown still open 1`] = `
<div <div
className="dropdown"
className="dropdown big-spacer-left flex-0"
> >
<Button <Button
onClick={[Function]} onClick={[Function]}
placement="bottom-right" placement="bottom-right"
> >
<HotspotActionsForm <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]} onSubmit={[Function]}
/> />
</DropdownOverlay> </DropdownOverlay>


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

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

exports[`should render correctly 1`] = ` exports[`should render correctly 1`] = `
<HotspotActionsFormRenderer <HotspotActionsFormRenderer
comment="" comment=""
hotspotKey="key"
hotspotStatus="REVIEWED"
onAssign={[Function]} onAssign={[Function]}
onChangeComment={[Function]} onChangeComment={[Function]}
onSelectOption={[Function]} onSelectOption={[Function]}

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

<SubmitButton <SubmitButton
disabled={false} disabled={false}
> >
hotspots.form.submit
hotspots.form.submit.TO_REVIEW
</SubmitButton> </SubmitButton>
</div> </div>
</form> </form>
<SubmitButton <SubmitButton
disabled={true} disabled={true}
> >
hotspots.form.submit
hotspots.form.submit.TO_REVIEW
</SubmitButton> </SubmitButton>
</div> </div>
</form> </form>
<SubmitButton <SubmitButton
disabled={false} disabled={false}
> >
hotspots.form.submit
hotspots.form.submit.TO_REVIEW
</SubmitButton> </SubmitButton>
</div> </div>
</form> </form>
<SubmitButton <SubmitButton
disabled={false} disabled={false}
> >
hotspots.form.submit
hotspots.form.submit.TO_REVIEW
</SubmitButton> </SubmitButton>
</div> </div>
</form> </form>

+ 1
- 1
server/sonar-web/src/main/js/types/security-hotspots.ts Visa fil

project: T.Component; project: T.Component;
resolution?: string; resolution?: string;
rule: HotspotRule; rule: HotspotRule;
status: string;
status: HotspotStatus;
textRange: T.TextRange; textRange: T.TextRange;
updateDate: string; updateDate: string;
users: T.UserBase[]; users: T.UserBase[];

+ 2
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties Visa fil

hotspots.form.select_user=Select a user... hotspots.form.select_user=Select a user...
hotspots.form.comment=Comment: hotspots.form.comment=Comment:
hotspots.form.comment.placeholder=This status requires justification 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=Fixed
hotspots.status_option.FIXED.description=The code has been modified to follow recommended secure coding practices. hotspots.status_option.FIXED.description=The code has been modified to follow recommended secure coding practices.

Laddar…
Avbryt
Spara