]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18361 Improve accessibility of the quality profile compare page
author7PH <benjamin.raymond@sonarsource.com>
Thu, 9 Feb 2023 09:34:39 +0000 (10:34 +0100)
committersonartech <sonartech@sonarsource.com>
Fri, 10 Feb 2023 20:02:45 +0000 (20:02 +0000)
server/sonar-web/src/main/js/app/styles/init/tables.css
server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfilesApp-it.tsx
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResultActivation.tsx
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.tsx
server/sonar-web/src/main/js/apps/quality-profiles/styles.css

index df2e1665f3e1f8ae98a7f4c4022e8409c9168dd3..a6317d0229e586585cdbc2a8273e34e071250def 100644 (file)
@@ -162,6 +162,12 @@ table.data th.small {
   white-space: nowrap;
 }
 
+table.data > caption {
+  padding: 8px 10px;
+  text-align: center;
+  font-weight: bold;
+}
+
 table.data > thead > tr > th {
   position: relative;
   vertical-align: top;
index 7d5413dbbff780192022378e3928477532fd03ad..00c6ac5aba265704c25fb499f489983e320d5e70 100644 (file)
@@ -83,9 +83,11 @@ const ui = {
   }),
   nameCreatePopupInput: byRole('textbox', { name: 'name field_required' }),
   comparisonDiffTableHeading: (rulesQuantity: number, profileName: string) =>
-    byRole('heading', { name: `quality_profiles.x_rules_only_in.${rulesQuantity} ${profileName}` }),
+    byRole('columnheader', {
+      name: `quality_profiles.x_rules_only_in.${rulesQuantity} ${profileName}`,
+    }),
   comparisonModifiedTableHeading: (rulesQuantity: number) =>
