@@ -20,11 +20,14 @@ | |||
import { difference } from 'lodash'; | |||
import * as React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import { Link } from 'react-router'; | |||
import { ButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons'; | |||
import Select from 'sonar-ui-common/components/controls/Select'; | |||
import SimpleModal from 'sonar-ui-common/components/controls/SimpleModal'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { Profile } from '../../../api/quality-profiles'; | |||
import DisableableSelectOption from '../../../components/common/DisableableSelectOption'; | |||
import { getQualityProfileUrl } from '../../../helpers/urls'; | |||
import { Store } from '../../../store/rootReducer'; | |||
export interface AddLanguageModalProps { | |||
@@ -52,7 +55,11 @@ export function AddLanguageModal(props: AddLanguageModalProps) { | |||
const profileOptions = | |||
language !== undefined | |||
? profilesByLanguage[language].map(p => ({ value: p.key, label: p.name })) | |||
? profilesByLanguage[language].map(p => ({ | |||
value: p.key, | |||
label: p.name, | |||
disabled: p.activeRuleCount === 0 | |||
})) | |||
: []; | |||
return ( | |||
@@ -102,6 +109,30 @@ export function AddLanguageModal(props: AddLanguageModalProps) { | |||
id="profiles" | |||
onChange={({ value }: { value: string }) => setSelected({ language, key: value })} | |||
options={profileOptions} | |||
optionRenderer={option => ( | |||
<DisableableSelectOption | |||
option={option} | |||
disabledReason={translate( | |||
'project_quality_profile.add_language_modal.no_active_rules' | |||
)} | |||
tooltipOverlay={ | |||
<> | |||
<p> | |||
{translate( | |||
'project_quality_profile.add_language_modal.profile_unavailable_no_active_rules' | |||
)} | |||
</p> | |||
{option.label && language && ( | |||
<Link to={getQualityProfileUrl(option.label, language)}> | |||
{translate( | |||
'project_quality_profile.add_language_modal.go_to_profile' | |||
)} | |||
</Link> | |||
)} | |||
</> | |||
} | |||
/> | |||
)} | |||
value={key} | |||
/> | |||
</div> |
@@ -18,6 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import { ButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons'; | |||
import Radio from 'sonar-ui-common/components/controls/Radio'; | |||
import Select from 'sonar-ui-common/components/controls/Select'; | |||
@@ -25,6 +26,8 @@ import SimpleModal from 'sonar-ui-common/components/controls/SimpleModal'; | |||
import { Alert } from 'sonar-ui-common/components/ui/Alert'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { Profile } from '../../../api/quality-profiles'; | |||
import DisableableSelectOption from '../../../components/common/DisableableSelectOption'; | |||
import { getQualityProfileUrl } from '../../../helpers/urls'; | |||
import BuiltInQualityProfileBadge from '../../quality-profiles/components/BuiltInQualityProfileBadge'; | |||
import { USE_SYSTEM_DEFAULT } from '../constants'; | |||
@@ -54,7 +57,11 @@ export default function SetQualityProfileModal(props: SetQualityProfileModalProp | |||
'project_quality_profile.change_lang_X_profile', | |||
currentProfile.languageName | |||
); | |||
const profileOptions = availableProfiles.map(p => ({ value: p.key, label: p.name })); | |||
const profileOptions = availableProfiles.map(p => ({ | |||
value: p.key, | |||
label: p.name, | |||
disabled: p.activeRuleCount === 0 | |||
})); | |||
const hasSelectedSysDefault = selected === USE_SYSTEM_DEFAULT; | |||
const hasChanged = usesDefault ? !hasSelectedSysDefault : selected !== currentProfile.key; | |||
const needsReanalysis = !component.qualityProfiles?.some(p => | |||
@@ -118,7 +125,34 @@ export default function SetQualityProfileModal(props: SetQualityProfileModalProp | |||
disabled={submitting || hasSelectedSysDefault} | |||
onChange={({ value }: { value: string }) => setSelected(value)} | |||
options={profileOptions} | |||
optionRenderer={option => <span>{option.label}</span>} | |||
optionRenderer={option => ( | |||
<DisableableSelectOption | |||
option={option} | |||
disabledReason={translate( | |||
'project_quality_profile.add_language_modal.no_active_rules' | |||
)} | |||
tooltipOverlay={ | |||
<> | |||
<p> | |||
{translate( | |||
'project_quality_profile.add_language_modal.profile_unavailable_no_active_rules' | |||
)} | |||
</p> | |||
{option.label && ( | |||
<Link | |||
to={getQualityProfileUrl( | |||
option.label, | |||
currentProfile.language | |||
)}> | |||
{translate( | |||
'project_quality_profile.add_language_modal.go_to_profile' | |||
)} | |||
</Link> | |||
)} | |||
</> | |||
} | |||
/> | |||
)} | |||
value={!hasSelectedSysDefault ? selected : currentProfile.key} | |||
/> | |||
</div> |
@@ -28,6 +28,27 @@ it('should render correctly', () => { | |||
expect(diveIntoSimpleModal(shallowRender())).toMatchSnapshot('default'); | |||
}); | |||
it('should render select options correctly', () => { | |||
return new Promise<void>((resolve, reject) => { | |||
const wrapper = shallowRender(); | |||
const langOnChange = getLanguageSelect(wrapper).props().onChange; | |||
if (!langOnChange) { | |||
reject(); | |||
return; | |||
} | |||
langOnChange({ value: 'js' }); | |||
const render = getProfileSelect(wrapper).props().optionRenderer; | |||
if (!render) { | |||
reject(); | |||
return; | |||
} | |||
expect(render({ value: 'bar', label: 'Profile 1' })).toMatchSnapshot('default'); | |||
resolve(); | |||
}); | |||
}); | |||
it('should correctly handle changes', () => { | |||
const onSubmit = jest.fn(); | |||
const wrapper = shallowRender({ onSubmit }); | |||
@@ -50,6 +71,9 @@ it('should correctly handle changes', () => { | |||
// Should now show 2 available profiles. | |||
profileSelect = getProfileSelect(wrapper); | |||
expect(profileSelect.props().options).toHaveLength(2); | |||
expect(profileSelect.props().options).toEqual( | |||
expect.arrayContaining([expect.objectContaining({ disabled: true })]) | |||
); | |||
// Choose 1 profile. | |||
const profileChange = profileSelect.props().onChange; | |||
@@ -100,7 +124,7 @@ function shallowRender(props: Partial<AddLanguageModalProps> = {}) { | |||
onSubmit={jest.fn()} | |||
profilesByLanguage={{ | |||
css: [ | |||
mockQualityProfile({ key: 'css', name: 'CSS' }), | |||
mockQualityProfile({ key: 'css', name: 'CSS', activeRuleCount: 0 }), | |||
mockQualityProfile({ key: 'css2', name: 'CSS 2' }) | |||
], | |||
ts: [mockQualityProfile({ key: 'ts', name: 'TS' })], |
@@ -32,12 +32,16 @@ it('should render correctly', () => { | |||
}); | |||
it('should render select options correctly', () => { | |||
const wrapper = shallowRender(); | |||
const render = wrapper.find(Select).props().optionRenderer; | |||
expect(render).toBeDefined(); | |||
expect(render!({ value: 'bar', label: 'Profile 1' })).toMatchSnapshot('default'); | |||
return new Promise<void>((resolve, reject) => { | |||
const wrapper = shallowRender(); | |||
const render = wrapper.find(Select).props().optionRenderer; | |||
if (!render) { | |||
reject(); | |||
return; | |||
} | |||
expect(render({ value: 'bar', label: 'Profile 1' })).toMatchSnapshot('default'); | |||
resolve(); | |||
}); | |||
}); | |||
it('should correctly handle changes', () => { | |||
@@ -90,7 +94,7 @@ function shallowRender(props: Partial<SetQualityProfileModalProps> = {}, dive = | |||
<SetQualityProfileModal | |||
availableProfiles={[ | |||
mockQualityProfile({ key: 'foo', isDefault: true, language: 'js' }), | |||
mockQualityProfile({ key: 'bar', language: 'js' }) | |||
mockQualityProfile({ key: 'bar', language: 'js', activeRuleCount: 0 }) | |||
]} | |||
component={mockComponent({ qualityProfiles: [{ key: 'foo', name: 'Foo', language: 'js' }] })} | |||
currentProfile={mockQualityProfile({ key: 'foo', language: 'js' })} |
@@ -67,6 +67,7 @@ Array [ | |||
disabled={true} | |||
id="profiles" | |||
onChange={[Function]} | |||
optionRenderer={[Function]} | |||
options={Array []} | |||
/> | |||
</div> | |||
@@ -89,3 +90,37 @@ Array [ | |||
</form>, | |||
] | |||
`; | |||
exports[`should render select options correctly: default 1`] = ` | |||
<DisableableSelectOption | |||
disabledReason="project_quality_profile.add_language_modal.no_active_rules" | |||
option={ | |||
Object { | |||
"label": "Profile 1", | |||
"value": "bar", | |||
} | |||
} | |||
tooltipOverlay={ | |||
<React.Fragment> | |||
<p> | |||
project_quality_profile.add_language_modal.profile_unavailable_no_active_rules | |||
</p> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/profiles/show", | |||
"query": Object { | |||
"language": "js", | |||
"name": "Profile 1", | |||
}, | |||
} | |||
} | |||
> | |||
project_quality_profile.add_language_modal.go_to_profile | |||
</Link> | |||
</React.Fragment> | |||
} | |||
/> | |||
`; |
@@ -77,10 +77,12 @@ Array [ | |||
options={ | |||
Array [ | |||
Object { | |||
"disabled": false, | |||
"label": "name", | |||
"value": "foo", | |||
}, | |||
Object { | |||
"disabled": true, | |||
"label": "name", | |||
"value": "bar", | |||
}, | |||
@@ -189,10 +191,12 @@ Array [ | |||
options={ | |||
Array [ | |||
Object { | |||
"disabled": false, | |||
"label": "name", | |||
"value": "foo", | |||
}, | |||
Object { | |||
"disabled": true, | |||
"label": "name", | |||
"value": "bar", | |||
}, | |||
@@ -301,10 +305,12 @@ Array [ | |||
options={ | |||
Array [ | |||
Object { | |||
"disabled": false, | |||
"label": "name", | |||
"value": "foo", | |||
}, | |||
Object { | |||
"disabled": true, | |||
"label": "name", | |||
"value": "bar", | |||
}, | |||
@@ -342,7 +348,35 @@ Array [ | |||
`; | |||
exports[`should render select options correctly: default 1`] = ` | |||
<span> | |||
Profile 1 | |||
</span> | |||
<DisableableSelectOption | |||
disabledReason="project_quality_profile.add_language_modal.no_active_rules" | |||
option={ | |||
Object { | |||
"label": "Profile 1", | |||
"value": "bar", | |||
} | |||
} | |||
tooltipOverlay={ | |||
<React.Fragment> | |||
<p> | |||
project_quality_profile.add_language_modal.profile_unavailable_no_active_rules | |||
</p> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/profiles/show", | |||
"query": Object { | |||
"language": "js", | |||
"name": "Profile 1", | |||
}, | |||
} | |||
} | |||
> | |||
project_quality_profile.add_language_modal.go_to_profile | |||
</Link> | |||
</React.Fragment> | |||
} | |||
/> | |||
`; |
@@ -22,6 +22,7 @@ import ActionsDropdown, { | |||
ActionsDropdownDivider, | |||
ActionsDropdownItem | |||
} from 'sonar-ui-common/components/controls/ActionsDropdown'; | |||
import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { | |||
changeProfileParent, | |||
@@ -145,7 +146,12 @@ export class ProfileActions extends React.PureComponent<Props, State> { | |||
}; | |||
handleSetDefaultClick = () => { | |||
setDefaultProfile(this.props.profile).then(this.props.updateProfiles, () => {}); | |||
const { profile } = this.props; | |||
if (profile.activeRuleCount > 0) { | |||
setDefaultProfile(profile).then(this.props.updateProfiles, () => { | |||
/* noop */ | |||
}); | |||
} | |||
}; | |||
profileActionPerformed = (name: string) => { | |||
@@ -181,6 +187,8 @@ export class ProfileActions extends React.PureComponent<Props, State> { | |||
activation: 'false' | |||
}); | |||
const hasNoActiveRules = profile.activeRuleCount === 0; | |||
return ( | |||
<> | |||
<ActionsDropdown className={this.props.className}> | |||
@@ -231,13 +239,24 @@ export class ProfileActions extends React.PureComponent<Props, State> { | |||
</ActionsDropdownItem> | |||
)} | |||
{actions.setAsDefault && ( | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__set-as-default" | |||
onClick={this.handleSetDefaultClick}> | |||
{translate('set_as_default')} | |||
</ActionsDropdownItem> | |||
)} | |||
{actions.setAsDefault && | |||
(hasNoActiveRules ? ( | |||
<li> | |||
<Tooltip | |||
placement="left" | |||
overlay={translate('quality_profiles.cannot_set_default_no_rules')}> | |||
<span className="it__quality-profiles__set-as-default text-muted-2"> | |||
{translate('set_as_default')} | |||
</span> | |||
</Tooltip> | |||
</li> | |||
) : ( | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__set-as-default" | |||
onClick={this.handleSetDefaultClick}> | |||
{translate('set_as_default')} | |||
</ActionsDropdownItem> | |||
))} | |||
{actions.delete && <ActionsDropdownDivider />} | |||
@@ -323,6 +323,22 @@ it('should correctly set a profile as the default', async () => { | |||
expect(updateProfiles).toHaveBeenCalled(); | |||
}); | |||
it('should not allow to set a profile as the default if the profile has no active rules', async () => { | |||
const profile = mockQualityProfile({ | |||
activeRuleCount: 0, | |||
actions: { | |||
setAsDefault: true | |||
} | |||
}); | |||
const wrapper = shallowRender({ profile }); | |||
wrapper.instance().handleSetDefaultClick(); | |||
await waitAndUpdate(wrapper); | |||
expect(setDefaultProfile).not.toHaveBeenCalled(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
function shallowRender(props: Partial<ProfileActions['props']> = {}) { | |||
const router = mockRouter(); | |||
return shallow<ProfileActions>( |
@@ -355,3 +355,43 @@ exports[`renders correctly: rename modal 1`] = ` | |||
/> | |||
</Fragment> | |||
`; | |||
exports[`should not allow to set a profile as the default if the profile has no active rules 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__backup" | |||
download="key.xml" | |||
to="/api/qualityprofiles/backup?language=js&qualityProfile=name" | |||
> | |||
backup_verb | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__compare" | |||
to={ | |||
Object { | |||
"pathname": "/profiles/compare", | |||
"query": Object { | |||
"language": "js", | |||
"name": "name", | |||
}, | |||
} | |||
} | |||
> | |||
compare | |||
</ActionsDropdownItem> | |||
<li> | |||
<Tooltip | |||
overlay="quality_profiles.cannot_set_default_no_rules" | |||
placement="left" | |||
> | |||
<span | |||
className="it__quality-profiles__set-as-default text-muted-2" | |||
> | |||
set_as_default | |||
</span> | |||
</Tooltip> | |||
</li> | |||
</ActionsDropdown> | |||
</Fragment> | |||
`; |
@@ -18,6 +18,8 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { Alert } from 'sonar-ui-common/components/ui/Alert'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { Exporter, Profile } from '../types'; | |||
import ProfileExporters from './ProfileExporters'; | |||
import ProfileInheritance from './ProfileInheritance'; | |||
@@ -25,14 +27,14 @@ import ProfilePermissions from './ProfilePermissions'; | |||
import ProfileProjects from './ProfileProjects'; | |||
import ProfileRules from './ProfileRules'; | |||
interface Props { | |||
export interface ProfileDetailsProps { | |||
exporters: Exporter[]; | |||
profile: Profile; | |||
profiles: Profile[]; | |||
updateProfiles: () => Promise<void>; | |||
} | |||
export default function ProfileDetails(props: Props) { | |||
export default function ProfileDetails(props: ProfileDetailsProps) { | |||
const { profile } = props; | |||
return ( | |||
<div> | |||
@@ -45,6 +47,17 @@ export default function ProfileDetails(props: Props) { | |||
)} | |||
</div> | |||
<div className="quality-profile-grid-right"> | |||
{profile.activeRuleCount === 0 && (profile.projectCount || profile.isDefault) && ( | |||
<Alert className="big-spacer-bottom" variant="warning"> | |||
{profile.projectCount !== undefined && | |||
profile.projectCount > 0 && | |||
translate('quality_profiles.warning.used_by_projects_no_rules')} | |||
{!profile.projectCount && | |||
profile.isDefault && | |||
translate('quality_profiles.warning.is_default_no_rules')} | |||
</Alert> | |||
)} | |||
<ProfileInheritance | |||
profile={profile} | |||
profiles={props.profiles} |
@@ -21,6 +21,7 @@ import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import { Button } from 'sonar-ui-common/components/controls/buttons'; | |||
import ListFooter from 'sonar-ui-common/components/controls/ListFooter'; | |||
import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { getProfileProjects } from '../../../api/quality-profiles'; | |||
@@ -131,6 +132,11 @@ export default class ProfileProjects extends React.PureComponent<Props, State> { | |||
} | |||
const { projects } = this.state; | |||
const { profile } = this.props; | |||
if (profile.activeRuleCount === 0 && projects.length === 0) { | |||
return <div>{translate('quality_profiles.cannot_associate_projects_no_rules')}</div>; | |||
} | |||
if (projects.length === 0) { | |||
return <div>{translate('quality_profiles.no_projects_associated_to_profile')}</div>; | |||
@@ -159,13 +165,24 @@ export default class ProfileProjects extends React.PureComponent<Props, State> { | |||
render() { | |||
const { profile } = this.props; | |||
const hasNoActiveRules = profile.activeRuleCount === 0; | |||
return ( | |||
<div className="boxed-group quality-profile-projects"> | |||
{profile.actions && profile.actions.associateProjects && ( | |||
<div className="boxed-group-actions"> | |||
<Button className="js-change-projects" onClick={this.handleChangeClick}> | |||
{translate('quality_profiles.change_projects')} | |||
</Button> | |||
<Tooltip | |||
overlay={ | |||
hasNoActiveRules | |||
? translate('quality_profiles.cannot_associate_projects_no_rules') | |||
: null | |||
}> | |||
<Button | |||
className="js-change-projects" | |||
onClick={this.handleChangeClick} | |||
disabled={hasNoActiveRules}> | |||
{translate('quality_profiles.change_projects')} | |||
</Button> | |||
</Tooltip> | |||
</div> | |||
)} | |||
@@ -19,31 +19,37 @@ | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { Profile } from '../../types'; | |||
import ProfileDetails from '../ProfileDetails'; | |||
import { mockQualityProfile } from '../../../../helpers/testMocks'; | |||
import ProfileDetails, { ProfileDetailsProps } from '../ProfileDetails'; | |||
it('renders without permissions', () => { | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect( | |||
shallow( | |||
<ProfileDetails | |||
exporters={[]} | |||
profile={{} as Profile} | |||
profiles={[]} | |||
updateProfiles={jest.fn()} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('renders with edit permission', () => { | |||
shallowRender({ profile: mockQualityProfile({ actions: { edit: true } }) }) | |||
).toMatchSnapshot('edit permissions'); | |||
expect( | |||
shallowRender({ | |||
profile: mockQualityProfile({ activeRuleCount: 0, projectCount: 0 }) | |||
}) | |||
).toMatchSnapshot('no active rules (same as default)'); | |||
expect( | |||
shallow( | |||
<ProfileDetails | |||
exporters={[]} | |||
profile={{ actions: { edit: true } } as Profile} | |||
profiles={[]} | |||
updateProfiles={jest.fn()} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
shallowRender({ | |||
profile: mockQualityProfile({ projectCount: 0, isDefault: true, activeRuleCount: 0 }) | |||
}) | |||
).toMatchSnapshot('is default profile, no active rules'); | |||
expect( | |||
shallowRender({ profile: mockQualityProfile({ projectCount: 10, activeRuleCount: 0 }) }) | |||
).toMatchSnapshot('projects associated, no active rules'); | |||
}); | |||
function shallowRender(props: Partial<ProfileDetailsProps> = {}) { | |||
return shallow<ProfileDetailsProps>( | |||
<ProfileDetails | |||
exporters={[]} | |||
profile={mockQualityProfile()} | |||
profiles={[]} | |||
updateProfiles={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -41,9 +41,26 @@ jest.mock('../../../../api/quality-profiles', () => ({ | |||
it('should render correctly', async () => { | |||
const wrapper = shallowRender(); | |||
expect(wrapper).toMatchSnapshot(); | |||
expect(wrapper).toMatchSnapshot('loading'); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot(); | |||
expect(wrapper).toMatchSnapshot('default'); | |||
wrapper.setProps({ | |||
profile: mockQualityProfile({ actions: { associateProjects: false } }) | |||
}); | |||
expect(wrapper).toMatchSnapshot('no rights'); | |||
wrapper.setProps({ | |||
profile: mockQualityProfile({ | |||
projectCount: 0, | |||
activeRuleCount: 0, | |||
actions: { associateProjects: true } | |||
}) | |||
}); | |||
expect(wrapper).toMatchSnapshot('no active rules, but associated projects'); | |||
wrapper.setProps({ | |||
profile: mockQualityProfile({ activeRuleCount: 0, actions: { associateProjects: true } }) | |||
}); | |||
wrapper.setState({ projects: [] }); | |||
expect(wrapper).toMatchSnapshot('no active rules, no associated projects'); | |||
}); | |||
it('should open and close the form', async () => { |
@@ -1,6 +1,98 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders with edit permission 1`] = ` | |||
exports[`should render correctly: default 1`] = ` | |||
<div> | |||
<div | |||
className="quality-profile-grid" | |||
> | |||
<div | |||
className="quality-profile-grid-left" | |||
> | |||
<ProfileRules | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
/> | |||
<ProfileExporters | |||
exporters={Array []} | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
/> | |||
</div> | |||
<div | |||
className="quality-profile-grid-right" | |||
> | |||
<ProfileInheritance | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
profiles={Array []} | |||
updateProfiles={[MockFunction]} | |||
/> | |||
<ProfileProjects | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly: edit permissions 1`] = ` | |||
<div> | |||
<div | |||
className="quality-profile-grid" | |||
@@ -14,6 +106,18 @@ exports[`renders with edit permission 1`] = ` | |||
"actions": Object { | |||
"edit": true, | |||
}, | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
/> | |||
@@ -24,6 +128,18 @@ exports[`renders with edit permission 1`] = ` | |||
"actions": Object { | |||
"edit": true, | |||
}, | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
/> | |||
@@ -33,6 +149,18 @@ exports[`renders with edit permission 1`] = ` | |||
"actions": Object { | |||
"edit": true, | |||
}, | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
/> | |||
@@ -46,6 +174,18 @@ exports[`renders with edit permission 1`] = ` | |||
"actions": Object { | |||
"edit": true, | |||
}, | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
profiles={Array []} | |||
@@ -57,6 +197,18 @@ exports[`renders with edit permission 1`] = ` | |||
"actions": Object { | |||
"edit": true, | |||
}, | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
/> | |||
@@ -65,7 +217,7 @@ exports[`renders with edit permission 1`] = ` | |||
</div> | |||
`; | |||
exports[`renders without permissions 1`] = ` | |||
exports[`should render correctly: is default profile, no active rules 1`] = ` | |||
<div> | |||
<div | |||
className="quality-profile-grid" | |||
@@ -74,23 +226,279 @@ exports[`renders without permissions 1`] = ` | |||
className="quality-profile-grid-left" | |||
> | |||
<ProfileRules | |||
profile={Object {}} | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 0, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": true, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 0, | |||
} | |||
} | |||
/> | |||
<ProfileExporters | |||
exporters={Array []} | |||
profile={Object {}} | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 0, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": true, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 0, | |||
} | |||
} | |||
/> | |||
</div> | |||
<div | |||
className="quality-profile-grid-right" | |||
> | |||
<Alert | |||
className="big-spacer-bottom" | |||
variant="warning" | |||
> | |||
quality_profiles.warning.is_default_no_rules | |||
</Alert> | |||
<ProfileInheritance | |||
profile={Object {}} | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 0, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": true, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 0, | |||
} | |||
} | |||
profiles={Array []} | |||
updateProfiles={[MockFunction]} | |||
/> | |||
<ProfileProjects | |||
profile={Object {}} | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 0, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": true, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 0, | |||
} | |||
} | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly: no active rules (same as default) 1`] = ` | |||
<div> | |||
<div | |||
className="quality-profile-grid" | |||
> | |||
<div | |||
className="quality-profile-grid-left" | |||
> | |||
<ProfileRules | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 0, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 0, | |||
} | |||
} | |||
/> | |||
<ProfileExporters | |||
exporters={Array []} | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 0, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 0, | |||
} | |||
} | |||
/> | |||
</div> | |||
<div | |||
className="quality-profile-grid-right" | |||
> | |||
<ProfileInheritance | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 0, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 0, | |||
} | |||
} | |||
profiles={Array []} | |||
updateProfiles={[MockFunction]} | |||
/> | |||
<ProfileProjects | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 0, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 0, | |||
} | |||
} | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly: projects associated, no active rules 1`] = ` | |||
<div> | |||
<div | |||
className="quality-profile-grid" | |||
> | |||
<div | |||
className="quality-profile-grid-left" | |||
> | |||
<ProfileRules | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 0, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 10, | |||
} | |||
} | |||
/> | |||
<ProfileExporters | |||
exporters={Array []} | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 0, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 10, | |||
} | |||
} | |||
/> | |||
</div> | |||
<div | |||
className="quality-profile-grid-right" | |||
> | |||
<Alert | |||
className="big-spacer-bottom" | |||
variant="warning" | |||
> | |||
quality_profiles.warning.used_by_projects_no_rules | |||
</Alert> | |||
<ProfileInheritance | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 0, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 10, | |||
} | |||
} | |||
profiles={Array []} | |||
updateProfiles={[MockFunction]} | |||
/> | |||
<ProfileProjects | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 0, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 10, | |||
} | |||
} | |||
/> | |||
</div> | |||
</div> |
@@ -1,18 +1,92 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
exports[`should render correctly: default 1`] = ` | |||
<div | |||
className="boxed-group quality-profile-projects" | |||
> | |||
<div | |||
className="boxed-group-actions" | |||
> | |||
<Button | |||
className="js-change-projects" | |||
onClick={[Function]} | |||
<Tooltip | |||
overlay={null} | |||
> | |||
quality_profiles.change_projects | |||
</Button> | |||
<Button | |||
className="js-change-projects" | |||
disabled={false} | |||
onClick={[Function]} | |||
> | |||
quality_profiles.change_projects | |||
</Button> | |||
</Tooltip> | |||
</div> | |||
<header | |||
className="boxed-group-header" | |||
> | |||
<h2> | |||
projects | |||
</h2> | |||
</header> | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<ul> | |||
<li | |||
className="spacer-top js-profile-project" | |||
data-key="org.sonarsource.xml:xml" | |||
key="org.sonarsource.xml:xml" | |||
> | |||
<Link | |||
className="link-with-icon" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "org.sonarsource.xml:xml", | |||
}, | |||
} | |||
} | |||
> | |||
<QualifierIcon | |||
qualifier="TRK" | |||
/> | |||
<span> | |||
SonarXML | |||
</span> | |||
</Link> | |||
</li> | |||
</ul> | |||
<ListFooter | |||
count={1} | |||
loadMore={[Function]} | |||
ready={true} | |||
total={10} | |||
/> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly: loading 1`] = ` | |||
<div | |||
className="boxed-group quality-profile-projects" | |||
> | |||
<div | |||
className="boxed-group-actions" | |||
> | |||
<Tooltip | |||
overlay={null} | |||
> | |||
<Button | |||
className="js-change-projects" | |||
disabled={false} | |||
onClick={[Function]} | |||
> | |||
quality_profiles.change_projects | |||
</Button> | |||
</Tooltip> | |||
</div> | |||
<header | |||
className="boxed-group-header" | |||
@@ -31,19 +105,24 @@ exports[`should render correctly 1`] = ` | |||
</div> | |||
`; | |||
exports[`should render correctly 2`] = ` | |||
exports[`should render correctly: no active rules, but associated projects 1`] = ` | |||
<div | |||
className="boxed-group quality-profile-projects" | |||
> | |||
<div | |||
className="boxed-group-actions" | |||
> | |||
<Button | |||
className="js-change-projects" | |||
onClick={[Function]} | |||
<Tooltip | |||
overlay="quality_profiles.cannot_associate_projects_no_rules" | |||
> | |||
quality_profiles.change_projects | |||
</Button> | |||
<Button | |||
className="js-change-projects" | |||
disabled={true} | |||
onClick={[Function]} | |||
> | |||
quality_profiles.change_projects | |||
</Button> | |||
</Tooltip> | |||
</div> | |||
<header | |||
className="boxed-group-header" | |||
@@ -94,3 +173,93 @@ exports[`should render correctly 2`] = ` | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly: no active rules, no associated projects 1`] = ` | |||
<div | |||
className="boxed-group quality-profile-projects" | |||
> | |||
<div | |||
className="boxed-group-actions" | |||
> | |||
<Tooltip | |||
overlay="quality_profiles.cannot_associate_projects_no_rules" | |||
> | |||
<Button | |||
className="js-change-projects" | |||
disabled={true} | |||
onClick={[Function]} | |||
> | |||
quality_profiles.change_projects | |||
</Button> | |||
</Tooltip> | |||
</div> | |||
<header | |||
className="boxed-group-header" | |||
> | |||
<h2> | |||
projects | |||
</h2> | |||
</header> | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<div> | |||
quality_profiles.cannot_associate_projects_no_rules | |||
</div> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly: no rights 1`] = ` | |||
<div | |||
className="boxed-group quality-profile-projects" | |||
> | |||
<header | |||
className="boxed-group-header" | |||
> | |||
<h2> | |||
projects | |||
</h2> | |||
</header> | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<ul> | |||
<li | |||
className="spacer-top js-profile-project" | |||
data-key="org.sonarsource.xml:xml" | |||
key="org.sonarsource.xml:xml" | |||
> | |||
<Link | |||
className="link-with-icon" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "org.sonarsource.xml:xml", | |||
}, | |||
} | |||
} | |||
> | |||
<QualifierIcon | |||
qualifier="TRK" | |||
/> | |||
<span> | |||
SonarXML | |||
</span> | |||
</Link> | |||
</li> | |||
</ul> | |||
<ListFooter | |||
count={1} | |||
loadMore={[Function]} | |||
ready={true} | |||
total={10} | |||
/> | |||
</div> | |||
</div> | |||
`; |
@@ -0,0 +1,44 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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 Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
export interface DisableableSelectOptionProps { | |||
option: { label?: string; value?: string | number | boolean; disabled?: boolean }; | |||
tooltipOverlay: React.ReactNode; | |||
disabledReason?: string; | |||
} | |||
export default function DisableableSelectOption(props: DisableableSelectOptionProps) { | |||
const { option, tooltipOverlay, disabledReason } = props; | |||
const label = option.label || option.value; | |||
return option.disabled ? ( | |||
<Tooltip overlay={tooltipOverlay} placement="left"> | |||
<span> | |||
{label} | |||
{disabledReason !== undefined && ( | |||
<em className="small little-spacer-left">({disabledReason})</em> | |||
)} | |||
</span> | |||
</Tooltip> | |||
) : ( | |||
<span>{label}</span> | |||
); | |||
} |
@@ -0,0 +1,47 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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 { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import DisableableSelectOption, { DisableableSelectOptionProps } from '../DisableableSelectOption'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ option: { value: 'baz' } })).toMatchSnapshot('no label'); | |||
expect(shallowRender({ option: { label: 'Bar', value: 'bar', disabled: true } })).toMatchSnapshot( | |||
'disabled' | |||
); | |||
expect( | |||
shallowRender({ | |||
option: { label: 'Bar', value: 'bar', disabled: true }, | |||
disabledReason: 'bar baz' | |||
}) | |||
).toMatchSnapshot('disabled, with explanation'); | |||
}); | |||
function shallowRender(props: Partial<DisableableSelectOptionProps> = {}) { | |||
return shallow<DisableableSelectOptionProps>( | |||
<DisableableSelectOption | |||
option={{ label: 'Foo', value: 'foo' }} | |||
tooltipOverlay="foo bar" | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,42 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<span> | |||
Foo | |||
</span> | |||
`; | |||
exports[`should render correctly: disabled 1`] = ` | |||
<Tooltip | |||
overlay="foo bar" | |||
placement="left" | |||
> | |||
<span> | |||
Bar | |||
</span> | |||
</Tooltip> | |||
`; | |||
exports[`should render correctly: disabled, with explanation 1`] = ` | |||
<Tooltip | |||
overlay="foo bar" | |||
placement="left" | |||
> | |||
<span> | |||
Bar | |||
<em | |||
className="small little-spacer-left" | |||
> | |||
( | |||
bar baz | |||
) | |||
</em> | |||
</span> | |||
</Tooltip> | |||
`; | |||
exports[`should render correctly: no label 1`] = ` | |||
<span> | |||
baz | |||
</span> | |||
`; |
@@ -1503,6 +1503,9 @@ project_quality_profile.add_language.action=Add language | |||
project_quality_profile.add_language_modal.title=Add a language | |||
project_quality_profile.add_language_modal.choose_language=Choose a language | |||
project_quality_profile.add_language_modal.choose_profile=Choose a profile | |||
project_quality_profile.add_language_modal.no_active_rules=this profile has no active rules | |||
project_quality_profile.add_language_modal.profile_unavailable_no_active_rules=This profile has no active rules, and cannot be used. Please enable at least 1 rule before using this profile. | |||
project_quality_profile.add_language_modal.go_to_profile=Go to Quality Profile | |||
project_quality_profile.change_profile=Change profile | |||
#------------------------------------------------------------------------------ | |||
@@ -1564,6 +1567,10 @@ quality_profiles.are_you_sure_want_delete_profile_x_and_descendants=Are you sure | |||
quality_profiles.this_profile_has_descendants=This profile has descendants. | |||
quality_profiles.profile_inheritance=Inheritance | |||
quality_profiles.no_projects_associated_to_profile=No projects are explicitly associated to the profile. | |||
quality_profiles.cannot_associate_projects_no_rules=You must activate at least 1 rule before you can assign projects to this profile. | |||
quality_profiles.cannot_set_default_no_rules=You must activate at least 1 rule before you can make this profile the default profile. | |||
quality_profiles.warning.used_by_projects_no_rules=The current profile is used on several projects, but it has no active rules. Please activate at least 1 rule for this profile. | |||
quality_profiles.warning.is_default_no_rules=The current profile is the default profile, but it has no active rules. Please activate at least 1 rule for this profile. | |||
quality_profiles.parent=Parent: | |||
quality_profiles.parameter_set_to=Parameter {0} set to {1} | |||
quality_profiles.x_rules_only_in={0} rules only in |