import React from 'react';
import { Link } from 'react-router';
import { keyBy } from 'lodash';
-import ProfileRulesRow from './ProfileRulesRow';
+import ProfileRulesRowOfType from './ProfileRulesRowOfType';
+import ProfileRulesRowTotal from './ProfileRulesRowTotal';
+import ProfileRulesDeprecatedWarning from './ProfileRulesDeprecatedWarning';
+import ProfileRulesSonarWayComparison from './ProfileRulesSonarWayComparison';
import { searchRules, takeFacet } from '../../../api/rules';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
-import { getRulesUrl, getDeprecatedActiveRulesUrl } from '../../../helpers/urls';
-import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
+import { getQualityProfiles } from '../../../api/quality-profiles';
+import { getRulesUrl } from '../../../helpers/urls';
+import { translate } from '../../../helpers/l10n';
import type { Profile } from '../propTypes';
const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];
};
type State = {
- total: ?number,
activatedTotal: ?number,
+ activatedByType?: { [string]: ?{ val: string, count: ?number } },
allByType?: { [string]: ?{ val: string, count: ?number } },
- activatedByType?: { [string]: ?{ val: string, count: ?number } }
+ compareToSonarWay: ?{ profile: string, profileName: string, missingRuleCount: number },
+ loading: boolean,
+ total: ?number
};
export default class ProfileRules extends React.PureComponent {
mounted: boolean;
props: Props;
state: State = {
- total: null,
activatedTotal: null,
+ activatedByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val'),
allByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val'),
- activatedByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val')
+ compareToSonarWay: null,
+ loading: true,
+ total: null
};
componentDidMount() {
this.mounted = false;
}
+ loadProfile() {
+ if (this.props.profile.isBuiltIn) {
+ return Promise.resolve(null);
+ }
+ return getQualityProfiles({
+ compareToSonarWay: true,
+ profile: this.props.profile.key
+ });
+ }
+
loadAllRules() {
return searchRules({
languages: this.props.profile.language,
}
loadRules() {
- Promise.all([this.loadAllRules(), this.loadActivatedRules()]).then(responses => {
+ this.setState({ loading: true });
+ Promise.all([
+ this.loadAllRules(),
+ this.loadActivatedRules(),
+ this.loadProfile()
+ ]).then(responses => {
if (this.mounted) {
- const [allRules, activatedRules] = responses;
+ const [allRules, activatedRules, showProfile] = responses;
this.setState({
- total: allRules.total,
activatedTotal: activatedRules.total,
allByType: keyBy(takeFacet(allRules, 'types'), 'val'),
- activatedByType: keyBy(takeFacet(activatedRules, 'types'), 'val')
+ activatedByType: keyBy(takeFacet(activatedRules, 'types'), 'val'),
+ compareToSonarWay: showProfile && showProfile.compareToSonarWay,
+ loading: false,
+ total: allRules.total
});
}
});
}
- getTooltip(count: ?number, total: ?number) {
- if (count == null || total == null) {
- return '';
- }
-
- return translateWithParameters(
- 'quality_profiles.x_activated_out_of_y',
- formatMeasure(count, 'INT'),
- formatMeasure(total, 'INT')
- );
- }
-
- getTooltipForType(type: string) {
- if (
- this.state.activatedByType &&
- this.state.activatedByType[type] &&
- this.state.allByType &&
- this.state.allByType[type]
- ) {
- const { count } = this.state.activatedByType[type];
- const total = this.state.allByType[type].count;
- return this.getTooltip(count, total);
- }
- }
-
- renderActiveTitle() {
- return (
- <strong>
- {translate('total')}
- </strong>
- );
- }
-
- renderActiveCount() {
- const rulesUrl = getRulesUrl(
- {
- qprofile: this.props.profile.key,
- activation: 'true'
- },
- this.props.organization
- );
-
- if (this.state.activatedTotal == null) {
- return null;
- }
-
- return (
- <Link to={rulesUrl}>
- <strong>
- {formatMeasure(this.state.activatedTotal, 'SHORT_INT')}
- </strong>
- </Link>
- );
- }
-
- renderActiveTotal() {
- const rulesUrl = getRulesUrl(
- {
- qprofile: this.props.profile.key,
- activation: 'false'
- },
- this.props.organization
- );
-
- if (this.state.total == null || this.state.activatedTotal == null) {
- return null;
- }
-
- if (this.state.total === this.state.activatedTotal) {
- return <span className="note text-muted">0</span>;
- }
-
- return (
- <Link to={rulesUrl} className="small text-muted">
- <strong>
- {formatMeasure(this.state.total - this.state.activatedTotal, 'SHORT_INT')}
- </strong>
- </Link>
- );
- }
-
- renderTitleForType(type: string) {
- return (
- <span>
- <IssueTypeIcon query={type} className="little-spacer-right" />
- {translate('issue.type', type, 'plural')}
- </span>
- );
- }
-
- renderCountForType(type: string) {
- const rulesUrl = getRulesUrl(
- {
- qprofile: this.props.profile.key,
- activation: 'true',
- types: type
- },
- this.props.organization
- );
-
- const count = this.state.activatedByType && this.state.activatedByType[type]
+ getRulesCountForType(type: string): ?number {
+ return this.state.activatedByType && this.state.activatedByType[type]
? this.state.activatedByType[type].count
: null;
-
- if (count == null) {
- return null;
- }
-
- return (
- <Link to={rulesUrl}>
- {formatMeasure(count, 'SHORT_INT')}
- </Link>
- );
}
- renderTotalForType(type: string) {
- const rulesUrl = getRulesUrl(
- {
- qprofile: this.props.profile.key,
- activation: 'false',
- types: type
- },
- this.props.organization
- );
-
- const count = this.state.activatedByType && this.state.activatedByType[type]
- ? this.state.activatedByType[type].count
- : null;
-
- const total = this.state.allByType && this.state.allByType[type]
+ getRulesTotalForType(type: string): ?number {
+ return this.state.allByType && this.state.allByType[type]
? this.state.allByType[type].count
: null;
-
- if (count == null || total == null) {
- return null;
- }
-
- if (total === count) {
- return <span className="note text-muted">0</span>;
- }
-
- return (
- <Link to={rulesUrl} className="small text-muted">
- {formatMeasure(total - count, 'SHORT_INT')}
- </Link>
- );
- }
-
- renderDeprecated() {
- const { profile } = this.props;
-
- if (profile.activeDeprecatedRuleCount === 0) {
- return null;
- }
-
- const url = getDeprecatedActiveRulesUrl({ qprofile: profile.key }, this.props.organization);
-
- return (
- <div className="quality-profile-rules-deprecated clearfix">
- <div className="pull-left">
- {translate('quality_profiles.deprecated_rules')}
- </div>
- <div className="pull-right">
- <Link to={url}>
- {profile.activeDeprecatedRuleCount}
- </Link>
- </div>
- </div>
- );
}
render() {
+ const { organization, profile } = this.props;
+ const { compareToSonarWay } = this.state;
const activateMoreUrl = getRulesUrl(
- {
- qprofile: this.props.profile.key,
- activation: 'false'
- },
- this.props.organization
+ { qprofile: profile.key, activation: 'false' },
+ organization
);
return (
<th>
<h2>{translate('rules')}</h2>
</th>
- <th>Active</th>
- <th>Inactive</th>
+ <th>{translate('active')}</th>
+ <th>{translate('inactive')}</th>
</tr>
</thead>
<tbody>
- <ProfileRulesRow
+ <ProfileRulesRowTotal
key="all"
- renderTitle={this.renderActiveTitle.bind(this)}
- renderCount={this.renderActiveCount.bind(this)}
- renderTotal={this.renderActiveTotal.bind(this)}
+ count={this.state.activatedTotal}
+ organization={organization}
+ qprofile={profile.key}
+ total={this.state.total}
/>
{TYPES.map(type => (
- <ProfileRulesRow
+ <ProfileRulesRowOfType
key={type}
- renderTitle={this.renderTitleForType.bind(this, type)}
- renderCount={this.renderCountForType.bind(this, type)}
- renderTotal={this.renderTotalForType.bind(this, type)}
+ count={this.getRulesCountForType(type)}
+ organization={organization}
+ qprofile={profile.key}
+ total={this.getRulesTotalForType(type)}
+ type={type}
/>
))}
</tbody>
</table>
{this.props.canAdmin &&
- !this.props.profile.isBuiltIn &&
+ !profile.isBuiltIn &&
<div className="text-right big-spacer-top">
<Link to={activateMoreUrl} className="button js-activate-rules">
{translate('quality_profiles.activate_more')}
</Link>
</div>}
</div>
-
- {this.renderDeprecated()}
+ {profile.activeDeprecatedRuleCount > 0 &&
+ <ProfileRulesDeprecatedWarning
+ activeDeprecatedRules={profile.activeDeprecatedRuleCount}
+ organization={organization}
+ profile={profile.key}
+ />}
+ {compareToSonarWay != null &&
+ compareToSonarWay.missingRuleCount > 0 &&
+ <ProfileRulesSonarWayComparison
+ organization={organization}
+ profile={profile.key}
+ sonarway={compareToSonarWay.profile}
+ sonarWayMissingRules={compareToSonarWay.missingRuleCount}
+ />}
</div>
);
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import React from 'react';
+import { Link } from 'react-router';
+import Tooltip from '../../../components/controls/Tooltip';
+import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls';
+import { translate } from '../../../helpers/l10n';
+
+type Props = { activeDeprecatedRules: number, organization: ?string, profile: string };
+
+export default function ProfileRulesDeprecatedWarning(props: Props) {
+ const url = getDeprecatedActiveRulesUrl({ qprofile: props.profile }, props.organization);
+ return (
+ <div className="quality-profile-rules-deprecated clearfix">
+ <span className="pull-left">
+ {translate('quality_profiles.deprecated_rules')}
+ <Tooltip overlay={translate('quality_profiles.deprecated_rules_description')}>
+ <i className="icon-help spacer-left" />
+ </Tooltip>
+ </span>
+ <Link className="pull-right" to={url}>
+ {props.activeDeprecatedRules}
+ </Link>
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 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.
- */
-// @flow
-import React from 'react';
-
-type Props = {
- renderCount: () => ?React.Element<*>,
- renderTitle: () => React.Element<*>,
- renderTotal: () => ?React.Element<*>
-};
-
-export default class ProfileRulesRow extends React.PureComponent {
- props: Props;
-
- render() {
- const { renderTitle, renderCount, renderTotal } = this.props;
-
- return (
- <tr>
- <td>
- {renderTitle()}
- </td>
- <td className="thin nowrap text-right">
- {renderCount()}
- </td>
- <td className="thin nowrap text-right">
- {renderTotal()}
- </td>
- </tr>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import React from 'react';
+import { Link } from 'react-router';
+import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
+import { formatMeasure } from '../../../helpers/measures';
+import { getRulesUrl } from '../../../helpers/urls';
+import { translate } from '../../../helpers/l10n';
+
+type Props = {
+ count: ?number,
+ organization: ?string,
+ qprofile: string,
+ total: ?number,
+ type: string
+};
+
+export default function ProfileRulesRowOfType(props: Props) {
+ const activeRulesUrl = getRulesUrl(
+ { qprofile: props.qprofile, activation: 'true', types: props.type },
+ props.organization
+ );
+ const inactiveRulesUrl = getRulesUrl(
+ { qprofile: props.qprofile, activation: 'false', types: props.type },
+ props.organization
+ );
+ let inactiveCount = null;
+ if (props.count != null && props.total != null) {
+ inactiveCount = props.total - props.count;
+ }
+
+ return (
+ <tr>
+ <td>
+ <span>
+ <IssueTypeIcon query={props.type} className="little-spacer-right" />
+ {translate('issue.type', props.type, 'plural')}
+ </span>
+ </td>
+ <td className="thin nowrap text-right">
+ {props.count != null &&
+ <Link to={activeRulesUrl}>
+ {formatMeasure(props.count, 'SHORT_INT')}
+ </Link>}
+ </td>
+ <td className="thin nowrap text-right">
+ {inactiveCount != null &&
+ (inactiveCount > 0
+ ? <Link to={inactiveRulesUrl} className="small text-muted">
+ {formatMeasure(inactiveCount, 'SHORT_INT')}
+ </Link>
+ : <span className="note text-muted">0</span>)}
+ </td>
+ </tr>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import React from 'react';
+import { Link } from 'react-router';
+import { formatMeasure } from '../../../helpers/measures';
+import { getRulesUrl } from '../../../helpers/urls';
+import { translate } from '../../../helpers/l10n';
+
+type Props = {
+ count: ?number,
+ organization: ?string,
+ qprofile: string,
+ total: ?number
+};
+
+export default function ProfileRulesRowTotal(props: Props) {
+ const activeRulesUrl = getRulesUrl(
+ { qprofile: props.qprofile, activation: 'true' },
+ props.organization
+ );
+ const inactiveRulesUrl = getRulesUrl(
+ { qprofile: props.qprofile, activation: 'false' },
+ props.organization
+ );
+ let inactiveCount = null;
+ if (props.count != null && props.total != null) {
+ inactiveCount = props.total - props.count;
+ }
+
+ return (
+ <tr>
+ <td>
+ <strong>
+ {translate('total')}
+ </strong>
+ </td>
+ <td className="thin nowrap text-right">
+ {props.count != null &&
+ <Link to={activeRulesUrl}>
+ <strong>
+ {formatMeasure(props.count, 'SHORT_INT')}
+ </strong>
+ </Link>}
+ </td>
+ <td className="thin nowrap text-right">
+ {inactiveCount != null &&
+ (inactiveCount > 0
+ ? <Link to={inactiveRulesUrl} className="small text-muted">
+ <strong>
+ {formatMeasure(inactiveCount, 'SHORT_INT')}
+ </strong>
+ </Link>
+ : <span className="note text-muted">0</span>)}
+ </td>
+ </tr>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import React from 'react';
+import { Link } from 'react-router';
+import Tooltip from '../../../components/controls/Tooltip';
+import { translate } from '../../../helpers/l10n';
+
+type Props = {
+ organization: ?string,
+ profile: string,
+ sonarway: string,
+ sonarWayMissingRules: number
+};
+
+export default function ProfileRulesSonarWayComparison(props: Props) {
+ return (
+ <div className="quality-profile-rules-sonarway-missing clearfix">
+ <span className="pull-left">
+ {translate('quality_profiles.sonarway_missing_rules')}
+ <Tooltip overlay={translate('quality_profiles.sonarway_missing_rules_description')}>
+ <i className="icon-help spacer-left" />
+ </Tooltip>
+ </span>
+ <Link className="pull-right">
+ {props.sonarWayMissingRules}
+ </Link>
+ </div>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 React from 'react';
+import { shallow } from 'enzyme';
+import ProfileRules from '../ProfileRules';
+import { doAsync } from '../../../../helpers/testUtils';
+import * as apiRules from '../../../../api/rules';
+import * as apiQP from '../../../../api/quality-profiles';
+
+const PROFILE = {
+ key: 'foo',
+ name: 'Foo',
+ isBuiltIn: false,
+ isDefault: false,
+ isInherited: false,
+ language: 'java',
+ languageName: 'Java',
+ activeRuleCount: 68,
+ activeDeprecatedRuleCount: 0,
+ rulesUpdatedAt: '2017-06-28T12:58:44+0000',
+ depth: 0,
+ childrenCount: 0
+};
+
+const apiResponseAll = {
+ total: 243,
+ facets: [
+ {
+ property: 'types',
+ values: [
+ { val: 'CODE_SMELL', count: 168 },
+ { val: 'BUG', count: 68 },
+ { val: 'VULNERABILITY', count: 7 }
+ ]
+ }
+ ]
+};
+
+const apiResponseActive = {
+ total: 68,
+ facets: [
+ {
+ property: 'types',
+ values: [
+ { val: 'BUG', count: 68 },
+ { val: 'CODE_SMELL', count: 0 },
+ { val: 'VULNERABILITY', count: 0 }
+ ]
+ }
+ ]
+};
+
+// Mock api some api functions
+// eslint-disable-next-line
+apiRules.searchRules = data =>
+ Promise.resolve(data.activation === 'true' ? apiResponseActive : apiResponseAll);
+// eslint-disable-next-line
+apiQP.getQualityProfiles = () =>
+ Promise.resolve({
+ compareToSonarWay: {
+ profile: 'sonarway',
+ profileName: 'Sonar way',
+ missingRuleCount: 4
+ }
+ });
+
+it('should render the quality profiles rules with sonarway comparison', () => {
+ const wrapper = shallow(<ProfileRules canAdmin={false} organization="foo" profile={PROFILE} />);
+ wrapper.instance().mounted = true;
+ wrapper.instance().loadRules();
+ return doAsync(() => {
+ wrapper.update();
+ expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(1);
+ expect(wrapper).toMatchSnapshot();
+ });
+});
+
+it('should show a button to activate more rules for admins', () => {
+ const wrapper = shallow(<ProfileRules canAdmin={true} organization="foo" profile={PROFILE} />);
+ expect(wrapper.find('.js-activate-rules')).toMatchSnapshot();
+});
+
+it('should show a deprecated rules warning message', () => {
+ const wrapper = shallow(
+ <ProfileRules
+ canAdmin={true}
+ organization="foo"
+ profile={{ ...PROFILE, activeDeprecatedRuleCount: 8 }}
+ />
+ );
+ expect(wrapper.find('ProfileRulesDeprecatedWarning')).toMatchSnapshot();
+});
+
+it('should not show a button to activate more rules on built in profiles', () => {
+ const wrapper = shallow(
+ <ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} />
+ );
+ expect(wrapper.find('.js-activate-rules')).toHaveLength(0);
+});
+
+it('should not show a button to activate more rules on built in profiles', () => {
+ const wrapper = shallow(
+ <ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} />
+ );
+ expect(wrapper.find('.js-activate-rules')).toHaveLength(0);
+});
+
+it('should not show sonarway comparison for built in profiles', () => {
+ // eslint-disable-next-line
+ apiQP.getQualityProfiles = jest.fn(() => Promise.resolve());
+ const wrapper = shallow(
+ <ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} />
+ );
+ wrapper.instance().mounted = true;
+ wrapper.instance().loadRules();
+ return doAsync(() => {
+ wrapper.update();
+ expect(apiQP.getQualityProfiles).toHaveBeenCalledTimes(0);
+ expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(0);
+ });
+});
+
+it('should not show sonarway comparison if there is no missing rules', () => {
+ // eslint-disable-next-line
+ apiQP.getQualityProfiles = jest.fn(() =>
+ Promise.resolve({
+ compareToSonarWay: {
+ profile: 'sonarway',
+ profileName: 'Sonar way',
+ missingRuleCount: 0
+ }
+ })
+ );
+ const wrapper = shallow(<ProfileRules canAdmin={true} organization={null} profile={PROFILE} />);
+ wrapper.instance().mounted = true;
+ wrapper.instance().loadRules();
+ return doAsync(() => {
+ wrapper.update();
+ expect(apiQP.getQualityProfiles).toHaveBeenCalledTimes(1);
+ expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(0);
+ });
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import React from 'react';
+import { shallow } from 'enzyme';
+import ProfileRulesDeprecatedWarning from '../ProfileRulesDeprecatedWarning';
+
+it('should render correctly', () => {
+ expect(
+ shallow(
+ <ProfileRulesDeprecatedWarning activeDeprecatedRules={18} organization="foo" profile="bar" />
+ )
+ ).toMatchSnapshot();
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import React from 'react';
+import { shallow } from 'enzyme';
+import ProfileRulesRowOfType from '../ProfileRulesRowOfType';
+
+it('should render correctly', () => {
+ expect(
+ shallow(
+ <ProfileRulesRowOfType count={3} organization="foo" qprofile="bar" total={10} type="BUG" />
+ )
+ ).toMatchSnapshot();
+});
+
+it('should render correctly if there is 0 rules', () => {
+ expect(
+ shallow(
+ <ProfileRulesRowOfType
+ count={0}
+ organization={null}
+ qprofile="bar"
+ total={0}
+ type="VULNERABILITY"
+ />
+ )
+ ).toMatchSnapshot();
+});
+
+it('should render correctly if there is missing data', () => {
+ expect(
+ shallow(
+ <ProfileRulesRowOfType
+ count={5}
+ organization={null}
+ qprofile="bar"
+ total={null}
+ type="VULNERABILITY"
+ />
+ )
+ ).toMatchSnapshot();
+ expect(
+ shallow(
+ <ProfileRulesRowOfType
+ count={null}
+ organization={null}
+ qprofile="foo"
+ total={10}
+ type="VULNERABILITY"
+ />
+ )
+ ).toMatchSnapshot();
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import React from 'react';
+import { shallow } from 'enzyme';
+import ProfileRulesRowTotal from '../ProfileRulesRowTotal';
+
+it('should render correctly', () => {
+ expect(
+ shallow(<ProfileRulesRowTotal count={3} organization="foo" qprofile="bar" total={10} />)
+ ).toMatchSnapshot();
+});
+
+it('should render correctly if there is 0 rules', () => {
+ expect(
+ shallow(<ProfileRulesRowTotal count={0} organization={null} qprofile="bar" total={0} />)
+ ).toMatchSnapshot();
+});
+
+it('should render correctly if there is missing data', () => {
+ expect(
+ shallow(<ProfileRulesRowTotal count={5} organization={null} qprofile="bar" total={null} />)
+ ).toMatchSnapshot();
+ expect(
+ shallow(<ProfileRulesRowTotal count={null} organization={null} qprofile="foo" total={10} />)
+ ).toMatchSnapshot();
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import React from 'react';
+import { shallow } from 'enzyme';
+import ProfileRulesSonarWayComparison from '../ProfileRulesSonarWayComparison';
+
+it('should render correctly', () => {
+ expect(
+ shallow(
+ <ProfileRulesSonarWayComparison
+ organization="foo"
+ profile="bar"
+ sonarway="baz"
+ sonarWayMissingRules={158}
+ />
+ )
+ ).toMatchSnapshot();
+});
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render the quality profiles rules with sonarway comparison 1`] = `
+<div
+ className="quality-profile-rules"
+>
+ <div
+ className="quality-profile-rules-distribution"
+ >
+ <table
+ className="data condensed"
+ >
+ <thead>
+ <tr>
+ <th>
+ <h2>
+ rules
+ </h2>
+ </th>
+ <th>
+ active
+ </th>
+ <th>
+ inactive
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <ProfileRulesRowTotal
+ count={68}
+ organization="foo"
+ qprofile="foo"
+ total={243}
+ />
+ <ProfileRulesRowOfType
+ count={68}
+ organization="foo"
+ qprofile="foo"
+ total={68}
+ type="BUG"
+ />
+ <ProfileRulesRowOfType
+ count={0}
+ organization="foo"
+ qprofile="foo"
+ total={7}
+ type="VULNERABILITY"
+ />
+ <ProfileRulesRowOfType
+ count={0}
+ organization="foo"
+ qprofile="foo"
+ total={168}
+ type="CODE_SMELL"
+ />
+ </tbody>
+ </table>
+ </div>
+ <ProfileRulesSonarWayComparison
+ organization="foo"
+ profile="foo"
+ sonarWayMissingRules={4}
+ sonarway="sonarway"
+ />
+</div>
+`;
+
+exports[`should show a button to activate more rules for admins 1`] = `
+<Link
+ className="button js-activate-rules"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/rules#qprofile=foo|activation=false"
+>
+ quality_profiles.activate_more
+</Link>
+`;
+
+exports[`should show a deprecated rules warning message 1`] = `
+<ProfileRulesDeprecatedWarning
+ activeDeprecatedRules={8}
+ organization="foo"
+ profile="foo"
+/>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="quality-profile-rules-deprecated clearfix"
+>
+ <span
+ className="pull-left"
+ >
+ quality_profiles.deprecated_rules
+ <Tooltip
+ overlay="quality_profiles.deprecated_rules_description"
+ placement="bottom"
+ >
+ <i
+ className="icon-help spacer-left"
+ />
+ </Tooltip>
+ </span>
+ <Link
+ className="pull-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/rules#qprofile=bar|activation=true|statuses=DEPRECATED"
+ >
+ 18
+ </Link>
+</div>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<tr>
+ <td>
+ <span>
+ <IssueTypeIcon
+ className="little-spacer-right"
+ query="BUG"
+ />
+ issue.type.BUG.plural
+ </span>
+ </td>
+ <td
+ className="thin nowrap text-right"
+ >
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/rules#qprofile=bar|activation=true|types=BUG"
+ >
+ 3
+ </Link>
+ </td>
+ <td
+ className="thin nowrap text-right"
+ >
+ <Link
+ className="small text-muted"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/rules#qprofile=bar|activation=false|types=BUG"
+ >
+ 7
+ </Link>
+ </td>
+</tr>
+`;
+
+exports[`should render correctly if there is 0 rules 1`] = `
+<tr>
+ <td>
+ <span>
+ <IssueTypeIcon
+ className="little-spacer-right"
+ query="VULNERABILITY"
+ />
+ issue.type.VULNERABILITY.plural
+ </span>
+ </td>
+ <td
+ className="thin nowrap text-right"
+ >
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/coding_rules#qprofile=bar|activation=true|types=VULNERABILITY"
+ >
+ 0
+ </Link>
+ </td>
+ <td
+ className="thin nowrap text-right"
+ >
+ <span
+ className="note text-muted"
+ >
+ 0
+ </span>
+ </td>
+</tr>
+`;
+
+exports[`should render correctly if there is missing data 1`] = `
+<tr>
+ <td>
+ <span>
+ <IssueTypeIcon
+ className="little-spacer-right"
+ query="VULNERABILITY"
+ />
+ issue.type.VULNERABILITY.plural
+ </span>
+ </td>
+ <td
+ className="thin nowrap text-right"
+ >
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/coding_rules#qprofile=bar|activation=true|types=VULNERABILITY"
+ >
+ 5
+ </Link>
+ </td>
+ <td
+ className="thin nowrap text-right"
+ />
+</tr>
+`;
+
+exports[`should render correctly if there is missing data 2`] = `
+<tr>
+ <td>
+ <span>
+ <IssueTypeIcon
+ className="little-spacer-right"
+ query="VULNERABILITY"
+ />
+ issue.type.VULNERABILITY.plural
+ </span>
+ </td>
+ <td
+ className="thin nowrap text-right"
+ />
+ <td
+ className="thin nowrap text-right"
+ />
+</tr>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<tr>
+ <td>
+ <strong>
+ total
+ </strong>
+ </td>
+ <td
+ className="thin nowrap text-right"
+ >
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/rules#qprofile=bar|activation=true"
+ >
+ <strong>
+ 3
+ </strong>
+ </Link>
+ </td>
+ <td
+ className="thin nowrap text-right"
+ >
+ <Link
+ className="small text-muted"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/rules#qprofile=bar|activation=false"
+ >
+ <strong>
+ 7
+ </strong>
+ </Link>
+ </td>
+</tr>
+`;
+
+exports[`should render correctly if there is 0 rules 1`] = `
+<tr>
+ <td>
+ <strong>
+ total
+ </strong>
+ </td>
+ <td
+ className="thin nowrap text-right"
+ >
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/coding_rules#qprofile=bar|activation=true"
+ >
+ <strong>
+ 0
+ </strong>
+ </Link>
+ </td>
+ <td
+ className="thin nowrap text-right"
+ >
+ <span
+ className="note text-muted"
+ >
+ 0
+ </span>
+ </td>
+</tr>
+`;
+
+exports[`should render correctly if there is missing data 1`] = `
+<tr>
+ <td>
+ <strong>
+ total
+ </strong>
+ </td>
+ <td
+ className="thin nowrap text-right"
+ >
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/coding_rules#qprofile=bar|activation=true"
+ >
+ <strong>
+ 5
+ </strong>
+ </Link>
+ </td>
+ <td
+ className="thin nowrap text-right"
+ />
+</tr>
+`;
+
+exports[`should render correctly if there is missing data 2`] = `
+<tr>
+ <td>
+ <strong>
+ total
+ </strong>
+ </td>
+ <td
+ className="thin nowrap text-right"
+ />
+ <td
+ className="thin nowrap text-right"
+ />
+</tr>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="quality-profile-rules-sonarway-missing clearfix"
+>
+ <span
+ className="pull-left"
+ >
+ quality_profiles.sonarway_missing_rules
+ <Tooltip
+ overlay="quality_profiles.sonarway_missing_rules_description"
+ placement="bottom"
+ >
+ <i
+ className="icon-help spacer-left"
+ />
+ </Tooltip>
+ </span>
+ <Link
+ className="pull-right"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ >
+ 158
+ </Link>
+</div>
+`;
padding: 15px 20px 20px;
}
-.quality-profile-rules {}
+.quality-profile-rules {
+ min-height: 182px;
+}
.quality-profile-rules > header {
padding: 15px 20px;
background-color: #f2dede;
}
+.quality-profile-rules-sonarway-missing {
+ padding: 15px 20px;
+ background-color: #fcf8e3;
+}
+
.quality-profile-exporters {
margin-top: 20px;
}
quality_profiles.latest_new_rules.activated={0}, activated on {1} profile(s)
quality_profiles.latest_new_rules.not_activated={0}, not yet activated
quality_profiles.deprecated_rules=Deprecated Rules
+quality_profiles.deprecated_rules_description=These deprecated rules will eventually disappear. You should proactively investigate replacing them.
quality_profiles.deprecated_rules_are_still_activated=Deprecated rules are still activated on {0} quality profile(s):
+quality_profiles.sonarway_missing_rules=Sonar way rules not included
+quality_profiles.sonarway_missing_rules_description=Recommended rules are missing from your profile
quality_profiles.stagnant_profiles=Stagnant Profiles
quality_profiles.not_updated_more_than_year=The following profiles haven't been updated for more than 1 year:
quality_profiles.exporters=Exporters