@@ -189,18 +189,19 @@ export default class RuleDetailsDescription extends React.PureComponent<Props, S | |||
render() { | |||
const { ruleDetails } = this.props; | |||
const hasDescription = !ruleDetails.isExternal || ruleDetails.type !== 'UNKNOWN'; | |||
return ( | |||
<div className="js-rule-description"> | |||
{ruleDetails.isExternal ? ( | |||
<div className="coding-rules-detail-description rule-desc markdown"> | |||
{translateWithParameters('issue.external_issue_description', ruleDetails.name)} | |||
</div> | |||
) : ( | |||
{hasDescription ? ( | |||
<div | |||
className="coding-rules-detail-description rule-desc markdown" | |||
dangerouslySetInnerHTML={{ __html: ruleDetails.htmlDesc || '' }} | |||
/> | |||
) : ( | |||
<div className="coding-rules-detail-description rule-desc markdown"> | |||
{translateWithParameters('issue.external_issue_description', ruleDetails.name)} | |||
</div> | |||
)} | |||
{!ruleDetails.templateKey && ( |
@@ -28,7 +28,7 @@ import LinkIcon from '../../../components/icons-components/LinkIcon'; | |||
import RuleScopeIcon from '../../../components/icons-components/RuleScopeIcon'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import DocTooltip from '../../../components/docs/DocTooltip'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import IssueTypeIcon from '../../../components/ui/IssueTypeIcon'; | |||
import SeverityHelper from '../../../components/shared/SeverityHelper'; | |||
import Dropdown from '../../../components/controls/Dropdown'; | |||
@@ -47,6 +47,8 @@ interface Props { | |||
ruleDetails: RuleDetails; | |||
} | |||
const EXTERNAL_RULE_REPO_PREFIX = 'external_'; | |||
export default class RuleDetailsMeta extends React.PureComponent<Props> { | |||
renderType = () => { | |||
const { ruleDetails } = this.props; | |||
@@ -207,8 +209,29 @@ export default class RuleDetailsMeta extends React.PureComponent<Props> { | |||
); | |||
}; | |||
renderExternalBadge = () => { | |||
const { ruleDetails } = this.props; | |||
if (!ruleDetails.repo) { | |||
return null; | |||
} | |||
const engine = ruleDetails.repo.replace(new RegExp(`^${EXTERNAL_RULE_REPO_PREFIX}`), ''); | |||
if (!engine) { | |||
return null; | |||
} | |||
return ( | |||
<Tooltip overlay={translateWithParameters('coding_rules.external_rule.engine', engine)}> | |||
<li className="coding-rules-detail-property"> | |||
<div className="outline-badge badge-tiny-height spacer-left vertical-text-top"> | |||
{engine} | |||
</div> | |||
</li> | |||
</Tooltip> | |||
); | |||
}; | |||
render() { | |||
const { ruleDetails } = this.props; | |||
const hasTypeData = !ruleDetails.isExternal || ruleDetails.type !== 'UNKNOWN'; | |||
return ( | |||
<div className="js-rule-meta"> | |||
<header className="page-header"> | |||
@@ -230,18 +253,27 @@ export default class RuleDetailsMeta extends React.PureComponent<Props> { | |||
</h3> | |||
</header> | |||
{!ruleDetails.isExternal && ( | |||
{hasTypeData && ( | |||
<ul className="coding-rules-detail-properties"> | |||
{this.renderType()} | |||
{this.renderSeverity()} | |||
{this.renderStatus()} | |||
{this.renderScope()} | |||
{!ruleDetails.isExternal && ( | |||
<> | |||
{this.renderStatus()} | |||
{this.renderScope()} | |||
</> | |||
)} | |||
{this.renderTags()} | |||
{this.renderCreationDate()} | |||
{!ruleDetails.isExternal && this.renderCreationDate()} | |||
{this.renderRepository()} | |||
{this.renderTemplate()} | |||
{this.renderParentTemplate()} | |||
{this.renderRemediation()} | |||
{!ruleDetails.isExternal && ( | |||
<> | |||
{this.renderTemplate()} | |||
{this.renderParentTemplate()} | |||
{this.renderRemediation()} | |||
</> | |||
)} | |||
{ruleDetails.isExternal && this.renderExternalBadge()} | |||
</ul> | |||
)} | |||
</div> |
@@ -0,0 +1,90 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { shallow } from 'enzyme'; | |||
import RuleDetailsDescription from '../RuleDetailsDescription'; | |||
import { click, change, waitAndUpdate } from '../../../../helpers/testUtils'; | |||
jest.mock('../../../../api/rules', () => ({ | |||
updateRule: jest.fn().mockResolvedValue('updatedrule') | |||
})); | |||
const RULE = { | |||
key: 'squid:S1133', | |||
repo: 'squid', | |||
name: 'Deprecated code should be removed', | |||
createdAt: '2013-07-26T09:40:51+0200', | |||
htmlDesc: '<p>Html Description</p>', | |||
mdNote: 'Md Note', | |||
severity: 'INFO', | |||
status: 'READY', | |||
lang: 'java', | |||
langName: 'Java', | |||
type: 'CODE_SMELL' | |||
}; | |||
const EXTERNAL_RULE = { | |||
key: 'external_xoo:OneExternalIssuePerLine', | |||
repo: 'external_xoo', | |||
name: 'xoo:OneExternalIssuePerLine', | |||
status: 'READY', | |||
isExternal: true, | |||
type: 'UNKNOWN' | |||
}; | |||
const EXTERNAL_RULE_WITH_DATA = { | |||
key: 'external_xoo:OneExternalIssueWithDetailsPerLine', | |||
repo: 'external_xoo', | |||
name: 'One external issue per line', | |||
createdAt: '2018-05-31T11:19:51+0200', | |||
htmlDesc: '<p>Html Description</p>', | |||
status: 'READY', | |||
isExternal: true, | |||
type: 'BUG' | |||
}; | |||
it('should display correctly', () => { | |||
expect(getWrapper()).toMatchSnapshot(); | |||
expect(getWrapper({ ruleDetails: EXTERNAL_RULE })).toMatchSnapshot(); | |||
expect(getWrapper({ ruleDetails: EXTERNAL_RULE_WITH_DATA })).toMatchSnapshot(); | |||
}); | |||
it('should add extra description', async () => { | |||
const onChange = jest.fn(); | |||
const wrapper = getWrapper({ canWrite: true, onChange }); | |||
click(wrapper.find('#coding-rules-detail-extend-description')); | |||
expect(wrapper.find('textarea').exists()).toBeTruthy(); | |||
change(wrapper.find('textarea'), 'new description'); | |||
click(wrapper.find('#coding-rules-detail-extend-description-submit')); | |||
await waitAndUpdate(wrapper); | |||
expect(onChange).toBeCalledWith('updatedrule'); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow( | |||
<RuleDetailsDescription | |||
canWrite={false} | |||
onChange={jest.fn()} | |||
organization={undefined} | |||
ruleDetails={RULE} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -20,33 +20,61 @@ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import RuleDetailsMeta from '../RuleDetailsMeta'; | |||
import { RuleDetails } from '../../../../app/types'; | |||
import { RuleScope } from '../../../../app/types'; | |||
import RuleDetailsTagsPopup from '../RuleDetailsTagsPopup'; | |||
const ruleDetails: RuleDetails = { | |||
createdAt: '', | |||
repo: '', | |||
key: 'key', | |||
lang: '', | |||
langName: '', | |||
name: '', | |||
severity: '', | |||
status: '', | |||
type: '' | |||
const RULE = { | |||
key: 'squid:S1133', | |||
repo: 'squid', | |||
name: 'Deprecated code should be removed', | |||
createdAt: '2013-07-26T09:40:51+0200', | |||
severity: 'INFO', | |||
status: 'READY', | |||
lang: 'java', | |||
langName: 'Java', | |||
scope: RuleScope.Main, | |||
type: 'CODE_SMELL' | |||
}; | |||
const EXTERNAL_RULE = { | |||
key: 'external_xoo:OneExternalIssuePerLine', | |||
repo: 'external_xoo', | |||
name: 'xoo:OneExternalIssuePerLine', | |||
createdAt: '2018-05-31T11:22:13+0200', | |||
status: 'READY', | |||
scope: RuleScope.All, | |||
isExternal: true, | |||
type: 'UNKNOWN' | |||
}; | |||
const EXTERNAL_RULE_WITH_DATA = { | |||
key: 'external_xoo:OneExternalIssueWithDetailsPerLine', | |||
repo: 'external_xoo', | |||
name: 'One external issue per line', | |||
createdAt: '2018-05-31T11:19:51+0200', | |||
severity: 'MAJOR', | |||
status: 'READY', | |||
tags: ['tag'], | |||
lang: 'xoo', | |||
langName: 'Xoo', | |||
scope: RuleScope.All, | |||
isExternal: true, | |||
type: 'BUG' | |||
}; | |||
it('should display right meta info', () => { | |||
expect(getWrapper()).toMatchSnapshot(); | |||
expect( | |||
getWrapper({ hideSimilarRulesFilter: true, ruleDetails: EXTERNAL_RULE }) | |||
).toMatchSnapshot(); | |||
expect( | |||
getWrapper({ hideSimilarRulesFilter: true, ruleDetails: EXTERNAL_RULE_WITH_DATA }) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should edit tags', () => { | |||
const onTagsChange = jest.fn(); | |||
const wrapper = shallow( | |||
<RuleDetailsMeta | |||
canWrite={true} | |||
onFilterChange={jest.fn()} | |||
onTagsChange={onTagsChange} | |||
organization={undefined} | |||
referencedRepositories={{}} | |||
ruleDetails={ruleDetails} | |||
/> | |||
); | |||
const wrapper = getWrapper({ onTagsChange }); | |||
expect(wrapper.find('[data-meta="tags"]')).toMatchSnapshot(); | |||
const overlay = wrapper | |||
.find('[data-meta="tags"]') | |||
@@ -56,3 +84,17 @@ it('should edit tags', () => { | |||
overlay.props.setTags(['foo', 'bar']); | |||
expect(onTagsChange).toBeCalledWith(['foo', 'bar']); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow( | |||
<RuleDetailsMeta | |||
canWrite={true} | |||
onFilterChange={jest.fn()} | |||
onTagsChange={jest.fn()} | |||
organization={undefined} | |||
referencedRepositories={{}} | |||
ruleDetails={RULE} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,64 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display correctly 1`] = ` | |||
<div | |||
className="js-rule-description" | |||
> | |||
<div | |||
className="coding-rules-detail-description rule-desc markdown" | |||
dangerouslySetInnerHTML={ | |||
Object { | |||
"__html": "<p>Html Description</p>", | |||
} | |||
} | |||
/> | |||
<div | |||
className="coding-rules-detail-description coding-rules-detail-description-extra" | |||
> | |||
<div | |||
id="coding-rules-detail-description-extra" | |||
/> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should display correctly 2`] = ` | |||
<div | |||
className="js-rule-description" | |||
> | |||
<div | |||
className="coding-rules-detail-description rule-desc markdown" | |||
> | |||
issue.external_issue_description.xoo:OneExternalIssuePerLine | |||
</div> | |||
<div | |||
className="coding-rules-detail-description coding-rules-detail-description-extra" | |||
> | |||
<div | |||
id="coding-rules-detail-description-extra" | |||
/> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should display correctly 3`] = ` | |||
<div | |||
className="js-rule-description" | |||
> | |||
<div | |||
className="coding-rules-detail-description rule-desc markdown" | |||
dangerouslySetInnerHTML={ | |||
Object { | |||
"__html": "<p>Html Description</p>", | |||
} | |||
} | |||
/> | |||
<div | |||
className="coding-rules-detail-description coding-rules-detail-description-extra" | |||
> | |||
<div | |||
id="coding-rules-detail-description-extra" | |||
/> | |||
</div> | |||
</div> | |||
`; |
@@ -1,5 +1,288 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display right meta info 1`] = ` | |||
<div | |||
className="js-rule-meta" | |||
> | |||
<header | |||
className="page-header" | |||
> | |||
<div | |||
className="pull-right" | |||
> | |||
<span | |||
className="note text-middle" | |||
> | |||
squid:S1133 | |||
</span> | |||
<Link | |||
className="coding-rules-detail-permalink link-no-underline spacer-left text-middle" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"query": Object { | |||
"open": "squid:S1133", | |||
"rule_key": "squid:S1133", | |||
}, | |||
} | |||
} | |||
> | |||
<LinkIcon /> | |||
</Link> | |||
<SimilarRulesFilter | |||
onFilterChange={[MockFunction]} | |||
rule={ | |||
Object { | |||
"createdAt": "2013-07-26T09:40:51+0200", | |||
"key": "squid:S1133", | |||
"lang": "java", | |||
"langName": "Java", | |||
"name": "Deprecated code should be removed", | |||
"repo": "squid", | |||
"scope": "MAIN", | |||
"severity": "INFO", | |||
"status": "READY", | |||
"type": "CODE_SMELL", | |||
} | |||
} | |||
/> | |||
</div> | |||
<h3 | |||
className="page-title coding-rules-detail-header" | |||
> | |||
<big> | |||
Deprecated code should be removed | |||
</big> | |||
</h3> | |||
</header> | |||
<ul | |||
className="coding-rules-detail-properties" | |||
> | |||
<Tooltip | |||
overlay="coding_rules.type.tooltip.CODE_SMELL" | |||
> | |||
<li | |||
className="coding-rules-detail-property" | |||
data-meta="type" | |||
> | |||
<IssueTypeIcon | |||
className="little-spacer-right" | |||
query="CODE_SMELL" | |||
/> | |||
issue.type.CODE_SMELL | |||
</li> | |||
</Tooltip> | |||
<Tooltip | |||
overlay="default_severity" | |||
> | |||
<li | |||
className="coding-rules-detail-property" | |||
data-meta="severity" | |||
> | |||
<SeverityHelper | |||
className="display-inline-flex-center" | |||
severity="INFO" | |||
/> | |||
</li> | |||
</Tooltip> | |||
<React.Fragment> | |||
<Tooltip | |||
overlay="coding_rules.scope.title" | |||
> | |||
<li | |||
className="coding-rules-detail-property" | |||
> | |||
<RuleScopeIcon | |||
className="little-spacer-right" | |||
/> | |||
coding_rules.scope.MAIN | |||
</li> | |||
</Tooltip> | |||
</React.Fragment> | |||
<li | |||
className="coding-rules-detail-property" | |||
data-meta="tags" | |||
> | |||
<Dropdown | |||
closeOnClick={false} | |||
closeOnClickOutside={true} | |||
overlay={ | |||
<RuleDetailsTagsPopup | |||
organization={undefined} | |||
setTags={[MockFunction]} | |||
sysTags={Array []} | |||
tags={Array []} | |||
/> | |||
} | |||
overlayPlacement="bottom-left" | |||
> | |||
<Button | |||
className="button-link" | |||
> | |||
<TagsList | |||
allowUpdate={true} | |||
tags={ | |||
Array [ | |||
"coding_rules.no_tags", | |||
] | |||
} | |||
/> | |||
</Button> | |||
</Dropdown> | |||
</li> | |||
<li | |||
className="coding-rules-detail-property" | |||
data-meta="available-since" | |||
> | |||
<span | |||
className="little-spacer-right" | |||
> | |||
coding_rules.available_since | |||
</span> | |||
<DateFormatter | |||
date="2013-07-26T09:40:51+0200" | |||
/> | |||
</li> | |||
<React.Fragment /> | |||
</ul> | |||
</div> | |||
`; | |||
exports[`should display right meta info 2`] = ` | |||
<div | |||
className="js-rule-meta" | |||
> | |||
<header | |||
className="page-header" | |||
> | |||
<div | |||
className="pull-right" | |||
> | |||
<span | |||
className="note text-middle" | |||
> | |||
external_xoo:OneExternalIssuePerLine | |||
</span> | |||
</div> | |||
<h3 | |||
className="page-title coding-rules-detail-header" | |||
> | |||
<big> | |||
xoo:OneExternalIssuePerLine | |||
</big> | |||
</h3> | |||
</header> | |||
</div> | |||
`; | |||
exports[`should display right meta info 3`] = ` | |||
<div | |||
className="js-rule-meta" | |||
> | |||
<header | |||
className="page-header" | |||
> | |||
<div | |||
className="pull-right" | |||
> | |||
<span | |||
className="note text-middle" | |||
> | |||
external_xoo:OneExternalIssueWithDetailsPerLine | |||
</span> | |||
</div> | |||
<h3 | |||
className="page-title coding-rules-detail-header" | |||
> | |||
<big> | |||
One external issue per line | |||
</big> | |||
</h3> | |||
</header> | |||
<ul | |||
className="coding-rules-detail-properties" | |||
> | |||
<Tooltip | |||
overlay="coding_rules.type.tooltip.BUG" | |||
> | |||
<li | |||
className="coding-rules-detail-property" | |||
data-meta="type" | |||
> | |||
<IssueTypeIcon | |||
className="little-spacer-right" | |||
query="BUG" | |||
/> | |||
issue.type.BUG | |||
</li> | |||
</Tooltip> | |||
<Tooltip | |||
overlay="default_severity" | |||
> | |||
<li | |||
className="coding-rules-detail-property" | |||
data-meta="severity" | |||
> | |||
<SeverityHelper | |||
className="display-inline-flex-center" | |||
severity="MAJOR" | |||
/> | |||
</li> | |||
</Tooltip> | |||
<li | |||
className="coding-rules-detail-property" | |||
data-meta="tags" | |||
> | |||
<Dropdown | |||
closeOnClick={false} | |||
closeOnClickOutside={true} | |||
overlay={ | |||
<RuleDetailsTagsPopup | |||
organization={undefined} | |||
setTags={[MockFunction]} | |||
sysTags={Array []} | |||
tags={ | |||
Array [ | |||
"tag", | |||
] | |||
} | |||
/> | |||
} | |||
overlayPlacement="bottom-left" | |||
> | |||
<Button | |||
className="button-link" | |||
> | |||
<TagsList | |||
allowUpdate={true} | |||
tags={ | |||
Array [ | |||
"tag", | |||
] | |||
} | |||
/> | |||
</Button> | |||
</Dropdown> | |||
</li> | |||
<Tooltip | |||
overlay="coding_rules.external_rule.engine.xoo" | |||
> | |||
<li | |||
className="coding-rules-detail-property" | |||
> | |||
<div | |||
className="outline-badge badge-tiny-height spacer-left vertical-text-top" | |||
> | |||
xoo | |||
</div> | |||
</li> | |||
</Tooltip> | |||
</ul> | |||
</div> | |||
`; | |||
exports[`should edit tags 1`] = ` | |||
<li | |||
className="coding-rules-detail-property" |
@@ -1334,6 +1334,8 @@ coding_rules.scope.MAIN=Main sources | |||
coding_rules.scope.TEST=Test sources | |||
coding_rules.scope.ALL=Main and Test sources | |||
coding_rules.external_rule.engine=Rule provided by an external rule engine: {0} | |||
#------------------------------------------------------------------------------ | |||
# | |||
# EMAIL CONFIGURATION |