]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9482 Display the number of missing Sonar Way rules on the QP page
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Thu, 29 Jun 2017 09:46:24 +0000 (11:46 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 4 Jul 2017 14:29:36 +0000 (16:29 +0200)
18 files changed:
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesDeprecatedWarning-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowOfType-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowTotal-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesSonarWayComparison-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.js.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.js.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.js.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.js.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.js.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/styles.css
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index a179e441d55c3d56a70cc4e5b370c517ab018176..d9bb55dfefd13c72ccae8a37ebb07bc7f577cad0 100644 (file)
 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'];
@@ -38,20 +40,24 @@ type Props = {
 };
 
 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() {
@@ -69,6 +75,16 @@ export default class ProfileRules extends React.PureComponent {
     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,
@@ -87,197 +103,44 @@ export default class ProfileRules extends React.PureComponent {
   }
 
   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 (
@@ -289,38 +152,53 @@ export default class ProfileRules extends React.PureComponent {
                 <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>
     );
   }
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.js
new file mode 100644 (file)
index 0000000..d50c81c
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.js
deleted file mode 100644 (file)
index 9a7d140..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.js
new file mode 100644 (file)
index 0000000..e6e04e5
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.js
new file mode 100644 (file)
index 0000000..f17bc2a
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.js
new file mode 100644 (file)
index 0000000..97427ee
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.js
new file mode 100644 (file)
index 0000000..cd62b6a
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * 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);
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesDeprecatedWarning-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesDeprecatedWarning-test.js
new file mode 100644 (file)
index 0000000..9a33872
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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();
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowOfType-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowOfType-test.js
new file mode 100644 (file)
index 0000000..dea445e
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * 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();
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowTotal-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowTotal-test.js
new file mode 100644 (file)
index 0000000..20f2d69
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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();
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesSonarWayComparison-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesSonarWayComparison-test.js
new file mode 100644 (file)
index 0000000..35c0548
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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();
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.js.snap
new file mode 100644 (file)
index 0000000..ea4e922
--- /dev/null
@@ -0,0 +1,85 @@
+// 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"
+/>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.js.snap
new file mode 100644 (file)
index 0000000..54fa91c
--- /dev/null
@@ -0,0 +1,29 @@
+// 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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.js.snap
new file mode 100644 (file)
index 0000000..6286609
--- /dev/null
@@ -0,0 +1,120 @@
+// 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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.js.snap
new file mode 100644 (file)
index 0000000..f868665
--- /dev/null
@@ -0,0 +1,112 @@
+// 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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.js.snap
new file mode 100644 (file)
index 0000000..6864b28
--- /dev/null
@@ -0,0 +1,28 @@
+// 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>
+`;
index 0498b5b1d48b0881ec20b056ec832556fef00df7..2712ddf243ec7efe15937612ac80a29bfecf0ce2 100644 (file)
@@ -63,7 +63,9 @@
   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;
 }
index bc2d0f77ff4203e7b7085167334aaf28a93f121e..f42eca5589d74653b5ed9b4d8024c7f8d4fd4661 100644 (file)
@@ -1514,7 +1514,10 @@ quality_profiles.latest_new_rules=Recently Added Rules
 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