import { getStandards } from '../../helpers/security-standard';
import { isLoggedIn } from '../../helpers/users';
import { BranchLike } from '../../types/branch-like';
-import { ComponentQualifier } from '../../types/component';
import {
HotspotFilters,
HotspotResolution,
return (
<SecurityHotspotsAppRenderer
branchLike={branchLike}
+ component={component}
filters={filters}
hotspots={hotspots}
hotspotsReviewedMeasure={hotspotsReviewedMeasure}
hotspotsTotal={hotspotsTotal}
- isProject={component.qualifier === ComponentQualifier.Project}
isStaticListOfHotspots={Boolean(hotspotKeys && hotspotKeys.length > 0)}
loading={loading}
loadingMeasure={loadingMeasure}
export interface SecurityHotspotsAppRendererProps {
branchLike?: BranchLike;
+ component: T.Component;
filters: HotspotFilters;
hotspots: RawHotspot[];
hotspotsReviewedMeasure?: string;
hotspotsTotal?: number;
- isProject: boolean;
isStaticListOfHotspots: boolean;
loading: boolean;
loadingMeasure: boolean;
export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRendererProps) {
const {
branchLike,
+ component,
hotspots,
hotspotsReviewedMeasure,
hotspotsTotal,
- isProject,
isStaticListOfHotspots,
loading,
loadingMeasure,
return (
<div id="security_hotspots">
<FilterBar
+ component={component}
filters={filters}
hotspotsReviewedMeasure={hotspotsReviewedMeasure}
- isProject={isProject}
isStaticListOfHotspots={isStaticListOfHotspots}
loadingMeasure={loadingMeasure}
onBranch={isBranch(branchLike)}
<div className="main">
<HotspotViewer
branchLike={branchLike}
+ component={component}
hotspotKey={selectedHotspot.key}
onUpdateHotspot={props.onUpdateHotspot}
securityCategories={securityCategories}
import * as React from 'react';
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
import { mockRawHotspot } from '../../../helpers/mocks/security-hotspots';
+import { mockComponent } from '../../../helpers/testMocks';
import { HotspotStatusFilter } from '../../../types/security-hotspots';
import FilterBar from '../components/FilterBar';
import SecurityHotspotsAppRenderer, {
function shallowRender(props: Partial<SecurityHotspotsAppRendererProps> = {}) {
return shallow(
<SecurityHotspotsAppRenderer
+ component={mockComponent()}
filters={{
assignedToMe: false,
sinceLeakPeriod: false,
status: HotspotStatusFilter.TO_REVIEW
}}
hotspots={[]}
- isProject={true}
isStaticListOfHotspots={true}
loading={false}
loadingMeasure={false}
"name": "branch-6.7",
}
}
+ component={
+ 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 [],
+ }
+ }
filters={
Object {
"assignedToMe": false,
}
}
hotspots={Array []}
- isProject={true}
isStaticListOfHotspots={false}
loading={true}
loadingMeasure={true}
id="security_hotspots"
>
<Connect(withCurrentUser(FilterBar))
+ component={
+ 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 [],
+ }
+ }
filters={
Object {
"assignedToMe": false,
"status": "TO_REVIEW",
}
}
- isProject={true}
isStaticListOfHotspots={true}
loadingMeasure={false}
onBranch={false}
className="main"
>
<HotspotViewer
+ component={
+ 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 [],
+ }
+ }
hotspotKey="h2"
onUpdateHotspot={[MockFunction]}
securityCategories={Object {}}
import Measure from '../../../components/measure/Measure';
import CoverageRating from '../../../components/ui/CoverageRating';
import { isLoggedIn } from '../../../helpers/users';
+import { ComponentQualifier } from '../../../types/component';
import { HotspotFilters, HotspotStatusFilter } from '../../../types/security-hotspots';
export interface FilterBarProps {
currentUser: T.CurrentUser;
+ component: T.Component;
filters: HotspotFilters;
hotspotsReviewedMeasure?: string;
- isProject: boolean;
isStaticListOfHotspots: boolean;
loadingMeasure: boolean;
onBranch: boolean;
export function FilterBar(props: FilterBarProps) {
const {
currentUser,
+ component,
filters,
hotspotsReviewedMeasure,
- isProject,
isStaticListOfHotspots,
loadingMeasure,
onBranch
} = props;
+ const isProject = component.qualifier === ComponentQualifier.Project;
return (
<div className="filter-bar display-flex-center">
interface Props {
branchLike?: BranchLike;
+ component: T.Component;
hotspotKey: string;
onUpdateHotspot: (hotspotKey: string) => Promise<void>;
securityCategories: T.StandardSecurityCategories;
this.mounted = false;
}
- fetchHotspot() {
+ fetchHotspot = () => {
this.setState({ loading: true });
return getSecurityHotspotDetails(this.props.hotspotKey)
.then(hotspot => {
return hotspot;
})
.catch(() => this.mounted && this.setState({ loading: false }));
- }
+ };
handleHotspotUpdate = () => {
return this.fetchHotspot().then((hotspot?: Hotspot) => {
};
render() {
- const { branchLike, securityCategories } = this.props;
+ const { branchLike, component, securityCategories } = this.props;
const { hotspot, loading } = this.state;
return (
<HotspotViewerRenderer
branchLike={branchLike}
+ component={component}
hotspot={hotspot}
loading={loading}
onUpdateHotspot={this.handleHotspotUpdate}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { ClipboardButton } from 'sonar-ui-common/components/controls/clipboard';
+import LinkIcon from 'sonar-ui-common/components/icons/LinkIcon';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n';
+import { getPathUrlAsString } from 'sonar-ui-common/helpers/urls';
+import { getBranchLikeQuery } from '../../../helpers/branch-like';
+import { getComponentSecurityHotspotsUrl } from '../../../helpers/urls';
import { BranchLike } from '../../../types/branch-like';
import { Hotspot } from '../../../types/security-hotspots';
import Assignee from './assignee/Assignee';
export interface HotspotViewerRendererProps {
branchLike?: BranchLike;
+ component: T.Component;
hotspot?: Hotspot;
loading: boolean;
onUpdateHotspot: () => Promise<void>;
}
export default function HotspotViewerRenderer(props: HotspotViewerRendererProps) {
- const { branchLike, hotspot, loading, securityCategories } = props;
+ const { branchLike, component, hotspot, loading, securityCategories } = props;
+
+ const permalink = getPathUrlAsString(
+ getComponentSecurityHotspotsUrl(component.key, {
+ ...getBranchLikeQuery(branchLike),
+ hotspots: hotspot?.key
+ }),
+ false
+ );
return (
<DeferredSpinner loading={loading}>
<div className="big-padded">
<div className="big-spacer-bottom">
<div className="display-flex-space-between">
- <h1>{hotspot.message}</h1>
+ <strong className="big">{hotspot.message}</strong>
+ <ClipboardButton copyValue={permalink}>
+ <LinkIcon className="spacer-right" />
+ <span>{translate('hotspots.get_permalink')}</span>
+ </ClipboardButton>
</div>
<div className="text-muted">
<span>{translate('category')}:</span>
import * as React from 'react';
import RadioToggle from 'sonar-ui-common/components/controls/RadioToggle';
import Select from 'sonar-ui-common/components/controls/Select';
-import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks';
+import { mockComponent, mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks';
+import { ComponentQualifier } from '../../../../types/component';
import { HotspotStatusFilter } from '../../../../types/security-hotspots';
import { AssigneeFilterOption, FilterBar, FilterBarProps } from '../FilterBar';
expect(shallowRender({ hotspotsReviewedMeasure: '23.30' })).toMatchSnapshot(
'with hotspots reviewed measure'
);
- expect(shallowRender({ currentUser: mockLoggedInUser(), isProject: false })).toMatchSnapshot(
- 'non-project'
- );
+ expect(
+ shallowRender({
+ currentUser: mockLoggedInUser(),
+ component: mockComponent({ qualifier: ComponentQualifier.Application })
+ })
+ ).toMatchSnapshot('non-project');
});
it('should render correctly when the list of hotspot is static', () => {
function shallowRender(props: Partial<FilterBarProps> = {}) {
return shallow(
<FilterBar
+ component={mockComponent()}
currentUser={mockCurrentUser()}
filters={{
assignedToMe: false,
sinceLeakPeriod: false,
status: HotspotStatusFilter.TO_REVIEW
}}
- isProject={true}
isStaticListOfHotspots={false}
loadingMeasure={false}
onBranch={true}
import * as React from 'react';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { getSecurityHotspotDetails } from '../../../../api/security-hotspots';
+import { mockComponent } from '../../../../helpers/testMocks';
import HotspotViewer from '../HotspotViewer';
const hotspotKey = 'hotspot-key';
function shallowRender(props?: Partial<HotspotViewer['props']>) {
return shallow<HotspotViewer>(
<HotspotViewer
+ component={mockComponent()}
hotspotKey={hotspotKey}
onUpdateHotspot={jest.fn()}
securityCategories={{ cat1: { title: 'cat1' } }}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockBranch } from '../../../../helpers/mocks/branch-like';
import { mockHotspot } from '../../../../helpers/mocks/security-hotspots';
-import { mockUser } from '../../../../helpers/testMocks';
+import { mockComponent, mockUser } from '../../../../helpers/testMocks';
import HotspotViewerRenderer, { HotspotViewerRendererProps } from '../HotspotViewerRenderer';
it('should render correctly', () => {
function shallowRender(props?: Partial<HotspotViewerRendererProps>) {
return shallow(
<HotspotViewerRenderer
+ branchLike={mockBranch()}
+ component={mockComponent()}
hotspot={mockHotspot()}
loading={false}
onUpdateHotspot={jest.fn()}
exports[`should render correctly 1`] = `
<HotspotViewerRenderer
+ component={
+ 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 [],
+ }
+ }
loading={true}
onUpdateHotspot={[Function]}
securityCategories={
exports[`should render correctly 2`] = `
<HotspotViewerRenderer
+ component={
+ 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 [],
+ }
+ }
hotspot={
Object {
"id": "I am a detailled hotspot",
<div
className="display-flex-space-between"
>
- <h1>
+ <strong
+ className="big"
+ >
'3' is a magic number.
- </h1>
+ </strong>
+ <ClipboardButton
+ copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123"
+ >
+ <LinkIcon
+ className="spacer-right"
+ />
+ <span>
+ hotspots.get_permalink
+ </span>
+ </ClipboardButton>
</div>
<div
className="text-muted"
/>
</div>
<HotspotSnippetContainer
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "branch-6.7",
+ }
+ }
hotspot={
Object {
"assignee": "assignee",
<div
className="display-flex-space-between"
>
- <h1>
+ <strong
+ className="big"
+ >
'3' is a magic number.
- </h1>
+ </strong>
+ <ClipboardButton
+ copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123"
+ >
+ <LinkIcon
+ className="spacer-right"
+ />
+ <span>
+ hotspots.get_permalink
+ </span>
+ </ClipboardButton>
</div>
<div
className="text-muted"
/>
</div>
<HotspotSnippetContainer
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "branch-6.7",
+ }
+ }
hotspot={
Object {
"assignee": "assignee",
<div
className="display-flex-space-between"
>
- <h1>
+ <strong
+ className="big"
+ >
'3' is a magic number.
- </h1>
+ </strong>
+ <ClipboardButton
+ copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123"
+ >
+ <LinkIcon
+ className="spacer-right"
+ />
+ <span>
+ hotspots.get_permalink
+ </span>
+ </ClipboardButton>
</div>
<div
className="text-muted"
/>
</div>
<HotspotSnippetContainer
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "branch-6.7",
+ }
+ }
hotspot={
Object {
"assignee": "assignee",
<div
className="display-flex-space-between"
>
- <h1>
+ <strong
+ className="big"
+ >
'3' is a magic number.
- </h1>
+ </strong>
+ <ClipboardButton
+ copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123"
+ >
+ <LinkIcon
+ className="spacer-right"
+ />
+ <span>
+ hotspots.get_permalink
+ </span>
+ </ClipboardButton>
</div>
<div
className="text-muted"
/>
</div>
<HotspotSnippetContainer
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "branch-6.7",
+ }
+ }
hotspot={
Object {
"assignee": "assignee",
<div
className="display-flex-space-between"
>
- <h1>
+ <strong
+ className="big"
+ >
'3' is a magic number.
- </h1>
+ </strong>
+ <ClipboardButton
+ copyValue="http://localhost/security_hotspots?id=my-project&branch=branch-6.7&hotspots=01fc972e-2a3c-433e-bcae-0bd7f88f5123"
+ >
+ <LinkIcon
+ className="spacer-right"
+ />
+ <span>
+ hotspots.get_permalink
+ </span>
+ </ClipboardButton>
</div>
<div
className="text-muted"
/>
</div>
<HotspotSnippetContainer
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "branch-6.7",
+ }
+ }
hotspot={
Object {
"assignee": undefined,
import { createStore, Store } from 'redux';
import { DocumentationEntry } from '../apps/documentation/utils';
import { Exporter, Profile } from '../apps/quality-profiles/types';
+import { ComponentQualifier } from '../types/component';
export function mockAlmApplication(overrides: Partial<T.AlmApplication> = {}): T.AlmApplication {
return {
key: 'my-project',
name: 'MyProject',
organization: 'foo',
- qualifier: 'TRK',
+ qualifier: ComponentQualifier.Project,
qualityGate: { isDefault: true, key: '30', name: 'Sonar way' },
qualityProfiles: [
{
hotspots.status_option.FIXED.description=The code has been modified to follow recommended secure coding practices.
hotspots.status_option.SAFE=Safe
hotspots.status_option.SAFE.description=The code is not at risk and doesn't need to be modified.
+hotspots.get_permalink=Get Permalink
hotspot.filters.title=Filters
hotspot.filters.assignee.assigned_to_me=Assigned to me