* 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';
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>
* 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 {
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';
<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" />
* 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';
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">
* 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';
{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} />
</>
* 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';
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 />
</>
);
}
*/
import styled from '@emotion/styled';
-import { Spinner } from '@sonarsource/echoes-react';
+import { Heading, Spinner } from '@sonarsource/echoes-react';
import {
LAYOUT_FOOTER_HEIGHT,
LargeCenteredLayout,
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) => {
<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>
* 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';
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' },
<Note>
{profile.languageName}
+
{', '}
+
<DiscreetLink
className="link-no-underline"
to={getDeprecatedActiveRulesUrl({ qprofile: profile.key })}
)}
</DiscreetLink>
</Note>
+
<EvolutionDeprecatedInherited
profile={profile}
profilesWithDeprecations={profilesWithDeprecations}
) {
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[]) {
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);
}
* 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';
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';
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);
}, 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(
</li>
))}
</ul>
+
{latestRulesTotal > RULES_LIMIT && (
<div className="sw-mt-6 sw-body-sm-highlight">
<Link to={getRulesUrl({ available_since: periodStartDate })}>
function parseRules(rules: Rule[], actives?: Record<string, RuleActivation[]>): ExtendedRule[] {
return rules.map((rule) => {
const activations = actives?.[rule.key]?.length ?? 0;
+
return { ...rule, activations };
});
}
* 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';
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));
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}>
{profile.name}
</DiscreetLink>
</div>
- {profile.rulesUpdatedAt && (
+
+ {isDefined(profile.rulesUpdatedAt) && profile.rulesUpdatedAt !== '' && (
<Note>
<DateFormatter date={profile.rulesUpdatedAt} long>
{(formattedDate) =>
* 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';
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' })}
</Link>
</div>
</div>
+
{actions.create && (
<div className="sw-flex sw-flex-col sw-items-end">
<ButtonGroup>
>
{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' })}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Heading } from '@sonarsource/echoes-react';
import {
ButtonPrimary,
ContentCell,
InputField,
InputSelect,
Spinner,
- SubHeading,
Table,
TableRow,
} from 'design-system';
<>
<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">