]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23030 Use Heading component
authorDavid Cho-Lerat <david.cho-lerat@sonarsource.com>
Wed, 18 Sep 2024 12:52:23 +0000 (14:52 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 24 Sep 2024 20:03:04 +0000 (20:03 +0000)
server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx
server/sonar-web/src/main/js/apps/account/notifications/Notifications.tsx
server/sonar-web/src/main/js/apps/account/notifications/Projects.tsx
server/sonar-web/src/main/js/apps/account/security/Security.tsx
server/sonar-web/src/main/js/apps/account/security/Tokens.tsx
server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.tsx
server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx
server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx

index 700f7f6e08a1fd1a9ef577076838b97fc5514584..aa7f9a13d9ce3e4cfee2b4de9b31d14fa10763cc 100644 (file)
@@ -18,7 +18,8 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { PageTitle, Table } from 'design-system';
+import { Heading } from '@sonarsource/echoes-react';
+import { Table } from 'design-system';
 import * as React from 'react';
 import { translate } from '../../../helpers/l10n';
 import { Notification, NotificationGlobalType } from '../../../types/notifications';
@@ -36,7 +37,9 @@ interface Props {
 export default function GlobalNotifications(props: Readonly<Props>) {
   return (
     <>
-      <PageTitle className="sw-mb-4" text={translate('my_profile.overall_notifications.title')} />
+      <Heading as="h2" hasMarginBottom>
+        {translate('my_profile.overall_notifications.title')}
+      </Heading>
 
       {!props.header && (
         <div className="sw-body-sm-highlight sw-mb-2">{translate('notifications.send_email')}</div>
index c3f60c9cce3c8c034bdd4ed182f829f0c4c357c3..cce33e291ee8ffa93fdc4fde321ccbd7e7aef4aa 100644 (file)
@@ -18,8 +18,9 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { FlagMessage, GreySeparator, Spinner, Title } from 'design-system';
-import { partition } from 'lodash';
+import { Heading, Spinner } from '@sonarsource/echoes-react';
+import { FlagMessage, GreySeparator } from 'design-system';
+import { isEmpty, partition } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import {
@@ -39,7 +40,9 @@ export function Notifications({
   perProjectTypes,
   removeNotification,
 }: WithNotificationsProps) {
-  const [globalNotifications, projectNotifications] = partition(notifications, (n) => !n.project);
+  const [globalNotifications, projectNotifications] = partition(notifications, (n) =>
+    isEmpty(n.project),
+  );
 
   const emailOnly = channels.length === 1 && channels[0] === 'EmailNotificationChannel';
 
@@ -59,13 +62,15 @@ export function Notifications({
     <div className="it__account-body">
       <Helmet defer={false} title={translate('my_account.notifications')} />
 
-      <Title>{translate('my_account.notifications')}</Title>
+      <Heading as="h1" hasMarginBottom>
+        {translate('my_account.notifications')}
+      </Heading>
 
       <FlagMessage className="sw-my-2" variant="info">
         {translate('notification.dispatcher.information')}
       </FlagMessage>
 
-      <Spinner loading={loading}>
+      <Spinner isLoading={loading}>
         {notifications && (
           <>
             <GreySeparator className="sw-mb-4 sw-mt-6" />
index fe009cb6d6035d4d91cc0620389d25f7800f8b3f..c18e1181ea5dd944d71c6a1c45f6b7156f54e6f2 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { Button, ButtonVariety } from '@sonarsource/echoes-react';
+import { Button, ButtonVariety, Heading } from '@sonarsource/echoes-react';
 import { InputSearch, Note } from 'design-system';
 import { groupBy, sortBy, uniqBy } from 'lodash';
 import * as React from 'react';
@@ -120,9 +120,9 @@ export default class Projects extends React.PureComponent<Props, State> {
     return (
       <section data-test="account__project-notifications">
         <div className="sw-flex sw-justify-between">
-          <h2 className="sw-body-md-highlight sw-mb-4">
+          <Heading as="h2" hasMarginBottom>
             {translate('my_profile.per_project_notifications.title')}
-          </h2>
+          </Heading>
 
           <Button onClick={this.openModal} variety={ButtonVariety.Primary}>
             <span data-test="account__add-project-notification">
index b1ea17b4c5e7e64afde8b41e714aead0672d2efb..0539ef8aff41a24ad8006e62700d51355029be05 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { PageTitle } from 'design-system';
+import { Heading } from '@sonarsource/echoes-react';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { useCurrentLoginUser } from '../../../app/components/current-user/CurrentUserContext';
@@ -36,10 +36,9 @@ export default function Security() {
 
       {currentUser.local && (
         <>
-          <PageTitle
-            className="sw-heading-md sw-my-6"
-            text={translate('my_profile.password.title')}
-          />
+          <Heading as="h2" className="sw-mt-6" hasMarginBottom>
+            {translate('my_profile.password.title')}
+          </Heading>
 
           <ResetPasswordForm user={currentUser} />
         </>
index 27d6728c85e6a96201b748e40a9ca5e443628740..f177b304a497c518f42c8ea6468c03e5fa1ab529 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { Title } from 'design-system';
+import { Heading } from '@sonarsource/echoes-react';
 import * as React from 'react';
 import InstanceMessage from '../../../components/common/InstanceMessage';
 import { translate } from '../../../helpers/l10n';
@@ -31,15 +31,15 @@ interface Props {
 export default function Tokens({ login }: Readonly<Props>) {
   return (
     <>
-      <Title>{translate('my_account.security')}</Title>
+      <Heading as="h1" hasMarginBottom>
+        {translate('my_account.security')}
+      </Heading>
 
-      <div>
-        <div className="sw-body-md sw-mb-4 sw-mr-4">
-          <InstanceMessage message={translate('my_account.tokens_description')} />
-        </div>
-
-        <TokensForm deleteConfirmation="modal" login={login} displayTokenTypeInput />
+      <div className="sw-body-md sw-mb-4 sw-mr-4">
+        <InstanceMessage message={translate('my_account.tokens_description')} />
       </div>
+
+      <TokensForm deleteConfirmation="modal" login={login} displayTokenTypeInput />
     </>
   );
 }
index b366a9ca956ef84b67458a91b88936ddf80fef41..0a2ea49e239b81b3695aeda3a2bdfdc7962db2e4 100644 (file)
@@ -19,7 +19,7 @@
  */
 
 import styled from '@emotion/styled';
-import { Spinner } from '@sonarsource/echoes-react';
+import { Heading, Spinner } from '@sonarsource/echoes-react';
 import {
   LAYOUT_FOOTER_HEIGHT,
   LargeCenteredLayout,
@@ -109,7 +109,7 @@ export class AllProjects extends React.PureComponent<Props, State> {
     const { isFavorite, isLegacy } = this.props;
     const { pageIndex, projects, query } = this.state;
 
-    if (pageIndex && projects && Object.keys(query).length !== 0) {
+    if (isDefined(pageIndex) && pageIndex !== 0 && projects && Object.keys(query).length !== 0) {
       this.setState({ loading: true });
 
       fetchProjects({ isFavorite, query, pageIndex: pageIndex + 1, isLegacy }).then((response) => {
@@ -309,22 +309,25 @@ export class AllProjects extends React.PureComponent<Props, State> {
       <StyledWrapper id="projects-page">
         <Helmet defer={false} title={translate('projects.page')} />
 
-        <h1 className="sw-sr-only">{translate('projects.page')}</h1>
+        <Heading as="h1" className="sw-sr-only">
+          {translate('projects.page')}
+        </Heading>
 
         <LargeCenteredLayout>
           <PageContentFontWrapper className="sw-flex sw-w-full sw-body-md">
             {this.renderSide()}
 
-            <div
-              role="main"
-              className="sw-flex sw-flex-col sw-box-border sw-min-w-0 sw-pl-12 sw-pt-6 sw-flex-1"
-            >
+            <main className="sw-flex sw-flex-col sw-box-border sw-min-w-0 sw-pl-12 sw-pt-6 sw-flex-1">
               <A11ySkipTarget anchor="projects_main" />
 
-              <h2 className="sw-sr-only">{translate('list_of_projects')}</h2>
+              <Heading as="h2" className="sw-sr-only">
+                {translate('list_of_projects')}
+              </Heading>
+
               {this.renderHeader()}
+
               {this.renderMain()}
-            </div>
+            </main>
           </PageContentFontWrapper>
         </LargeCenteredLayout>
       </StyledWrapper>
index 735bf38a28d80ea26a0ceffebed4af9de96fedf9..4b3080b4499d2cb0a6fed7b60400217999c3284e 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 { Heading } from '@sonarsource/echoes-react';
 import { DiscreetLink, FlagMessage, Note } from 'design-system';
 import { sortBy } from 'lodash';
 import * as React from 'react';
 import { useIntl } from 'react-intl';
+import { isDefined } from '../../../helpers/types';
 import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls';
 import { Profile } from '../types';
 import { getProfilePath } from '../utils';
@@ -49,9 +52,10 @@ export default function EvolutionDeprecated({ profiles }: Readonly<Props>) {
 
   return (
     <section aria-label={intl.formatMessage({ id: 'quality_profiles.deprecated_rules' })}>
-      <h2 className="sw-heading-md sw-mb-6">
+      <Heading as="h2" hasMarginBottom>
         {intl.formatMessage({ id: 'quality_profiles.deprecated_rules' })}
-      </h2>
+      </Heading>
+
       <FlagMessage variant="error" className="sw-mb-3">
         {intl.formatMessage(
           { id: 'quality_profiles.deprecated_rules_are_still_activated' },
@@ -70,7 +74,9 @@ export default function EvolutionDeprecated({ profiles }: Readonly<Props>) {
 
             <Note>
               {profile.languageName}
+
               {', '}
+
               <DiscreetLink
                 className="link-no-underline"
                 to={getDeprecatedActiveRulesUrl({ qprofile: profile.key })}
@@ -85,6 +91,7 @@ export default function EvolutionDeprecated({ profiles }: Readonly<Props>) {
                 )}
               </DiscreetLink>
             </Note>
+
             <EvolutionDeprecatedInherited
               profile={profile}
               profilesWithDeprecations={profilesWithDeprecations}
@@ -104,31 +111,34 @@ function EvolutionDeprecatedInherited(
 ) {
   const { profile, profilesWithDeprecations } = props;
   const intl = useIntl();
+
   const rules = React.useMemo(
     () => getDeprecatedRulesInheritanceChain(profile, profilesWithDeprecations),
     [profile, profilesWithDeprecations],
   );
-  if (rules.length) {
-    return (
-      <>
-        {rules.map((rule) => {
-          if (rule.from.key === profile.key) {
-            return null;
-          }
-
-          return (
-            <Note key={rule.from.key}>
-              {intl.formatMessage(
-                { id: 'coding_rules.filters.inheritance.x_inherited_from_y' },
-                { count: rule.count, name: rule.from.name },
-              )}
-            </Note>
-          );
-        })}
-      </>
-    );
+
+  if (rules.length === 0) {
+    return null;
   }
-  return null;
+
+  return (
+    <>
+      {rules.map((rule) => {
+        if (rule.from.key === profile.key) {
+          return null;
+        }
+
+        return (
+          <Note key={rule.from.key}>
+            {intl.formatMessage(
+              { id: 'coding_rules.filters.inheritance.x_inherited_from_y' },
+              { count: rule.count, name: rule.from.name },
+            )}
+          </Note>
+        );
+      })}
+    </>
+  );
 }
 
 function getDeprecatedRulesInheritanceChain(profile: Profile, profilesWithDeprecations: Profile[]) {
@@ -139,14 +149,16 @@ function getDeprecatedRulesInheritanceChain(profile: Profile, profilesWithDeprec
     return rules;
   }
 
-  if (profile.parentKey) {
+  if (isDefined(profile.parentKey) && profile.parentKey !== '') {
     const parentProfile = profilesWithDeprecations.find((p) => p.key === profile.parentKey);
+
     if (parentProfile) {
       const parentRules = getDeprecatedRulesInheritanceChain(
         parentProfile,
         profilesWithDeprecations,
       );
-      if (parentRules.length) {
+
+      if (parentRules.length !== 0) {
         count -= parentRules.reduce((n, rule) => n + rule.count, 0);
         rules = rules.concat(parentRules);
       }
index 125155c9f42ec5f19c0b70dc6a68cacae81206a2..0e6806612004cc66d1230e4371b02fce0ba991f9 100644 (file)
@@ -17,6 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
+import { Heading } from '@sonarsource/echoes-react';
 import { DiscreetLink, Link, Note } from 'design-system';
 import { noop, sortBy } from 'lodash';
 import * as React from 'react';
@@ -26,6 +28,7 @@ import { MetricType } from '~sonar-aligned/types/metrics';
 import { listRules } from '../../../api/rules';
 import { toShortISO8601String } from '../../../helpers/dates';
 import { translateWithParameters } from '../../../helpers/l10n';
+import { isDefined } from '../../../helpers/types';
 import { getRulesUrl } from '../../../helpers/urls';
 import { Rule, RuleActivation } from '../../../types/types';
 
@@ -39,6 +42,7 @@ export default function EvolutionRules() {
   const intl = useIntl();
   const [latestRules, setLatestRules] = React.useState<ExtendedRule[]>();
   const [latestRulesTotal, setLatestRulesTotal] = React.useState<number>();
+
   const periodStartDate = React.useMemo(() => {
     const startDate = new Date();
     startDate.setFullYear(startDate.getFullYear() - 1);
@@ -60,21 +64,23 @@ export default function EvolutionRules() {
     }, noop);
   }, [periodStartDate]);
 
-  if (!latestRulesTotal || !latestRules) {
+  if (!(isDefined(latestRulesTotal) && latestRulesTotal !== 0) || !latestRules) {
     return null;
   }
 
   return (
     <section aria-label={intl.formatMessage({ id: 'quality_profiles.latest_new_rules' })}>
-      <h2 className="sw-heading-md sw-mb-6">
+      <Heading as="h2" hasMarginBottom>
         {intl.formatMessage({ id: 'quality_profiles.latest_new_rules' })}
-      </h2>
+      </Heading>
+
       <ul className="sw-flex sw-flex-col sw-gap-4 sw-body-sm">
         {latestRules.map((rule) => (
           <li className="sw-flex sw-flex-col sw-gap-1" key={rule.key}>
             <div className="sw-truncate">
               <DiscreetLink to={getRulesUrl({ rule_key: rule.key })}>{rule.name}</DiscreetLink>
             </div>
+
             <Note className="sw-truncate">
               {rule.activations
                 ? translateWithParameters(
@@ -90,6 +96,7 @@ export default function EvolutionRules() {
           </li>
         ))}
       </ul>
+
       {latestRulesTotal > RULES_LIMIT && (
         <div className="sw-mt-6 sw-body-sm-highlight">
           <Link to={getRulesUrl({ available_since: periodStartDate })}>
@@ -107,6 +114,7 @@ export default function EvolutionRules() {
 function parseRules(rules: Rule[], actives?: Record<string, RuleActivation[]>): ExtendedRule[] {
   return rules.map((rule) => {
     const activations = actives?.[rule.key]?.length ?? 0;
+
     return { ...rule, activations };
   });
 }
index e41b60b349d1d70c9dcfc49a34e4a1d67952a8b9..d44fe4cab528813343654f3e2ff530c9e13d0be9 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 { Heading } from '@sonarsource/echoes-react';
 import { DiscreetLink, FlagMessage, Note } from 'design-system';
 import * as React from 'react';
 import { useIntl } from 'react-intl';
 import DateFormatter from '../../../components/intl/DateFormatter';
+import { isDefined } from '../../../helpers/types';
 import { Profile } from '../types';
 import { getProfilePath, isStagnant } from '../utils';
 
@@ -28,7 +31,7 @@ interface Props {
   profiles: Profile[];
 }
 
-export default function EvolutionStagnant(props: Props) {
+export default function EvolutionStagnant(props: Readonly<Props>) {
   const intl = useIntl();
   const outdated = props.profiles.filter((profile) => !profile.isBuiltIn && isStagnant(profile));
 
@@ -38,13 +41,14 @@ export default function EvolutionStagnant(props: Props) {
 
   return (
     <section aria-label={intl.formatMessage({ id: 'quality_profiles.stagnant_profiles' })}>
-      <h2 className="sw-heading-md sw-mb-6">
+      <Heading as="h2" hasMarginBottom>
         {intl.formatMessage({ id: 'quality_profiles.stagnant_profiles' })}
-      </h2>
+      </Heading>
 
       <FlagMessage variant="warning" className="sw-mb-3">
         {intl.formatMessage({ id: 'quality_profiles.not_updated_more_than_year' })}
       </FlagMessage>
+
       <ul className="sw-flex sw-flex-col sw-gap-4 sw-body-sm">
         {outdated.map((profile) => (
           <li className="sw-flex sw-flex-col sw-gap-1" key={profile.key}>
@@ -53,7 +57,8 @@ export default function EvolutionStagnant(props: Props) {
                 {profile.name}
               </DiscreetLink>
             </div>
-            {profile.rulesUpdatedAt && (
+
+            {isDefined(profile.rulesUpdatedAt) && profile.rulesUpdatedAt !== '' && (
               <Note>
                 <DateFormatter date={profile.rulesUpdatedAt} long>
                   {(formattedDate) =>
index 55043a09630e4cd76178bf22ebc63432e0fbd5d1..1fd5d6c299a0d245f32e6a6b4c0ae88ff9834b53 100644 (file)
@@ -17,8 +17,9 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { Button, ButtonGroup, ButtonVariety } from '@sonarsource/echoes-react';
-import { FlagMessage, Link } from 'design-system';
+
+import { Button, ButtonGroup, ButtonVariety, Heading, Link } from '@sonarsource/echoes-react';
+import { FlagMessage } from 'design-system';
 import * as React from 'react';
 import { useIntl } from 'react-intl';
 import { useLocation, useRouter } from '~sonar-aligned/components/hoc/withRouter';
@@ -61,7 +62,10 @@ export default function PageHeader(props: Readonly<Props>) {
   return (
     <header className="sw-grid sw-grid-cols-3 sw-gap-12">
       <div className="sw-col-span-2">
-        <h1 className="sw-heading-lg sw-mb-4">{translate('quality_profiles.page')}</h1>
+        <Heading as="h1" hasMarginBottom>
+          {translate('quality_profiles.page')}
+        </Heading>
+
         <div className="sw-body-sm">
           {intl.formatMessage({ id: 'quality_profiles.intro' })}
 
@@ -70,6 +74,7 @@ export default function PageHeader(props: Readonly<Props>) {
           </Link>
         </div>
       </div>
+
       {actions.create && (
         <div className="sw-flex sw-flex-col sw-items-end">
           <ButtonGroup>
@@ -81,10 +86,12 @@ export default function PageHeader(props: Readonly<Props>) {
             >
               {intl.formatMessage({ id: 'create' })}
             </Button>
+
             <Button id="quality-profiles-restore" onClick={() => setModal('restoreProfile')}>
               {intl.formatMessage({ id: 'restore' })}
             </Button>
           </ButtonGroup>
+
           {languages.length === 0 && (
             <FlagMessage className="sw-mt-2" variant="warning">
               {intl.formatMessage({ id: 'quality_profiles.no_languages_available' })}
index 1fd491db24fde48f0b765beee3e7fd2723a1a658..d7914cb8807f86c219245b5b905162228844f907 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
+import { Heading } from '@sonarsource/echoes-react';
 import {
   ButtonPrimary,
   ContentCell,
@@ -25,7 +26,6 @@ import {
   InputField,
   InputSelect,
   Spinner,
-  SubHeading,
   Table,
   TableRow,
 } from 'design-system';
@@ -197,7 +197,9 @@ export function TokensForm(props: Readonly<Props>) {
     <>
       <GreySeparator className="sw-mb-4 sw-mt-6" />
 
-      <SubHeading as="h2">{translate('users.tokens.generate')}</SubHeading>
+      <Heading as="h2" hasMarginBottom>
+        {translate('users.tokens.generate')}
+      </Heading>
 
       <form autoComplete="off" className="sw-flex sw-items-center" onSubmit={handleGenerateToken}>
         <div className="sw-flex sw-flex-col sw-mr-2">