diff options
author | David Cho-Lerat <david.cho-lerat@sonarsource.com> | 2024-09-18 14:52:23 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-09-24 20:03:04 +0000 |
commit | 9f80a169ea2c85db1cd8479c42ce609b80aeafaf (patch) | |
tree | 26dcfcc6d66a2e08e81b470fc69325d19b9e07a1 /server | |
parent | 7869ce9d4981e5ce428aef07bed9e99ca7506f47 (diff) | |
download | sonarqube-9f80a169ea2c85db1cd8479c42ce609b80aeafaf.tar.gz sonarqube-9f80a169ea2c85db1cd8479c42ce609b80aeafaf.zip |
SONAR-23030 Use Heading component
Diffstat (limited to 'server')
11 files changed, 112 insertions, 68 deletions
diff --git a/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx b/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx index 700f7f6e08a..aa7f9a13d9c 100644 --- a/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx +++ b/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx @@ -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> diff --git a/server/sonar-web/src/main/js/apps/account/notifications/Notifications.tsx b/server/sonar-web/src/main/js/apps/account/notifications/Notifications.tsx index c3f60c9cce3..cce33e291ee 100644 --- a/server/sonar-web/src/main/js/apps/account/notifications/Notifications.tsx +++ b/server/sonar-web/src/main/js/apps/account/notifications/Notifications.tsx @@ -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" /> diff --git a/server/sonar-web/src/main/js/apps/account/notifications/Projects.tsx b/server/sonar-web/src/main/js/apps/account/notifications/Projects.tsx index fe009cb6d60..c18e1181ea5 100644 --- a/server/sonar-web/src/main/js/apps/account/notifications/Projects.tsx +++ b/server/sonar-web/src/main/js/apps/account/notifications/Projects.tsx @@ -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"> diff --git a/server/sonar-web/src/main/js/apps/account/security/Security.tsx b/server/sonar-web/src/main/js/apps/account/security/Security.tsx index b1ea17b4c5e..0539ef8aff4 100644 --- a/server/sonar-web/src/main/js/apps/account/security/Security.tsx +++ b/server/sonar-web/src/main/js/apps/account/security/Security.tsx @@ -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} /> </> diff --git a/server/sonar-web/src/main/js/apps/account/security/Tokens.tsx b/server/sonar-web/src/main/js/apps/account/security/Tokens.tsx index 27d6728c85e..f177b304a49 100644 --- a/server/sonar-web/src/main/js/apps/account/security/Tokens.tsx +++ b/server/sonar-web/src/main/js/apps/account/security/Tokens.tsx @@ -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 /> </> ); } diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx index b366a9ca956..0a2ea49e239 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx @@ -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> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx index 735bf38a28d..4b3080b4499 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx @@ -17,10 +17,13 @@ * 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); } diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx index 125155c9f42..0e680661200 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx @@ -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 }; }); } diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.tsx index e41b60b349d..d44fe4cab52 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.tsx @@ -17,10 +17,13 @@ * 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) => diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx index 55043a09630..1fd5d6c299a 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx @@ -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' })} diff --git a/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx b/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx index 1fd491db24f..d7914cb8807 100644 --- a/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx @@ -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"> |