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 {
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 (
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>
* 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';
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';
'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 =>
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>
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 });
// 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;
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' })],
});
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', () => {
<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' })}
disabled={true}
id="profiles"
onChange={[Function]}
+ optionRenderer={[Function]}
options={Array []}
/>
</div>
</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>
+ }
+/>
+`;
options={
Array [
Object {
+ "disabled": false,
"label": "name",
"value": "foo",
},
Object {
+ "disabled": true,
"label": "name",
"value": "bar",
},
options={
Array [
Object {
+ "disabled": false,
"label": "name",
"value": "foo",
},
Object {
+ "disabled": true,
"label": "name",
"value": "bar",
},
options={
Array [
Object {
+ "disabled": false,
"label": "name",
"value": "foo",
},
Object {
+ "disabled": true,
"label": "name",
"value": "bar",
},
`;
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>
+ }
+/>
`;
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,
};
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) => {
activation: 'false'
});
+ const hasNoActiveRules = profile.activeRuleCount === 0;
+
return (
<>
<ActionsDropdown className={this.props.className}>
</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 />}
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>(
/>
</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>
+`;
* 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';
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>
)}
</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}
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';
}
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>;
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>
)}
*/
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}
+ />
+ );
+}
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 () => {
// 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"
"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,
}
}
/>
"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,
}
}
/>
"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,
}
}
/>
"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 []}
"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,
}
}
/>
</div>
`;
-exports[`renders without permissions 1`] = `
+exports[`should render correctly: is default profile, no active rules 1`] = `
<div>
<div
className="quality-profile-grid"
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>
// 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"
</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"
</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>
+`;
--- /dev/null
+/*
+ * 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>
+ );
+}
--- /dev/null
+/*
+ * 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}
+ />
+ );
+}
--- /dev/null
+// 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>
+`;
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
#------------------------------------------------------------------------------
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