-    byRole('heading', {
+    byRole('table', {
       name: `quality_profiles.x_rules_have_different_configuration.${rulesQuantity}`,
     }),
   newRuleLink: byRole('link', { name: 'Recently Added Rule' }),
index 51b987e02930aa80dc27c4b52cdd0715f69b515b..333e6aeff304f0e93a895641fcc6365dfb1eb04c 100644 (file)
@@ -77,11 +77,6 @@ export default class ComparisonResultActivation extends React.PureComponent<Prop
   render() {
     const { profile } = this.props;
 
-    const canActivate = !profile.isBuiltIn && profile.actions && profile.actions.edit;
-    if (!canActivate) {
-      return null;
-    }
-
     return (
       <DeferredSpinner loading={this.state.state === 'opening'}>
         <Tooltip
index ad320dec6e3165714b71ee664e1dcc29667bd8ed..dde41ffd05f08b17eedb51cdb963effe4a72248c 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import classNames from 'classnames';
 import * as React from 'react';
 import { CompareResponse, Profile } from '../../../api/quality-profiles';
 import Link from '../../../components/common/Link';
 import ChevronLeftIcon from '../../../components/icons/ChevronLeftIcon';
 import ChevronRightIcon from '../../../components/icons/ChevronRightIcon';
 import SeverityIcon from '../../../components/icons/SeverityIcon';
-import { translateWithParameters } from '../../../helpers/l10n';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { getRulesUrl } from '../../../helpers/urls';
 import { Dict } from '../../../types/types';
 import ComparisonEmpty from './ComparisonEmpty';
@@ -38,6 +39,10 @@ interface Props extends CompareResponse {
 }
 
 export default class ComparisonResults extends React.PureComponent<Props> {
+  canActivate(profile: Profile) {
+    return !profile.isBuiltIn && profile.actions && profile.actions.edit;
+  }
+
   renderRule(rule: { key: string; name: string }, severity: string) {
     return (
       <div>
@@ -70,38 +75,43 @@ export default class ComparisonResults extends React.PureComponent<Props> {
     if (this.props.inLeft.length === 0) {
       return null;
     }
+
+    const renderSecondColumn = this.props.rightProfile && this.canActivate(this.props.rightProfile);
+
     return (
-      <>
-        <tr>
-          <td>
-            <h6>
+      <table className="data fixed zebra">
+        <thead>
+          <tr>
+            <th>
               {translateWithParameters(
                 'quality_profiles.x_rules_only_in',
                 this.props.inLeft.length
               )}{' '}
               {this.props.left.name}
-            </h6>
-          </td>
-          <td>&nbsp;</td>
-        </tr>
-        {this.props.inLeft.map((rule) => (
-          <tr className="js-comparison-in-left" key={`left-${rule.key}`}>
-            <td>{this.renderRule(rule, rule.severity)}</td>
-            <td>
-              {this.props.rightProfile && (
-                <ComparisonResultActivation
-                  key={rule.key}
-                  onDone={this.props.refresh}
-                  profile={this.props.rightProfile}
-                  ruleKey={rule.key}
-                >
-                  <ChevronRightIcon />
-                </ComparisonResultActivation>
-              )}
-            </td>
+            </th>
+            {renderSecondColumn && <th aria-label={translate('actions')}>&nbsp;</th>}
           </tr>
-        ))}
-      </>
+        </thead>
+        <tbody>
+          {this.props.inLeft.map((rule) => (
+            <tr key={`left-${rule.key}`}>
+              <td>{this.renderRule(rule, rule.severity)}</td>
+              {renderSecondColumn && (
+                <td>
+                  <ComparisonResultActivation
+                    key={rule.key}
+                    onDone={this.props.refresh}
+                    profile={this.props.rightProfile as Profile}
+                    ruleKey={rule.key}
+                  >
+                    <ChevronRightIcon />
+                  </ComparisonResultActivation>
+                </td>
+              )}
+            </tr>
+          ))}
+        </tbody>
+      </table>
     );
   }
 
@@ -109,36 +119,47 @@ export default class ComparisonResults extends React.PureComponent<Props> {
     if (this.props.inRight.length === 0) {
       return null;
     }
+
+    const renderFirstColumn = this.props.leftProfile && this.canActivate(this.props.leftProfile);
+
     return (
-      <>
-        <tr>
-          <td>&nbsp;</td>
-          <td>
-            <h6>
+      <table
+        className={classNames('data fixed zebra quality-profile-compare-right-table', {
+          'has-first-column': renderFirstColumn,
+        })}
+      >
+        <thead>
+          <tr>
+            {renderFirstColumn && <th aria-label={translate('actions')}>&nbsp;</th>}
+            <th>
               {translateWithParameters(
                 'quality_profiles.x_rules_only_in',
                 this.props.inRight.length
               )}{' '}
               {this.props.right.name}
-            </h6>
-          </td>
-        </tr>
-        {this.props.inRight.map((rule) => (
-          <tr className="js-comparison-in-right" key={`right-${rule.key}`}>
-            <td className="text-right">
-              <ComparisonResultActivation
-                key={rule.key}
-                onDone={this.props.refresh}
-                profile={this.props.leftProfile}
-                ruleKey={rule.key}
-              >
-                <ChevronLeftIcon />
-              </ComparisonResultActivation>
-            </td>
-            <td>{this.renderRule(rule, rule.severity)}</td>
+            </th>
           </tr>
-        ))}
-      </>
+        </thead>
+        <tbody>
+          {this.props.inRight.map((rule) => (
+            <tr key={`right-${rule.key}`}>
+              {renderFirstColumn && (
+                <td className="text-right">
+                  <ComparisonResultActivation
+                    key={rule.key}
+                    onDone={this.props.refresh}
+                    profile={this.props.leftProfile}
+                    ruleKey={rule.key}
+                  >
+                    <ChevronLeftIcon />
+                  </ComparisonResultActivation>
+                </td>
+              )}
+              <td>{this.renderRule(rule, rule.severity)}</td>
+            </tr>
+          ))}
+        </tbody>
+      </table>
     );
   }
 
@@ -147,38 +168,34 @@ export default class ComparisonResults extends React.PureComponent<Props> {
       return null;
     }
     return (
-      <>
-        <tr>
-          <td className="text-center" colSpan={2}>
-            <h6>
-              {translateWithParameters(
-                'quality_profiles.x_rules_have_different_configuration',
-                this.props.modified.length
-              )}
-            </h6>
-          </td>
-        </tr>
-        <tr>
-          <td>
-            <h6>{this.props.left.name}</h6>
-          </td>
-          <td>
-            <h6>{this.props.right.name}</h6>
-          </td>
-        </tr>
-        {this.props.modified.map((rule) => (
-          <tr className="js-comparison-modified" key={`modified-${rule.key}`}>
-            <td>
-              {this.renderRule(rule, rule.left.severity)}
-              {this.renderParameters(rule.left.params)}
-            </td>
-            <td>
-              {this.renderRule(rule, rule.right.severity)}
-              {this.renderParameters(rule.right.params)}
-            </td>
+      <table className="data fixed zebra zebra-inversed">
+        <caption>
+          {translateWithParameters(
+            'quality_profiles.x_rules_have_different_configuration',
+            this.props.modified.length
+          )}
+        </caption>
+        <thead>
+          <tr>
+            <th>{this.props.left.name}</th>
+            <th>{this.props.right.name}</th>
           </tr>
-        ))}
-      </>
+        </thead>
+        <tbody>
+          {this.props.modified.map((rule) => (
+            <tr key={`modified-${rule.key}`}>
+              <td>
+                {this.renderRule(rule, rule.left.severity)}
+                {this.renderParameters(rule.left.params)}
+              </td>
+              <td>
+                {this.renderRule(rule, rule.right.severity)}
+                {this.renderParameters(rule.right.params)}
+              </td>
+            </tr>
+          ))}
+        </tbody>
+      </table>
     );
   }
 
@@ -188,13 +205,11 @@ export default class ComparisonResults extends React.PureComponent<Props> {
     }
 
     return (
-      <table className="data zebra quality-profile-comparison-table">
-        <tbody>
-          {this.renderLeft()}
-          {this.renderRight()}
-          {this.renderModified()}
-        </tbody>
-      </table>
+      <>
+        {this.renderLeft()}
+        {this.renderRight()}
+        {this.renderModified()}
+      </>
     );
   }
 }
index 3b7c844023f391e5bc38489fd6f27136e1205d44..4fb6b68c3e7f8615d9a13ce48f0dfd22b4070e66 100644 (file)
   padding: 0;
 }
 
-.quality-profile-comparison-table {
-  table-layout: fixed;
-}
-
 .quality-profile-changelog-rule-cell {
   line-height: 1.5;
 }
   word-break: break-word;
 }
 
+.quality-profile-compare-right-table:not(.has-first-column) td,
+.quality-profile-compare-right-table:not(.has-first-column) th {
+  /* Aligns the first column with the second one (50%) and add usual cell padding */
+  padding-left: calc(50% + 10px);
+}
+
 #create-profile-form .radio-card {
   width: 245px;
   background-color: var(--neutral50);