@@ -18,17 +18,12 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { cloneDeep, countBy, pick, trim } from 'lodash'; | |||
import { RuleDescriptionSections } from '../../apps/coding-rules/rule'; | |||
import { mockQualityProfile, mockRuleDetails, mockRuleRepository } from '../../helpers/testMocks'; | |||
import { RuleRepository } from '../../types/coding-rules'; | |||
import { RawIssuesResponse } from '../../types/issues'; | |||
import { SearchRulesQuery } from '../../types/rules'; | |||
import { | |||
Rule, | |||
RuleActivation, | |||
RuleDescriptionSections, | |||
RuleDetails, | |||
RulesUpdateRequest | |||
} from '../../types/types'; | |||
import { Rule, RuleActivation, RuleDetails, RulesUpdateRequest } from '../../types/types'; | |||
import { getFacet } from '../issues'; | |||
import { | |||
bulkActivateRules, | |||
@@ -67,6 +62,8 @@ export default class CodingRulesMock { | |||
mockQualityProfile({ key: 'p3', name: 'QP FooBar', language: 'java', languageName: 'Java' }) | |||
]; | |||
const resourceContent = 'Some link <a href="http://example.com">Awsome Reading</a>'; | |||
this.defaultRules = [ | |||
mockRuleDetails({ | |||
key: 'rule1', | |||
@@ -86,7 +83,7 @@ export default class CodingRulesMock { | |||
{ key: RuleDescriptionSections.ASSESS_THE_PROBLEM, content: 'Assess' }, | |||
{ | |||
key: RuleDescriptionSections.RESOURCES, | |||
content: 'Some link <a href="http://example.com">Awsome Reading</a>' | |||
content: resourceContent | |||
} | |||
], | |||
langName: 'JavaScript' | |||
@@ -110,7 +107,7 @@ export default class CodingRulesMock { | |||
{ key: RuleDescriptionSections.HOW_TO_FIX, content: 'This how to fix' }, | |||
{ | |||
key: RuleDescriptionSections.RESOURCES, | |||
content: 'Some link <a href="http://example.com">Awsome Reading</a>' | |||
content: resourceContent | |||
} | |||
] | |||
}), | |||
@@ -122,6 +119,33 @@ export default class CodingRulesMock { | |||
name: 'Bad Python rule', | |||
isExternal: true, | |||
descriptionSections: undefined | |||
}), | |||
mockRuleDetails({ | |||
key: 'rule7', | |||
type: 'VULNERABILITY', | |||
lang: 'py', | |||
langName: 'Python', | |||
name: 'Python rule with context', | |||
descriptionSections: [ | |||
{ | |||
key: RuleDescriptionSections.INTRODUCTION, | |||
content: 'Introduction to this rule with context' | |||
}, | |||
{ | |||
key: RuleDescriptionSections.HOW_TO_FIX, | |||
content: 'This how to fix for spring', | |||
context: { displayName: 'Spring' } | |||
}, | |||
{ | |||
key: RuleDescriptionSections.HOW_TO_FIX, | |||
content: 'This how to fix for spring boot', | |||
context: { displayName: 'Spring boot' } | |||
}, | |||
{ | |||
key: RuleDescriptionSections.RESOURCES, | |||
content: resourceContent | |||
} | |||
] | |||
}) | |||
]; | |||
@@ -18,6 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { cloneDeep, keyBy, range, times } from 'lodash'; | |||
import { RuleDescriptionSections } from '../../apps/coding-rules/rule'; | |||
import { | |||
mockSnippetsByComponent, | |||
mockSourceLine, | |||
@@ -32,7 +33,6 @@ import { Standards } from '../../types/security'; | |||
import { | |||
Dict, | |||
RuleActivation, | |||
RuleDescriptionSections, | |||
RuleDetails, | |||
SnippetsByComponent, | |||
SourceViewerFile | |||
@@ -248,6 +248,27 @@ export default class IssuesServiceMock { | |||
{ key: RuleDescriptionSections.INTRODUCTION, content: '<h1>Into</h1>' }, | |||
{ key: RuleDescriptionSections.ROOT_CAUSE, content: '<h1>Because</h1>' }, | |||
{ key: RuleDescriptionSections.HOW_TO_FIX, content: '<h1>Fix with</h1>' }, | |||
{ | |||
content: '<p> Context 1 content<p>', | |||
key: RuleDescriptionSections.HOW_TO_FIX, | |||
context: { | |||
displayName: 'Spring' | |||
} | |||
}, | |||
{ | |||
content: '<p> Context 2 content<p>', | |||
key: RuleDescriptionSections.HOW_TO_FIX, | |||
context: { | |||
displayName: 'Context 2' | |||
} | |||
}, | |||
{ | |||
content: '<p> Context 3 content<p>', | |||
key: RuleDescriptionSections.HOW_TO_FIX, | |||
context: { | |||
displayName: 'Context 3' | |||
} | |||
}, | |||
{ key: RuleDescriptionSections.RESOURCES, content: '<h1>Link</h1>' } | |||
] | |||
}) |
@@ -134,6 +134,39 @@ it('should show rule advanced section', async () => { | |||
expect(screen.getByRole('link', { name: 'Awsome Reading' })).toBeInTheDocument(); | |||
}); | |||
it('should show rule advanced section with context', async () => { | |||
const user = userEvent.setup(); | |||
renderCodingRulesApp(undefined, 'coding_rules?open=rule7'); | |||
expect( | |||
await screen.findByRole('heading', { level: 3, name: 'Python rule with context' }) | |||
).toBeInTheDocument(); | |||
expect( | |||
screen.getByRole('button', { | |||
name: 'coding_rules.description_section.title.how_to_fix' | |||
}) | |||
).toBeInTheDocument(); | |||
await user.click( | |||
screen.getByRole('button', { | |||
name: 'coding_rules.description_section.title.how_to_fix' | |||
}) | |||
); | |||
expect(screen.getByRole('radio', { name: 'Spring' })).toBeInTheDocument(); | |||
expect(screen.getByRole('radio', { name: 'Spring boot' })).toBeInTheDocument(); | |||
expect( | |||
screen.getByRole('radio', { name: 'coding_rules.description_context_other' }) | |||
).toBeInTheDocument(); | |||
expect(screen.getByText('This how to fix for spring')).toBeInTheDocument(); | |||
await user.click(screen.getByRole('radio', { name: 'Spring boot' })); | |||
expect(screen.getByText('This how to fix for spring boot')).toBeInTheDocument(); | |||
await user.click(screen.getByRole('radio', { name: 'coding_rules.description_context_other' })); | |||
expect(screen.getByText('coding_rules.context.others.title')).toBeInTheDocument(); | |||
expect(screen.getByText('coding_rules.context.others.description.first')).toBeInTheDocument(); | |||
expect(screen.getByText('coding_rules.context.others.description.second')).toBeInTheDocument(); | |||
}); | |||
it('should be able to extend the rule description', async () => { | |||
const user = userEvent.setup(); | |||
handler.setIsAdmin(); |
@@ -23,7 +23,8 @@ import FormattingTips from '../../../components/common/FormattingTips'; | |||
import { Button, ResetButtonLink } from '../../../components/controls/buttons'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { sanitizeString } from '../../../helpers/sanitize'; | |||
import { RuleDescriptionSections, RuleDetails } from '../../../types/types'; | |||
import { RuleDetails } from '../../../types/types'; | |||
import { RuleDescriptionSections } from '../rule'; | |||
import RemoveExtendedDescriptionModal from './RemoveExtendedDescriptionModal'; | |||
import RuleTabViewer from './RuleTabViewer'; | |||
@@ -17,11 +17,14 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { groupBy } from 'lodash'; | |||
import * as React from 'react'; | |||
import BoxedTabs from '../../../components/controls/BoxedTabs'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { sanitizeString } from '../../../helpers/sanitize'; | |||
import { RuleDescriptionSections, RuleDetails } from '../../../types/types'; | |||
import { RuleDetails } from '../../../types/types'; | |||
import { RuleDescriptionSection, RuleDescriptionSections } from '../rule'; | |||
import RuleContextDescription from '../../../components/rules/RuleContextDescription'; | |||
interface Props { | |||
ruleDetails: RuleDetails; | |||
@@ -35,7 +38,7 @@ interface State { | |||
interface Tab { | |||
key: TabKeys; | |||
label: React.ReactNode; | |||
content: string; | |||
descriptionSections: RuleDescriptionSection[]; | |||
} | |||
enum TabKeys { | |||
@@ -65,6 +68,7 @@ export default class RuleViewerTabs extends React.PureComponent<Props, State> { | |||
computeState() { | |||
const { ruleDetails } = this.props; | |||
const groupedDescriptions = groupBy(ruleDetails.descriptionSections, 'key'); | |||
const tabs = [ | |||
{ | |||
@@ -73,32 +77,24 @@ export default class RuleViewerTabs extends React.PureComponent<Props, State> { | |||
ruleDetails.type === 'SECURITY_HOTSPOT' | |||
? translate('coding_rules.description_section.title.root_cause.SECURITY_HOTSPOT') | |||
: translate('coding_rules.description_section.title.root_cause'), | |||
content: ruleDetails.descriptionSections?.find( | |||
section => section.key === RuleDescriptionSections.ROOT_CAUSE | |||
)?.content | |||
descriptionSections: groupedDescriptions[RuleDescriptionSections.ROOT_CAUSE] | |||
}, | |||
{ | |||
key: TabKeys.AssessTheIssue, | |||
label: translate('coding_rules.description_section.title', TabKeys.AssessTheIssue), | |||
content: ruleDetails.descriptionSections?.find( | |||
section => section.key === RuleDescriptionSections.ASSESS_THE_PROBLEM | |||
)?.content | |||
descriptionSections: groupedDescriptions[RuleDescriptionSections.ASSESS_THE_PROBLEM] | |||
}, | |||
{ | |||
key: TabKeys.HowToFixIt, | |||
label: translate('coding_rules.description_section.title', TabKeys.HowToFixIt), | |||
content: ruleDetails.descriptionSections?.find( | |||
section => section.key === RuleDescriptionSections.HOW_TO_FIX | |||
)?.content | |||
descriptionSections: groupedDescriptions[RuleDescriptionSections.HOW_TO_FIX] | |||
}, | |||
{ | |||
key: TabKeys.Resources, | |||
label: translate('coding_rules.description_section.title', TabKeys.Resources), | |||
content: ruleDetails.descriptionSections?.find( | |||
section => section.key === RuleDescriptionSections.RESOURCES | |||
)?.content | |||
descriptionSections: groupedDescriptions[RuleDescriptionSections.RESOURCES] | |||
} | |||
].filter(tab => tab.content !== undefined) as Array<Tab>; | |||
].filter(tab => tab.descriptionSections) as Array<Tab>; | |||
return { | |||
currentTab: tabs[0], | |||
@@ -112,7 +108,6 @@ export default class RuleViewerTabs extends React.PureComponent<Props, State> { | |||
const intro = ruleDetails.descriptionSections?.find( | |||
section => section.key === RuleDescriptionSections.INTRODUCTION | |||
)?.content; | |||
return ( | |||
<> | |||
{intro && ( | |||
@@ -130,11 +125,20 @@ export default class RuleViewerTabs extends React.PureComponent<Props, State> { | |||
/> | |||
<div className="bordered-right bordered-left bordered-bottom huge-spacer-bottom"> | |||
<div | |||
className="big-padded rule-desc" | |||
// eslint-disable-next-line react/no-danger | |||
dangerouslySetInnerHTML={{ __html: sanitizeString(currentTab.content) }} | |||
/> | |||
{currentTab.descriptionSections.length === 1 && | |||
!currentTab.descriptionSections[0].context ? ( | |||
<div | |||
className="big-padded rule-desc" | |||
// eslint-disable-next-line react/no-danger | |||
dangerouslySetInnerHTML={{ | |||
__html: sanitizeString(currentTab.descriptionSections[0].content) | |||
}} | |||
/> | |||
) : ( | |||
<div className="big-padded rule-desc"> | |||
<RuleContextDescription description={currentTab.descriptionSections} /> | |||
</div> | |||
)} | |||
</div> | |||
</> | |||
); |
@@ -0,0 +1,37 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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. | |||
*/ | |||
export enum RuleDescriptionSections { | |||
DEFAULT = 'default', | |||
INTRODUCTION = 'introduction', | |||
ROOT_CAUSE = 'root_cause', | |||
ASSESS_THE_PROBLEM = 'assess_the_problem', | |||
HOW_TO_FIX = 'how_to_fix', | |||
RESOURCES = 'resources' | |||
} | |||
export interface RuleDescriptionContext { | |||
displayName: string; | |||
} | |||
export interface RuleDescriptionSection { | |||
key: RuleDescriptionSections; | |||
content: string; | |||
context?: RuleDescriptionContext; | |||
} |
@@ -274,3 +274,11 @@ | |||
.coding-rule-activation-actions { | |||
padding-left: 20px; | |||
} | |||
.rules-context-description ul { | |||
padding: 0px; | |||
} | |||
.rules-context-description h2.rule-contexts-title { | |||
border: 0px; | |||
} |
@@ -52,7 +52,20 @@ it('should open issue and navigate', async () => { | |||
expect(screen.getByRole('button', { name: `issue.tabs.how` })).toBeInTheDocument(); | |||
await user.click(screen.getByRole('button', { name: `issue.tabs.how` })); | |||
expect(screen.getByRole('heading', { name: 'Fix with' })).toBeInTheDocument(); | |||
expect(screen.getByRole('radio', { name: 'Context 2' })).toBeInTheDocument(); | |||
expect(screen.getByRole('radio', { name: 'Context 3' })).toBeInTheDocument(); | |||
expect(screen.getByRole('radio', { name: 'Spring' })).toBeInTheDocument(); | |||
expect( | |||
screen.getByRole('radio', { name: 'coding_rules.description_context_other' }) | |||
).toBeInTheDocument(); | |||
await user.click(screen.getByRole('radio', { name: 'Context 2' })); | |||
expect(screen.getByText('Context 2 content')).toBeInTheDocument(); | |||
await user.click(screen.getByRole('radio', { name: 'coding_rules.description_context_other' })); | |||
expect(screen.getByText('coding_rules.context.others.title')).toBeInTheDocument(); | |||
expect(screen.getByText('coding_rules.context.others.description.first')).toBeInTheDocument(); | |||
expect(screen.getByText('coding_rules.context.others.description.second')).toBeInTheDocument(); | |||
expect(screen.getByRole('button', { name: `issue.tabs.why` })).toBeInTheDocument(); | |||
await user.click(screen.getByRole('button', { name: `issue.tabs.why` })); |
@@ -18,13 +18,16 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import classNames from 'classnames'; | |||
import { groupBy } from 'lodash'; | |||
import * as React from 'react'; | |||
import { Link } from 'react-router-dom'; | |||
import BoxedTabs from '../../../components/controls/BoxedTabs'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { sanitizeString } from '../../../helpers/sanitize'; | |||
import { getRuleUrl } from '../../../helpers/urls'; | |||
import { Component, Issue, RuleDescriptionSections, RuleDetails } from '../../../types/types'; | |||
import { Component, Issue, RuleDetails } from '../../../types/types'; | |||
import RuleContextDescription from '../../../components/rules/RuleContextDescription'; | |||
import { RuleDescriptionSection, RuleDescriptionSections } from '../../coding-rules/rule'; | |||
interface Props { | |||
component?: Component; | |||
@@ -41,7 +44,7 @@ interface State { | |||
interface Tab { | |||
key: TabKeys; | |||
label: React.ReactNode; | |||
content: string; | |||
descriptionSections: RuleDescriptionSection[]; | |||
isDefault: boolean; | |||
} | |||
@@ -78,49 +81,54 @@ export default class IssueViewerTabs extends React.PureComponent<Props, State> { | |||
computeTabs() { | |||
const { ruleDetails } = this.props; | |||
const groupedDescriptions = groupBy(ruleDetails.descriptionSections, 'key'); | |||
const tabs = [ | |||
if (ruleDetails.htmlNote) { | |||
if (groupedDescriptions[RuleDescriptionSections.RESOURCES] !== undefined) { | |||
// We add the extended description (htmlNote) in the first context, in case there are contexts | |||
// Extended description will get reworked in future | |||
groupedDescriptions[RuleDescriptionSections.RESOURCES][0].content += | |||
'<br/>' + ruleDetails.htmlNote; | |||
} else { | |||
groupedDescriptions[RuleDescriptionSections.RESOURCES] = [ | |||
{ | |||
key: RuleDescriptionSections.RESOURCES, | |||
content: ruleDetails.htmlNote | |||
} | |||
]; | |||
} | |||
} | |||
return [ | |||
{ | |||
key: TabKeys.Code, | |||
label: translate('issue.tabs', TabKeys.Code), | |||
content: '' | |||
descriptionSections: [] | |||
}, | |||
{ | |||
key: TabKeys.WhyIsThisAnIssue, | |||
label: translate('issue.tabs', TabKeys.WhyIsThisAnIssue), | |||
content: ruleDetails.descriptionSections?.find(section => | |||
[RuleDescriptionSections.DEFAULT, RuleDescriptionSections.ROOT_CAUSE].includes( | |||
section.key | |||
) | |||
)?.content, | |||
descriptionSections: | |||
groupedDescriptions[RuleDescriptionSections.DEFAULT] || | |||
groupedDescriptions[RuleDescriptionSections.ROOT_CAUSE], | |||
isDefault: | |||
ruleDetails.descriptionSections?.find( | |||
ruleDetails.descriptionSections?.filter( | |||
section => section.key === RuleDescriptionSections.DEFAULT | |||
) !== undefined | |||
}, | |||
{ | |||
key: TabKeys.HowToFixIt, | |||
label: translate('issue.tabs', TabKeys.HowToFixIt), | |||
content: ruleDetails.descriptionSections?.find( | |||
section => section.key === RuleDescriptionSections.HOW_TO_FIX | |||
)?.content, | |||
descriptionSections: groupedDescriptions[RuleDescriptionSections.HOW_TO_FIX], | |||
isDefault: false | |||
}, | |||
{ | |||
key: TabKeys.Resources, | |||
label: translate('issue.tabs', TabKeys.Resources), | |||
content: ruleDetails.descriptionSections?.find( | |||
section => section.key === RuleDescriptionSections.RESOURCES | |||
)?.content, | |||
descriptionSections: groupedDescriptions[RuleDescriptionSections.RESOURCES], | |||
isDefault: false | |||
} | |||
].filter(tab => tab.content !== undefined) as Array<Tab>; | |||
if (ruleDetails.htmlNote) { | |||
tabs[tabs.length - 1].content += '<br/>' + ruleDetails.htmlNote; | |||
} | |||
return tabs; | |||
].filter(tab => tab.descriptionSections) as Array<Tab>; | |||
} | |||
render() { | |||
@@ -131,7 +139,7 @@ export default class IssueViewerTabs extends React.PureComponent<Props, State> { | |||
issue: { message } | |||
} = this.props; | |||
const { tabs, currentTabKey } = this.state; | |||
const selectedTab = tabs.find(tab => tab.key === currentTabKey); | |||
return ( | |||
<> | |||
<div | |||
@@ -152,26 +160,35 @@ export default class IssueViewerTabs extends React.PureComponent<Props, State> { | |||
tabs={tabs} | |||
/> | |||
</div> | |||
<div className="bordered-right bordered-left bordered-bottom huge-spacer-bottom"> | |||
<div | |||
className={classNames('padded', { | |||
hidden: currentTabKey !== TabKeys.Code | |||
})}> | |||
{codeTabContent} | |||
{selectedTab && ( | |||
<div className="bordered-right bordered-left bordered-bottom huge-spacer-bottom"> | |||
{selectedTab.key === TabKeys.Code && <div className="padded">{codeTabContent}</div>} | |||
{selectedTab.key !== TabKeys.Code && | |||
(selectedTab.descriptionSections.length === 1 && | |||
!selectedTab.descriptionSections[0].context ? ( | |||
<div | |||
key={selectedTab.key} | |||
className={classNames('big-padded', { | |||
markdown: selectedTab.isDefault, | |||
'rule-desc': !selectedTab.isDefault | |||
})} | |||
// eslint-disable-next-line react/no-danger | |||
dangerouslySetInnerHTML={{ | |||
__html: sanitizeString(selectedTab.descriptionSections[0].content) | |||
}} | |||
/> | |||
) : ( | |||
<div | |||
key={selectedTab.key} | |||
className={classNames('big-padded', { | |||
markdown: selectedTab.isDefault, | |||
'rule-desc': !selectedTab.isDefault | |||
})}> | |||
<RuleContextDescription description={selectedTab.descriptionSections} /> | |||
</div> | |||
))} | |||
</div> | |||
{tabs.slice(1).map(tab => ( | |||
<div | |||
key={tab.key} | |||
className={classNames('big-padded', { | |||
hidden: currentTabKey !== tab.key, | |||
markdown: tab.isDefault, | |||
'rule-desc': !tab.isDefault | |||
})} | |||
// eslint-disable-next-line react/no-danger | |||
dangerouslySetInnerHTML={{ __html: sanitizeString(tab.content) }} | |||
/> | |||
))} | |||
</div> | |||
)} | |||
</> | |||
); | |||
} |
@@ -26,7 +26,8 @@ import { | |||
HotspotStatusFilter, | |||
HotspotStatusOption | |||
} from '../../../types/security-hotspots'; | |||
import { Component, RuleDescriptionSections } from '../../../types/types'; | |||
import { Component } from '../../../types/types'; | |||
import { RuleDescriptionSection } from '../../coding-rules/rule'; | |||
import { getStatusFilterFromStatusOption } from '../utils'; | |||
import HotspotViewerRenderer from './HotspotViewerRenderer'; | |||
@@ -42,6 +43,7 @@ interface Props { | |||
interface State { | |||
hotspot?: Hotspot; | |||
ruleDescriptionSections?: RuleDescriptionSection[]; | |||
lastStatusChangedTo?: HotspotStatusOption; | |||
loading: boolean; | |||
showStatusUpdateSuccessModal: boolean; | |||
@@ -82,24 +84,11 @@ export default class HotspotViewer extends React.PureComponent<Props, State> { | |||
const ruleDetails = await getRuleDetails({ key: hotspot.rule.key }).then(r => r.rule); | |||
if (this.mounted) { | |||
hotspot.rule.riskDescription = | |||
ruleDetails.descriptionSections?.find(section => | |||
[RuleDescriptionSections.DEFAULT, RuleDescriptionSections.ROOT_CAUSE].includes( | |||
section.key | |||
) | |||
)?.content || hotspot.rule.riskDescription; | |||
hotspot.rule.fixRecommendations = | |||
ruleDetails.descriptionSections?.find( | |||
section => RuleDescriptionSections.HOW_TO_FIX === section.key | |||
)?.content || hotspot.rule.fixRecommendations; | |||
hotspot.rule.vulnerabilityDescription = | |||
ruleDetails.descriptionSections?.find( | |||
section => RuleDescriptionSections.ASSESS_THE_PROBLEM === section.key | |||
)?.content || hotspot.rule.vulnerabilityDescription; | |||
this.setState({ hotspot, loading: false }); | |||
this.setState({ | |||
hotspot, | |||
loading: false, | |||
ruleDescriptionSections: ruleDetails.descriptionSections | |||
}); | |||
} | |||
} catch (error) { | |||
if (this.mounted) { | |||
@@ -141,13 +130,20 @@ export default class HotspotViewer extends React.PureComponent<Props, State> { | |||
render() { | |||
const { component, hotspotsReviewedMeasure, selectedHotspotLocation } = this.props; | |||
const { hotspot, lastStatusChangedTo, loading, showStatusUpdateSuccessModal } = this.state; | |||
const { | |||
hotspot, | |||
ruleDescriptionSections, | |||
lastStatusChangedTo, | |||
loading, | |||
showStatusUpdateSuccessModal | |||
} = this.state; | |||
return ( | |||
<HotspotViewerRenderer | |||
component={component} | |||
commentTextRef={this.commentTextRef} | |||
hotspot={hotspot} | |||
ruleDescriptionSections={ruleDescriptionSections} | |||
hotspotsReviewedMeasure={hotspotsReviewedMeasure} | |||
lastStatusChangedTo={lastStatusChangedTo} | |||
loading={loading} |
@@ -24,6 +24,7 @@ import { fillBranchLike } from '../../../helpers/branch-like'; | |||
import { Hotspot, HotspotStatusOption } from '../../../types/security-hotspots'; | |||
import { Component } from '../../../types/types'; | |||
import { CurrentUser } from '../../../types/users'; | |||
import { RuleDescriptionSection } from '../../coding-rules/rule'; | |||
import { HotspotHeader } from './HotspotHeader'; | |||
import HotspotReviewHistoryAndComments from './HotspotReviewHistoryAndComments'; | |||
import HotspotSnippetContainer from './HotspotSnippetContainer'; | |||
@@ -35,6 +36,7 @@ export interface HotspotViewerRendererProps { | |||
component: Component; | |||
currentUser: CurrentUser; | |||
hotspot?: Hotspot; | |||
ruleDescriptionSections?: RuleDescriptionSection[]; | |||
hotspotsReviewedMeasure?: string; | |||
lastStatusChangedTo?: HotspotStatusOption; | |||
loading: boolean; | |||
@@ -58,7 +60,8 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) { | |||
lastStatusChangedTo, | |||
showStatusUpdateSuccessModal, | |||
commentTextRef, | |||
selectedHotspotLocation | |||
selectedHotspotLocation, | |||
ruleDescriptionSections | |||
} = props; | |||
return ( | |||
@@ -87,6 +90,7 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) { | |||
/> | |||
} | |||
hotspot={hotspot} | |||
ruleDescriptionSections={ruleDescriptionSections} | |||
selectedHotspotLocation={selectedHotspotLocation} | |||
/> | |||
<HotspotReviewHistoryAndComments |
@@ -17,7 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import classNames from 'classnames'; | |||
import { groupBy } from 'lodash'; | |||
import * as React from 'react'; | |||
import BoxedTabs from '../../../components/controls/BoxedTabs'; | |||
import { isInput, isShortcut } from '../../../helpers/keyboardEventHelpers'; | |||
@@ -25,10 +25,13 @@ import { KeyboardKeys } from '../../../helpers/keycodes'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { sanitizeString } from '../../../helpers/sanitize'; | |||
import { Hotspot } from '../../../types/security-hotspots'; | |||
import RuleContextDescription from '../../../components/rules/RuleContextDescription'; | |||
import { RuleDescriptionSection, RuleDescriptionSections } from '../../coding-rules/rule'; | |||
interface Props { | |||
codeTabContent: React.ReactNode; | |||
hotspot: Hotspot; | |||
ruleDescriptionSections?: RuleDescriptionSection[]; | |||
selectedHotspotLocation?: number; | |||
} | |||
@@ -40,7 +43,7 @@ interface State { | |||
interface Tab { | |||
key: TabKeys; | |||
label: React.ReactNode; | |||
content: string; | |||
descriptionSections: RuleDescriptionSection[]; | |||
} | |||
export enum TabKeys { | |||
@@ -114,31 +117,34 @@ export default class HotspotViewerTabs extends React.PureComponent<Props, State> | |||
}; | |||
computeTabs() { | |||
const { hotspot } = this.props; | |||
const { ruleDescriptionSections } = this.props; | |||
const groupedDescriptions = groupBy(ruleDescriptionSections, description => description.key); | |||
const descriptionTabs = [ | |||
{ | |||
key: TabKeys.RiskDescription, | |||
label: translate('hotspots.tabs.risk_description'), | |||
content: hotspot.rule.riskDescription || '' | |||
descriptionSections: | |||
groupedDescriptions[RuleDescriptionSections.DEFAULT] || | |||
groupedDescriptions[RuleDescriptionSections.ROOT_CAUSE] | |||
}, | |||
{ | |||
key: TabKeys.VulnerabilityDescription, | |||
label: translate('hotspots.tabs.vulnerability_description'), | |||
content: hotspot.rule.vulnerabilityDescription || '' | |||
descriptionSections: groupedDescriptions[RuleDescriptionSections.ASSESS_THE_PROBLEM] | |||
}, | |||
{ | |||
key: TabKeys.FixRecommendation, | |||
label: translate('hotspots.tabs.fix_recommendations'), | |||
content: hotspot.rule.fixRecommendations || '' | |||
descriptionSections: groupedDescriptions[RuleDescriptionSections.HOW_TO_FIX] | |||
} | |||
].filter(tab => tab.content.length > 0); | |||
].filter(tab => tab.descriptionSections); | |||
return [ | |||
{ | |||
key: TabKeys.Code, | |||
label: translate('hotspots.tabs.code'), | |||
content: '' | |||
descriptionSections: [] | |||
}, | |||
...descriptionTabs | |||
]; | |||
@@ -162,27 +168,27 @@ export default class HotspotViewerTabs extends React.PureComponent<Props, State> | |||
render() { | |||
const { codeTabContent } = this.props; | |||
const { tabs, currentTab } = this.state; | |||
return ( | |||
<> | |||
<BoxedTabs onSelect={this.handleSelectTabs} selected={currentTab.key} tabs={tabs} /> | |||
<div className="bordered huge-spacer-bottom"> | |||
<div | |||
className={classNames('padded', { | |||
hidden: currentTab.key !== TabKeys.Code | |||
})}> | |||
{codeTabContent} | |||
</div> | |||
{tabs.slice(1).map(tab => ( | |||
<div | |||
key={tab.key} | |||
className={classNames('markdown big-padded', { | |||
hidden: currentTab.key !== tab.key | |||
})} | |||
// eslint-disable-next-line react/no-danger | |||
dangerouslySetInnerHTML={{ __html: sanitizeString(tab.content) }} | |||
/> | |||
))} | |||
{currentTab.key === TabKeys.Code && <div className="padded">{codeTabContent}</div>} | |||
{currentTab.key !== TabKeys.Code && | |||
(currentTab.descriptionSections.length === 1 && | |||
!currentTab.descriptionSections[0].context ? ( | |||
<div | |||
key={currentTab.key} | |||
className="markdown big-padded" | |||
// eslint-disable-next-line react/no-danger | |||
dangerouslySetInnerHTML={{ | |||
__html: sanitizeString(currentTab.descriptionSections[0].content) | |||
}} | |||
/> | |||
) : ( | |||
<div className="markdown big-padded"> | |||
<RuleContextDescription description={currentTab.descriptionSections} /> | |||
</div> | |||
))} | |||
</div> | |||
</> | |||
); |
@@ -27,7 +27,7 @@ import { scrollToElement } from '../../../../helpers/scrolling'; | |||
import { mockRuleDetails } from '../../../../helpers/testMocks'; | |||
import { waitAndUpdate } from '../../../../helpers/testUtils'; | |||
import { HotspotStatusOption } from '../../../../types/security-hotspots'; | |||
import { RuleDescriptionSections } from '../../../../types/types'; | |||
import { RuleDescriptionSections } from '../../../coding-rules/rule'; | |||
import HotspotViewer from '../HotspotViewer'; | |||
import HotspotViewerRenderer from '../HotspotViewerRenderer'; | |||
@@ -85,11 +85,20 @@ it('should render fetch rule details', async () => { | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().hotspot?.rule).toStrictEqual({ | |||
fixRecommendations: 'how', | |||
riskDescription: 'cause', | |||
vulnerabilityDescription: 'assess' | |||
}); | |||
expect(wrapper.state().ruleDescriptionSections).toStrictEqual([ | |||
{ | |||
key: RuleDescriptionSections.ASSESS_THE_PROBLEM, | |||
content: 'assess' | |||
}, | |||
{ | |||
key: RuleDescriptionSections.ROOT_CAUSE, | |||
content: 'cause' | |||
}, | |||
{ | |||
key: RuleDescriptionSections.HOW_TO_FIX, | |||
content: 'how' | |||
} | |||
]); | |||
}); | |||
it('should refresh hotspot list on status update', () => { |
@@ -21,9 +21,10 @@ import { mount, shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import BoxedTabs, { BoxedTabsProps } from '../../../../components/controls/BoxedTabs'; | |||
import { KeyboardKeys } from '../../../../helpers/keycodes'; | |||
import { mockHotspot, mockHotspotRule } from '../../../../helpers/mocks/security-hotspots'; | |||
import { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; | |||
import { mockUser } from '../../../../helpers/testMocks'; | |||
import { mockEvent } from '../../../../helpers/testUtils'; | |||
import { RuleDescriptionSections } from '../../../coding-rules/rule'; | |||
import HotspotViewerTabs, { TabKeys } from '../HotspotViewerTabs'; | |||
const originalAddEventListener = window.addEventListener; | |||
@@ -62,13 +63,9 @@ it('should render correctly', () => { | |||
expect( | |||
shallowRender({ | |||
hotspot: mockHotspot({ | |||
creationDate: undefined, | |||
rule: mockHotspotRule({ | |||
riskDescription: undefined, | |||
fixRecommendations: undefined, | |||
vulnerabilityDescription: undefined | |||
}) | |||
}) | |||
creationDate: undefined | |||
}), | |||
ruleDescriptionSections: undefined | |||
}) | |||
.find<BoxedTabsProps<string>>(BoxedTabs) | |||
.props().tabs | |||
@@ -95,16 +92,21 @@ it('should render correctly', () => { | |||
it('should filter empty tab', () => { | |||
const count = shallowRender({ | |||
hotspot: mockHotspot({ | |||
rule: mockHotspotRule() | |||
}) | |||
hotspot: mockHotspot() | |||
}).state().tabs.length; | |||
expect( | |||
shallowRender({ | |||
hotspot: mockHotspot({ | |||
rule: mockHotspotRule({ riskDescription: undefined }) | |||
}) | |||
ruleDescriptionSections: [ | |||
{ | |||
key: RuleDescriptionSections.ROOT_CAUSE, | |||
content: 'cause' | |||
}, | |||
{ | |||
key: RuleDescriptionSections.HOW_TO_FIX, | |||
content: 'how' | |||
} | |||
] | |||
}).state().tabs.length | |||
).toBe(count - 1); | |||
}); | |||
@@ -188,6 +190,20 @@ function shallowRender(props?: Partial<HotspotViewerTabs['props']>) { | |||
<HotspotViewerTabs | |||
codeTabContent={<div>CodeTabContent</div>} | |||
hotspot={mockHotspot()} | |||
ruleDescriptionSections={[ | |||
{ | |||
key: RuleDescriptionSections.ASSESS_THE_PROBLEM, | |||
content: 'assess' | |||
}, | |||
{ | |||
key: RuleDescriptionSections.ROOT_CAUSE, | |||
content: 'cause' | |||
}, | |||
{ | |||
key: RuleDescriptionSections.HOW_TO_FIX, | |||
content: 'how' | |||
} | |||
]} | |||
{...props} | |||
/> | |||
); |
@@ -92,12 +92,9 @@ exports[`should render correctly 1`] = ` | |||
}, | |||
"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", | |||
@@ -192,12 +189,9 @@ exports[`should render correctly 1`] = ` | |||
}, | |||
"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", |
@@ -93,12 +93,9 @@ exports[`should render correctly 1`] = ` | |||
}, | |||
"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", | |||
@@ -197,12 +194,9 @@ exports[`should render correctly without user 1`] = ` | |||
}, | |||
"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", |
@@ -87,12 +87,9 @@ exports[`should render correctly 1`] = ` | |||
}, | |||
"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", |
@@ -92,12 +92,9 @@ exports[`should render correctly 1`] = ` | |||
}, | |||
"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", | |||
@@ -228,12 +225,9 @@ exports[`should render correctly when secondary location is selected: with selec | |||
}, | |||
"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", | |||
@@ -359,12 +353,9 @@ exports[`should render correctly: with sourcelines 1`] = ` | |||
}, | |||
"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", | |||
@@ -480,12 +471,9 @@ exports[`should render correctly: with sourcelines 1`] = ` | |||
}, | |||
"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", |
@@ -71,11 +71,7 @@ exports[`should render correctly 2`] = ` | |||
hotspot={ | |||
Object { | |||
"id": "I am a detailled hotspot", | |||
"rule": Object { | |||
"fixRecommendations": undefined, | |||
"riskDescription": undefined, | |||
"vulnerabilityDescription": undefined, | |||
}, | |||
"rule": Object {}, | |||
} | |||
} | |||
loading={false} | |||
@@ -84,6 +80,7 @@ exports[`should render correctly 2`] = ` | |||
onShowCommentForm={[Function]} | |||
onSwitchFilterToStatusOfUpdatedHotspot={[Function]} | |||
onUpdateHotspot={[Function]} | |||
ruleDescriptionSections={Array []} | |||
showStatusUpdateSuccessModal={false} | |||
/> | |||
`; |
@@ -63,12 +63,9 @@ exports[`should render correctly: anonymous user 1`] = ` | |||
}, | |||
"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", | |||
@@ -176,12 +173,9 @@ exports[`should render correctly: anonymous user 1`] = ` | |||
}, | |||
"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", | |||
@@ -266,12 +260,9 @@ exports[`should render correctly: anonymous user 1`] = ` | |||
}, | |||
"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", | |||
@@ -364,12 +355,9 @@ exports[`should render correctly: anonymous user 1`] = ` | |||
}, | |||
"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", | |||
@@ -465,12 +453,9 @@ exports[`should render correctly: assignee without name 1`] = ` | |||
}, | |||
"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", | |||
@@ -578,12 +563,9 @@ exports[`should render correctly: assignee without name 1`] = ` | |||
}, | |||
"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", | |||
@@ -668,12 +650,9 @@ exports[`should render correctly: assignee without name 1`] = ` | |||
}, | |||
"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", | |||
@@ -766,12 +745,9 @@ exports[`should render correctly: assignee without name 1`] = ` | |||
}, | |||
"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", | |||
@@ -867,12 +843,9 @@ exports[`should render correctly: default 1`] = ` | |||
}, | |||
"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", | |||
@@ -980,12 +953,9 @@ exports[`should render correctly: default 1`] = ` | |||
}, | |||
"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", | |||
@@ -1070,12 +1040,9 @@ exports[`should render correctly: default 1`] = ` | |||
}, | |||
"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", | |||
@@ -1168,12 +1135,9 @@ exports[`should render correctly: default 1`] = ` | |||
}, | |||
"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", | |||
@@ -1269,12 +1233,9 @@ exports[`should render correctly: deleted assignee 1`] = ` | |||
}, | |||
"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", | |||
@@ -1382,12 +1343,9 @@ exports[`should render correctly: deleted assignee 1`] = ` | |||
}, | |||
"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", | |||
@@ -1472,12 +1430,9 @@ exports[`should render correctly: deleted assignee 1`] = ` | |||
}, | |||
"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", | |||
@@ -1570,12 +1525,9 @@ exports[`should render correctly: deleted assignee 1`] = ` | |||
}, | |||
"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", | |||
@@ -1684,12 +1636,9 @@ exports[`should render correctly: show success modal 1`] = ` | |||
}, | |||
"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", | |||
@@ -1797,12 +1746,9 @@ exports[`should render correctly: show success modal 1`] = ` | |||
}, | |||
"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", | |||
@@ -1887,12 +1833,9 @@ exports[`should render correctly: show success modal 1`] = ` | |||
}, | |||
"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", | |||
@@ -1985,12 +1928,9 @@ exports[`should render correctly: show success modal 1`] = ` | |||
}, | |||
"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", | |||
@@ -2086,12 +2026,9 @@ exports[`should render correctly: unassigned 1`] = ` | |||
}, | |||
"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", | |||
@@ -2199,12 +2136,9 @@ exports[`should render correctly: unassigned 1`] = ` | |||
}, | |||
"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", | |||
@@ -2289,12 +2223,9 @@ exports[`should render correctly: unassigned 1`] = ` | |||
}, | |||
"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", | |||
@@ -2387,12 +2318,9 @@ exports[`should render correctly: unassigned 1`] = ` | |||
}, | |||
"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", |
@@ -8,22 +8,37 @@ exports[`should render correctly: fix 1`] = ` | |||
tabs={ | |||
Array [ | |||
Object { | |||
"content": "", | |||
"descriptionSections": Array [], | |||
"key": "code", | |||
"label": "hotspots.tabs.code", | |||
}, | |||
Object { | |||
"content": "<p>This a <strong>strong</strong> message about risk !</p>", | |||
"descriptionSections": Array [ | |||
Object { | |||
"content": "cause", | |||
"key": "root_cause", | |||
}, | |||
], | |||
"key": "risk", | |||
"label": "hotspots.tabs.risk_description", | |||
}, | |||
Object { | |||
"content": "<p>This a <strong>strong</strong> message about vulnerability !</p>", | |||
"descriptionSections": Array [ | |||
Object { | |||
"content": "assess", | |||
"key": "assess_the_problem", | |||
}, | |||
], | |||
"key": "vulnerability", | |||
"label": "hotspots.tabs.vulnerability_description", | |||
}, | |||
Object { | |||
"content": "<p>This a <strong>strong</strong> message about fixing !</p>", | |||
"descriptionSections": Array [ | |||
Object { | |||
"content": "how", | |||
"key": "how_to_fix", | |||
}, | |||
], | |||
"key": "fix", | |||
"label": "hotspots.tabs.fix_recommendations", | |||
}, | |||
@@ -33,36 +48,11 @@ exports[`should render correctly: fix 1`] = ` | |||
<div | |||
className="bordered huge-spacer-bottom" | |||
> | |||
<div | |||
className="padded hidden" | |||
> | |||
<div> | |||
CodeTabContent | |||
</div> | |||
</div> | |||
<div | |||
className="markdown big-padded hidden" | |||
dangerouslySetInnerHTML={ | |||
Object { | |||
"__html": "<p>This a <strong>strong</strong> message about risk !</p>", | |||
} | |||
} | |||
key="risk" | |||
/> | |||
<div | |||
className="markdown big-padded hidden" | |||
dangerouslySetInnerHTML={ | |||
Object { | |||
"__html": "<p>This a <strong>strong</strong> message about vulnerability !</p>", | |||
} | |||
} | |||
key="vulnerability" | |||
/> | |||
<div | |||
className="markdown big-padded" | |||
dangerouslySetInnerHTML={ | |||
Object { | |||
"__html": "<p>This a <strong>strong</strong> message about fixing !</p>", | |||
"__html": "how", | |||
} | |||
} | |||
key="fix" | |||
@@ -79,22 +69,37 @@ exports[`should render correctly: risk 1`] = ` | |||
tabs={ | |||
Array [ | |||
Object { | |||
"content": "", | |||
"descriptionSections": Array [], | |||
"key": "code", | |||
"label": "hotspots.tabs.code", | |||
}, | |||
Object { | |||
"content": "<p>This a <strong>strong</strong> message about risk !</p>", | |||
"descriptionSections": Array [ | |||
Object { | |||
"content": "cause", | |||
"key": "root_cause", | |||
}, | |||
], | |||
"key": "risk", | |||
"label": "hotspots.tabs.risk_description", | |||
}, | |||
Object { | |||
"content": "<p>This a <strong>strong</strong> message about vulnerability !</p>", | |||
"descriptionSections": Array [ | |||
Object { | |||
"content": "assess", | |||
"key": "assess_the_problem", | |||
}, | |||
], | |||
"key": "vulnerability", | |||
"label": "hotspots.tabs.vulnerability_description", | |||
}, | |||
Object { | |||
"content": "<p>This a <strong>strong</strong> message about fixing !</p>", | |||
"descriptionSections": Array [ | |||
Object { | |||
"content": "how", | |||
"key": "how_to_fix", | |||
}, | |||
], | |||
"key": "fix", | |||
"label": "hotspots.tabs.fix_recommendations", | |||
}, | |||
@@ -111,33 +116,6 @@ exports[`should render correctly: risk 1`] = ` | |||
CodeTabContent | |||
</div> | |||
</div> | |||
<div | |||
className="markdown big-padded hidden" | |||
dangerouslySetInnerHTML={ | |||
Object { | |||
"__html": "<p>This a <strong>strong</strong> message about risk !</p>", | |||
} | |||
} | |||
key="risk" | |||
/> | |||
<div | |||
className="markdown big-padded hidden" | |||
dangerouslySetInnerHTML={ | |||
Object { | |||
"__html": "<p>This a <strong>strong</strong> message about vulnerability !</p>", | |||
} | |||
} | |||
key="vulnerability" | |||
/> | |||
<div | |||
className="markdown big-padded hidden" | |||
dangerouslySetInnerHTML={ | |||
Object { | |||
"__html": "<p>This a <strong>strong</strong> message about fixing !</p>", | |||
} | |||
} | |||
key="fix" | |||
/> | |||
</div> | |||
</Fragment> | |||
`; | |||
@@ -150,22 +128,37 @@ exports[`should render correctly: vulnerability 1`] = ` | |||
tabs={ | |||
Array [ | |||
Object { | |||
"content": "", | |||
"descriptionSections": Array [], | |||
"key": "code", | |||
"label": "hotspots.tabs.code", | |||
}, | |||
Object { | |||
"content": "<p>This a <strong>strong</strong> message about risk !</p>", | |||
"descriptionSections": Array [ | |||
Object { | |||
"content": "cause", | |||
"key": "root_cause", | |||
}, | |||
], | |||
"key": "risk", | |||
"label": "hotspots.tabs.risk_description", | |||
}, | |||
Object { | |||
"content": "<p>This a <strong>strong</strong> message about vulnerability !</p>", | |||
"descriptionSections": Array [ | |||
Object { | |||
"content": "assess", | |||
"key": "assess_the_problem", | |||
}, | |||
], | |||
"key": "vulnerability", | |||
"label": "hotspots.tabs.vulnerability_description", | |||
}, | |||
Object { | |||
"content": "<p>This a <strong>strong</strong> message about fixing !</p>", | |||
"descriptionSections": Array [ | |||
Object { | |||
"content": "how", | |||
"key": "how_to_fix", | |||
}, | |||
], | |||
"key": "fix", | |||
"label": "hotspots.tabs.fix_recommendations", | |||
}, | |||
@@ -175,40 +168,15 @@ exports[`should render correctly: vulnerability 1`] = ` | |||
<div | |||
className="bordered huge-spacer-bottom" | |||
> | |||
<div | |||
className="padded hidden" | |||
> | |||
<div> | |||
CodeTabContent | |||
</div> | |||
</div> | |||
<div | |||
className="markdown big-padded hidden" | |||
dangerouslySetInnerHTML={ | |||
Object { | |||
"__html": "<p>This a <strong>strong</strong> message about risk !</p>", | |||
} | |||
} | |||
key="risk" | |||
/> | |||
<div | |||
className="markdown big-padded" | |||
dangerouslySetInnerHTML={ | |||
Object { | |||
"__html": "<p>This a <strong>strong</strong> message about vulnerability !</p>", | |||
"__html": "assess", | |||
} | |||
} | |||
key="vulnerability" | |||
/> | |||
<div | |||
className="markdown big-padded hidden" | |||
dangerouslySetInnerHTML={ | |||
Object { | |||
"__html": "<p>This a <strong>strong</strong> message about fixing !</p>", | |||
} | |||
} | |||
key="fix" | |||
/> | |||
</div> | |||
</Fragment> | |||
`; | |||
@@ -221,22 +189,37 @@ exports[`should render correctly: with comments or changelog element 1`] = ` | |||
tabs={ | |||
Array [ | |||
Object { | |||
"content": "", | |||
"descriptionSections": Array [], | |||
"key": "code", | |||
"label": "hotspots.tabs.code", | |||
}, | |||
Object { | |||
"content": "<p>This a <strong>strong</strong> message about risk !</p>", | |||
"descriptionSections": Array [ | |||
Object { | |||
"content": "cause", | |||
"key": "root_cause", | |||
}, | |||
], | |||
"key": "risk", | |||
"label": "hotspots.tabs.risk_description", | |||
}, | |||
Object { | |||
"content": "<p>This a <strong>strong</strong> message about vulnerability !</p>", | |||
"descriptionSections": Array [ | |||
Object { | |||
"content": "assess", | |||
"key": "assess_the_problem", | |||
}, | |||
], | |||
"key": "vulnerability", | |||
"label": "hotspots.tabs.vulnerability_description", | |||
}, | |||
Object { | |||
"content": "<p>This a <strong>strong</strong> message about fixing !</p>", | |||
"descriptionSections": Array [ | |||
Object { | |||
"content": "how", | |||
"key": "how_to_fix", | |||
}, | |||
], | |||
"key": "fix", | |||
"label": "hotspots.tabs.fix_recommendations", | |||
}, | |||
@@ -253,33 +236,6 @@ exports[`should render correctly: with comments or changelog element 1`] = ` | |||
CodeTabContent | |||
</div> | |||
</div> | |||
<div | |||
className="markdown big-padded hidden" | |||
dangerouslySetInnerHTML={ | |||
Object { | |||
"__html": "<p>This a <strong>strong</strong> message about risk !</p>", | |||
} | |||
} | |||
key="risk" | |||
/> | |||
<div | |||
className="markdown big-padded hidden" | |||
dangerouslySetInnerHTML={ | |||
Object { | |||
"__html": "<p>This a <strong>strong</strong> message about vulnerability !</p>", | |||
} | |||
} | |||
key="vulnerability" | |||
/> | |||
<div | |||
className="markdown big-padded hidden" | |||
dangerouslySetInnerHTML={ | |||
Object { | |||
"__html": "<p>This a <strong>strong</strong> message about fixing !</p>", | |||
} | |||
} | |||
key="fix" | |||
/> | |||
</div> | |||
</Fragment> | |||
`; |
@@ -83,12 +83,9 @@ exports[`should render correctly: closed 1`] = ` | |||
}, | |||
"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", | |||
@@ -223,12 +220,9 @@ exports[`should render correctly: open 1`] = ` | |||
}, | |||
"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", | |||
@@ -363,12 +357,9 @@ exports[`should render correctly: readonly 1`] = ` | |||
}, | |||
"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", |
@@ -0,0 +1,121 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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'; | |||
import RadioToggle from '../controls/RadioToggle'; | |||
import { sanitizeString } from '../../helpers/sanitize'; | |||
import { RuleDescriptionSection } from '../../apps/coding-rules/rule'; | |||
import OtherContextOption from './OtherContextOption'; | |||
const OTHERS_KEY = 'others'; | |||
interface Props { | |||
description: RuleDescriptionSection[]; | |||
} | |||
interface State { | |||
contexts: RuleDescriptionContextDisplay[]; | |||
selectedContext: RuleDescriptionContextDisplay; | |||
} | |||
interface RuleDescriptionContextDisplay { | |||
displayName: string; | |||
content: string; | |||
key: string; | |||
} | |||
export default class RuleContextDescription extends React.PureComponent<Props, State> { | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = this.computeState(props.description); | |||
} | |||
componentDidUpdate(prevProps: Props) { | |||
if (prevProps.description !== this.props.description) { | |||
this.setState(this.computeState(this.props.description)); | |||
} | |||
} | |||
computeState = (descriptions: RuleDescriptionSection[]) => { | |||
const contexts = descriptions | |||
.map(sec => ({ | |||
displayName: sec.context?.displayName || '', | |||
content: sec.content, | |||
key: sec.key.toString() | |||
})) | |||
.filter(sec => sec.displayName !== '') | |||
.sort((a, b) => a.displayName.localeCompare(b.displayName)); | |||
if (contexts.length > 0) { | |||
contexts.push({ | |||
displayName: translate('coding_rules.description_context_other'), | |||
content: '', | |||
key: OTHERS_KEY | |||
}); | |||
} | |||
return { | |||
contexts, | |||
selectedContext: contexts[0] | |||
}; | |||
}; | |||
handleToggleContext = (value: string) => { | |||
const { contexts } = this.state; | |||
const selected = contexts.find(ctxt => ctxt.displayName === value); | |||
if (selected) { | |||
this.setState({ selectedContext: selected }); | |||
} | |||
}; | |||
render() { | |||
const { contexts } = this.state; | |||
const { selectedContext } = this.state; | |||
const options = contexts.map(ctxt => ({ | |||
label: ctxt.displayName, | |||
value: ctxt.displayName | |||
})); | |||
return ( | |||
<div className="rules-context-description"> | |||
<h2 className="rule-contexts-title"> | |||
{translate('coding_rules.description_context_title')} | |||
</h2> | |||
<RadioToggle | |||
className="big-spacer-bottom" | |||
name="filter" | |||
onCheck={this.handleToggleContext} | |||
options={options} | |||
value={selectedContext.displayName} | |||
/> | |||
{selectedContext.key === OTHERS_KEY ? ( | |||
<OtherContextOption /> | |||
) : ( | |||
<div | |||
/* eslint-disable-next-line react/no-danger */ | |||
dangerouslySetInnerHTML={{ __html: sanitizeString(selectedContext.content) }} | |||
/> | |||
)} | |||
</div> | |||
); | |||
} | |||
} |
@@ -113,9 +113,6 @@ export function mockHotspotRule(overrides?: Partial<HotspotRule>): HotspotRule { | |||
return { | |||
key: 'squid:S2077', | |||
name: 'That rule', | |||
fixRecommendations: '<p>This a <strong>strong</strong> message about fixing !</p>', | |||
riskDescription: '<p>This a <strong>strong</strong> message about risk !</p>', | |||
vulnerabilityDescription: '<p>This a <strong>strong</strong> message about vulnerability !</p>', | |||
vulnerabilityProbability: RiskExposure.HIGH, | |||
securityCategory: 'sql-injection', | |||
...overrides |
@@ -18,6 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { To } from 'react-router-dom'; | |||
import { RuleDescriptionSections } from '../apps/coding-rules/rule'; | |||
import { DocumentationEntry } from '../apps/documentation/utils'; | |||
import { Exporter, Profile } from '../apps/quality-profiles/types'; | |||
import { Location, Router } from '../components/hoc/withRouter'; | |||
@@ -47,7 +48,6 @@ import { | |||
ProfileInheritanceDetails, | |||
Rule, | |||
RuleActivation, | |||
RuleDescriptionSections, | |||
RuleDetails, | |||
RuleParameter, | |||
SysInfoBase, |
@@ -121,12 +121,9 @@ export interface HotspotUpdate extends HotspotUpdateFields { | |||
} | |||
export interface HotspotRule { | |||
fixRecommendations?: string; | |||
key: string; | |||
name: string; | |||
riskDescription?: string; | |||
securityCategory: string; | |||
vulnerabilityDescription?: string; | |||
vulnerabilityProbability: RiskExposure; | |||
} | |||
@@ -18,6 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { RuleDescriptionSection } from '../apps/coding-rules/rule'; | |||
import { ComponentQualifier } from './component'; | |||
import { UserActive, UserBase } from './users'; | |||
@@ -562,19 +563,6 @@ export interface RuleActivation { | |||
severity: string; | |||
} | |||
export enum RuleDescriptionSections { | |||
DEFAULT = 'default', | |||
INTRODUCTION = 'introduction', | |||
ROOT_CAUSE = 'root_cause', | |||
ASSESS_THE_PROBLEM = 'assess_the_problem', | |||
HOW_TO_FIX = 'how_to_fix', | |||
RESOURCES = 'resources' | |||
} | |||
export interface RuleDescriptionSection { | |||
key: RuleDescriptionSections; | |||
content: string; | |||
} | |||
export interface RulesUpdateRequest { | |||
key: string; | |||
markdown_description?: string; |
@@ -1912,6 +1912,8 @@ coding_rules.description_section.title.assess_the_problem=Assess the risk? | |||
coding_rules.description_section.title.how_to_fix=How to fix it? | |||
coding_rules.description_section.title.resources=Resources | |||
coding_rules.description_context_title=Which component or framework contains the issue? | |||
coding_rules.description_context_other=Other | |||
#------------------------------------------------------------------------------ | |||
# |