size?: keyof typeof SIZE;
status: QGStatus;
tooltipPlacement?: BasePlacement;
- withTooltip?: boolean;
}
const RX_4 = 4;
size = 'md',
status,
tooltipPlacement = PopupPlacement.Right,
- withTooltip,
ariaLabel,
} = props;
const iconProps = {
size,
tooltipPlacement,
width: SIZE[size],
- withTooltip,
};
let StatusComponent: React.ReactNode;
switch (status) {
size: keyof typeof SIZE;
tooltipPlacement?: BasePlacement;
width: string;
- withTooltip?: boolean;
}
-function QGNotComputed({
- className,
- rx,
- size,
- tooltipPlacement,
- withTooltip,
- ...sizeProps
-}: IconProps) {
+function QGNotComputed({ className, rx, size, tooltipPlacement, ...sizeProps }: IconProps) {
const theme = useTheme();
const contrastColor = themeContrast('qgIndicatorNotComputed')({ theme });
return (
);
}
-function QGPassed({ className, rx, size, tooltipPlacement, withTooltip, ...sizeProps }: IconProps) {
+function QGPassed({ className, rx, size, tooltipPlacement, ...sizeProps }: IconProps) {
const theme = useTheme();
const contrastColor = themeContrast('qgIndicatorPassed')({ theme });
return (
);
}
-function QGFailed({ className, rx, size, tooltipPlacement, withTooltip, ...sizeProps }: IconProps) {
+function QGFailed({ className, rx, size, tooltipPlacement, ...sizeProps }: IconProps) {
const theme = useTheme();
const contrastColor = themeContrast('qgIndicatorFailed')({ theme });
return (
export const LightPrimary = styled.span`
color: ${themeContrast('primaryLight')};
`;
+
+export const PageContentFontWrapper = styled.div`
+ color: ${themeColor('pageContent')};
+`;
it('should display tooltip', () => {
const { rerender } = setupWithProps({
status: 'NONE',
- withTooltip: true,
ariaLabel: 'label-none',
});
expect(screen.getByLabelText('label-none')).toBeInTheDocument();
- rerender(<QualityGateIndicator ariaLabel="label-ok" status="OK" withTooltip={true} />);
+ rerender(<QualityGateIndicator ariaLabel="label-ok" status="OK" />);
expect(screen.getByLabelText('label-ok')).toBeInTheDocument();
- rerender(<QualityGateIndicator ariaLabel="label-error" status="ERROR" withTooltip={true} />);
+ rerender(<QualityGateIndicator ariaLabel="label-error" status="ERROR" />);
expect(screen.getByLabelText('label-error')).toBeInTheDocument();
});
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { ThemeProvider } from '@emotion/react';
+import classNames from 'classnames';
import { lightTheme } from 'design-system';
import * as React from 'react';
import { Outlet, useLocation } from 'react-router-dom';
import A11ySkipLinks from '../../components/a11y/A11ySkipLinks';
import SuggestionsProvider from '../../components/embed-docs-modal/SuggestionsProvider';
import Workspace from '../../components/workspace/Workspace';
-import BranchStatusContextProvider from './branch-status/BranchStatusContextProvider';
import GlobalFooter from './GlobalFooter';
+import StartupModal from './StartupModal';
+import SystemAnnouncement from './SystemAnnouncement';
+import BranchStatusContextProvider from './branch-status/BranchStatusContextProvider';
import IndexationContextProvider from './indexation/IndexationContextProvider';
import IndexationNotification from './indexation/IndexationNotification';
import LanguagesContextProvider from './languages/LanguagesContextProvider';
import MetricsContextProvider from './metrics/MetricsContextProvider';
import GlobalNav from './nav/global/GlobalNav';
import PromotionNotification from './promotion-notification/PromotionNotification';
-import StartupModal from './StartupModal';
-import SystemAnnouncement from './SystemAnnouncement';
import UpdateNotification from './update-notification/UpdateNotification';
+const TEMP_PAGELIST_WITH_NEW_BACKGROUND = ['/dashboard'];
+
export default function GlobalContainer() {
// it is important to pass `location` down to `GlobalNav` to trigger render on url change
const location = useLocation();
<StartupModal>
<A11ySkipLinks />
<div className="global-container">
- <div className="page-wrapper" id="container">
+ <div
+ className={classNames('page-wrapper', {
+ 'new-background': TEMP_PAGELIST_WITH_NEW_BACKGROUND.includes(location.pathname),
+ })}
+ id="container"
+ >
<div className="page-container">
<BranchStatusContextProvider>
<Workspace>
font-family: var(--sourceCodeFontFamily);
font-size: var(--smallFontSize);
}
+
+.new-background {
+ background-color: #fcfcfd;
+}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Card, FlagMessage, Link } from 'design-system';
import * as React from 'react';
-import DocLink from '../../../components/common/DocLink';
-import Link from '../../../components/common/Link';
-import QualifierIcon from '../../../components/icons/QualifierIcon';
-import { Alert } from '../../../components/ui/Alert';
+import withAppStateContext, {
+ WithAppStateContextProps,
+} from '../../../app/components/app-state/withAppStateContext';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
+import { getUrlForDoc } from '../../../helpers/docs';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getProjectQueryUrl } from '../../../helpers/urls';
-import { ComponentQualifier } from '../../../types/component';
import { QualityGateStatus } from '../../../types/quality-gates';
import { CaycStatus } from '../../../types/types';
caycStatus: CaycStatus;
}
-export default function ApplicationNonCaycProjectWarning({ projects, caycStatus }: Props) {
+function ApplicationNonCaycProjectWarning({
+ projects,
+ caycStatus,
+ appState,
+}: Props & WithAppStateContextProps) {
+ const caycUrl = getUrlForDoc(appState.version, '/user-guide/clean-as-you-code/');
+ const caycDrawbacksUrl = getUrlForDoc(
+ appState.version,
+ '/user-guide/clean-as-you-code/#potential-drawbacks'
+ );
+
return (
- <div className="overview-quality-gate-conditions-list padded big-spacer-top">
+ <Card className="sw-mt-4 sw-body-sm">
{caycStatus === CaycStatus.NonCompliant ? (
- <Alert variant="warning">
+ <FlagMessage
+ ariaLabel={translateWithParameters(
+ 'overview.quality_gate.application.non_cayc.projects_x',
+ projects.length
+ )}
+ variant="warning"
+ >
{translateWithParameters(
'overview.quality_gate.application.non_cayc.projects_x',
projects.length
)}
- </Alert>
+ </FlagMessage>
) : (
- <p className="padded">
+ <p className="sw-p-2">
{translateWithParameters(
'overview.quality_gate.application.cayc_over_compliant.projects_x',
projects.length
</p>
)}
- <ul className="spacer-left spacer-bottom big-spacer-top">
+ <ul className="sw-mt-4 sw-ml-2 sw-mb-2">
{projects.map(({ key, name, branchLike }) => (
- <li key={key} className="text-ellipsis spacer-bottom" title={name}>
- <Link
- className="link-no-underline"
- to={getProjectQueryUrl(key, getBranchLikeQuery(branchLike))}
- >
- <QualifierIcon
- className="little-spacer-right"
- qualifier={ComponentQualifier.Project}
- />
- {name}
- </Link>
+ <li key={key} className="sw-text-ellipsis sw-mb-2" title={name}>
+ <Link to={getProjectQueryUrl(key, getBranchLikeQuery(branchLike))}>{name}</Link>
</li>
))}
</ul>
- <hr className="big-spacer-top big-spacer-bottom" />
- <div className="spacer spacer-bottom big-spacer-top">
+ <hr className="sw-my-4" />
+ <div className="sw-m-2 sw-mt-4">
{caycStatus === CaycStatus.NonCompliant ? (
- <DocLink to="/user-guide/clean-as-you-code/">
- {translate('overview.quality_gate.conditions.cayc.link')}
- </DocLink>
+ <Link to={caycUrl}>{translate('overview.quality_gate.conditions.cayc.link')}</Link>
) : (
- <DocLink to="/user-guide/clean-as-you-code/#potential-drawbacks">
+ <Link to={caycDrawbacksUrl}>
{translate('overview.quality_gate.conditions.cayc_over_compliant.link')}
- </DocLink>
+ </Link>
)}
</div>
- </div>
+ </Card>
);
}
+
+export default withAppStateContext(ApplicationNonCaycProjectWarning);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { LargeCenteredLayout } from 'design-system';
+import { LargeCenteredLayout, PageContentFontWrapper } from 'design-system';
import * as React from 'react';
import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
import { parseDate } from '../../../helpers/dates';
projectBinding={projectBinding}
/>
<LargeCenteredLayout>
- <div className="overview sw-mt-6">
- <A11ySkipTarget anchor="overview_main" />
+ <PageContentFontWrapper>
+ <div className="overview sw-mt-6 sw-body-sm">
+ <A11ySkipTarget anchor="overview_main" />
- {projectIsEmpty ? (
- <NoCodeWarning branchLike={branch} component={component} measures={measures} />
- ) : (
- <div className="sw-flex">
- <div className="width-30 sw-mr-12 sw-pt-6">
- <QualityGatePanel
- component={component}
- loading={loadingStatus}
- qgStatuses={qgStatuses}
- />
- </div>
-
- <div className="sw-flex-1">
- <div className="sw-flex sw-flex-col sw-pt-6">
- <MeasuresPanel
- appLeak={appLeak}
- branch={branch}
+ {projectIsEmpty ? (
+ <NoCodeWarning branchLike={branch} component={component} measures={measures} />
+ ) : (
+ <div className="sw-flex">
+ <div className="sw-w-1/3 sw-mr-12 sw-pt-6">
+ <QualityGatePanel
component={component}
loading={loadingStatus}
- measures={measures}
- period={period}
qgStatuses={qgStatuses}
/>
+ </div>
- <ActivityPanel
- analyses={analyses}
- branchLike={branch}
- component={component}
- graph={graph}
- leakPeriodDate={leakPeriod && parseDate(leakPeriod.date)}
- loading={loadingHistory}
- measuresHistory={measuresHistory}
- metrics={metrics}
- onGraphChange={onGraphChange}
- />
+ <div className="sw-flex-1">
+ <div className="sw-flex sw-flex-col sw-pt-6">
+ <MeasuresPanel
+ appLeak={appLeak}
+ branch={branch}
+ component={component}
+ loading={loadingStatus}
+ measures={measures}
+ period={period}
+ qgStatuses={qgStatuses}
+ />
+
+ <ActivityPanel
+ analyses={analyses}
+ branchLike={branch}
+ component={component}
+ graph={graph}
+ leakPeriodDate={leakPeriod && parseDate(leakPeriod.date)}
+ loading={loadingHistory}
+ measuresHistory={measuresHistory}
+ metrics={metrics}
+ onGraphChange={onGraphChange}
+ />
+ </div>
</div>
</div>
- </div>
- )}
- </div>
+ )}
+ </div>
+ </PageContentFontWrapper>
</LargeCenteredLayout>
</>
);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { DiscreetLink, FlagMessage, Link } from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import DocLink from '../../../components/common/DocLink';
-import Link from '../../../components/common/Link';
-import { Alert } from '../../../components/ui/Alert';
+import withAppStateContext, {
+ WithAppStateContextProps,
+} from '../../../app/components/app-state/withAppStateContext';
+import { getUrlForDoc } from '../../../helpers/docs';
import { translate } from '../../../helpers/l10n';
import { getQualityGateUrl } from '../../../helpers/urls';
import { Component } from '../../../types/types';
component: Pick<Component, 'key' | 'qualifier' | 'qualityGate'>;
}
-export default function CleanAsYouCodeWarning({ component }: Props) {
+function CleanAsYouCodeWarning({ component, appState }: Props & WithAppStateContextProps) {
+ const caycUrl = getUrlForDoc(appState.version, '/user-guide/clean-as-you-code/');
+
return (
<>
- <Alert variant="warning">{translate('overview.quality_gate.conditions.cayc.warning')}</Alert>
+ <FlagMessage
+ ariaLabel={translate('overview.quality_gate.conditions.cayc.warning')}
+ variant="warning"
+ >
+ {translate('overview.quality_gate.conditions.cayc.warning')}
+ </FlagMessage>
{component.qualityGate ? (
- <p className="big-spacer-top big-spacer-bottom">
+ <p className="sw-my-4">
<FormattedMessage
id="overview.quality_gate.conditions.cayc.details_with_link"
defaultMessage={translate('overview.quality_gate.conditions.cayc.details_with_link')}
values={{
link: (
- <Link to={getQualityGateUrl(component.qualityGate.name)}>
+ <DiscreetLink to={getQualityGateUrl(component.qualityGate.name)}>
{translate('overview.quality_gate.conditions.non_cayc.warning.link')}
- </Link>
+ </DiscreetLink>
),
}}
/>
</p>
) : (
- <p className="big-spacer-top big-spacer-bottom">
- {translate('overview.quality_gate.conditions.cayc.details')}
- </p>
+ <p className="sw-my-4">{translate('overview.quality_gate.conditions.cayc.details')}</p>
)}
- <DocLink to="/user-guide/clean-as-you-code/">
- {translate('overview.quality_gate.conditions.cayc.link')}
- </DocLink>
+ <Link to={caycUrl}>{translate('overview.quality_gate.conditions.cayc.link')}</Link>
</>
);
}
+
+export default withAppStateContext(CleanAsYouCodeWarning);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { DiscreetLink, Link } from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import DocLink from '../../../components/common/DocLink';
-import Link from '../../../components/common/Link';
+import withAppStateContext, {
+ WithAppStateContextProps,
+} from '../../../app/components/app-state/withAppStateContext';
+import { getUrlForDoc } from '../../../helpers/docs';
import { translate } from '../../../helpers/l10n';
import { getQualityGateUrl } from '../../../helpers/urls';
import { Component } from '../../../types/types';
component: Pick<Component, 'key' | 'qualifier' | 'qualityGate'>;
}
-export default function CleanAsYouCodeWarningOverCompliant({ component }: Props) {
+function CleanAsYouCodeWarningOverCompliant({
+ component,
+ appState,
+}: Props & WithAppStateContextProps) {
+ const caycDrawbackUrl = getUrlForDoc(
+ appState.version,
+ '/user-guide/clean-as-you-code/#potential-drawbacks'
+ );
+
return (
<>
{component.qualityGate ? (
- <p className="big-spacer-bottom">
+ <p className="sw-mb-4">
<FormattedMessage
id="overview.quality_gate.conditions.cayc_over_compliant.details_with_link"
defaultMessage={translate(
)}
values={{
link: (
- <Link to={getQualityGateUrl(component.qualityGate.name)}>
+ <DiscreetLink to={getQualityGateUrl(component.qualityGate.name)}>
{translate('overview.quality_gate.conditions.cayc_over_compliant.warning.link')}
- </Link>
+ </DiscreetLink>
),
}}
/>
</p>
) : (
- <p className="big-spacer-bottom">
+ <p className="sw-mb-4">
{translate('overview.quality_gate.conditions.cayc_over_compliant.details')}
</p>
)}
- <DocLink to="/user-guide/clean-as-you-code/#potential-drawbacks">
+ <Link to={caycDrawbackUrl}>
{translate('overview.quality_gate.conditions.cayc_over_compliant.link')}
- </DocLink>
+ </Link>
</>
);
}
+
+export default withAppStateContext(CleanAsYouCodeWarningOverCompliant);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { FlagMessage } from 'design-system';
import * as React from 'react';
-import { Alert } from '../../../components/ui/Alert';
import { getBranchLikeDisplayName, isMainBranch } from '../../../helpers/branch-like';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { BranchLike } from '../../../types/branch-like';
/* eslint-enable no-lonely-if */
return (
- <Alert display="banner" variant="warning">
+ <FlagMessage ariaLabel={title} variant="warning">
{title}
- </Alert>
+ </FlagMessage>
);
}
import { QualityGateStatusTitle } from '../components/QualityGateStatusTitle';
import SonarLintPromotion from '../components/SonarLintPromotion';
import ApplicationNonCaycProjectWarning from './ApplicationNonCaycProjectWarning';
+import CleanAsYouCodeWarning from './CleanAsYouCodeWarning';
+import CleanAsYouCodeWarningOverCompliant from './CleanAsYouCodeWarningOverCompliant';
import QualityGatePanelSection from './QualityGatePanelSection';
export interface QualityGatePanelProps {
caycStatus={CaycStatus.OverCompliant}
/>
)}
+
+ {qgStatuses.length === 1 &&
+ qgStatuses[0].caycStatus === CaycStatus.NonCompliant &&
+ !isApplication(component.qualifier) && (
+ <Card className="sw-mt-4 sw-body-sm">
+ <CleanAsYouCodeWarning component={component} />
+ </Card>
+ )}
+
+ {qgStatuses.length === 1 &&
+ qgStatuses[0].caycStatus === CaycStatus.OverCompliant &&
+ !isApplication(component.qualifier) && (
+ <Card className="sw-mt-4 sw-body-sm">
+ <CleanAsYouCodeWarningOverCompliant component={component} />
+ </Card>
+ )}
+
<SonarLintPromotion
qgConditions={flatMap(qgStatuses, (qgStatus) => qgStatus.failedConditions)}
/>
} from '../../../types/quality-gates';
import { CaycStatus, Component } from '../../../types/types';
import QualityGateConditions from '../components/QualityGateConditions';
-import CleanAsYouCodeWarning from './CleanAsYouCodeWarning';
-import CleanAsYouCodeWarningOverCompliant from './CleanAsYouCodeWarningOverCompliant';
export interface QualityGatePanelSectionProps {
branchLike?: BranchLike;
<BasicSeparator />
</>
) : (
- <>
- {renderFailedConditions()}
- {qgStatus.caycStatus === CaycStatus.NonCompliant &&
- !isApplication(component.qualifier) && (
- <div className="big-padded bordered-bottom overview-quality-gate-conditions-list">
- <CleanAsYouCodeWarning component={component} />
- </div>
- )}
- {qgStatus.caycStatus === CaycStatus.OverCompliant &&
- !isApplication(component.qualifier) && (
- <div className="big-padded bordered-bottom overview-quality-gate-conditions-list">
- <CleanAsYouCodeWarningOverCompliant component={component} />
- </div>
- )}
- </>
+ renderFailedConditions()
)}
</>
);
expect(screen.getByText('overview.quality_gate.conditions.cayc.warning')).toBeInTheDocument();
});
+ it('should show Cayc message when QG is over-compliant', async () => {
+ jest
+ .mocked(getQualityGateProjectStatus)
+ .mockResolvedValueOnce(
+ mockQualityGateProjectStatus({ status: 'OK', caycStatus: CaycStatus.OverCompliant })
+ );
+
+ renderBranchOverview();
+
+ expect(await screen.findByText('metric.level.OK')).toBeInTheDocument();
+ expect(
+ screen.getByText('overview.quality_gate.conditions.cayc_over_compliant.link')
+ ).toBeInTheDocument();
+ });
+
it('should show a failed QG', async () => {
renderBranchOverview();
return <LinkBox to={METRICS_TO_URL_MAPPING[metricKey]()}>{children}</LinkBox>;
}
- if (isIssueMeasure(condition.measure.metric.key)) {
- const url = getComponentIssuesUrl(component.key, {
- ...propsToIssueParams(condition.measure.metric.key, condition.period != null),
- ...getBranchLikeQuery(branchLike),
- });
-
- return <LinkBox to={url}>{children}</LinkBox>;
- }
-
- const url = getComponentDrilldownUrl({
- componentKey: component.key,
- metric: condition.measure.metric.key,
- branchLike,
- listView: true,
- });
+ const url = isIssueMeasure(condition.measure.metric.key)
+ ? getComponentIssuesUrl(component.key, {
+ ...propsToIssueParams(condition.measure.metric.key, condition.period != null),
+ ...getBranchLikeQuery(branchLike),
+ })
+ : getComponentDrilldownUrl({
+ componentKey: component.key,
+ metric: condition.measure.metric.key,
+ branchLike,
+ listView: true,
+ });
return <LinkBox to={url}>{children}</LinkBox>;
}
rel="noopener noreferrer"
target="_blank"
showExternalIcon={false}
+ className="sw-mr-1"
>
SonarLint
</DiscreetLink>
- <SonarLintIcon size={16} />
+ <span className="sw-align-middle">
+ <SonarLintIcon size={16} />
+ </span>
</>
),
}}
import { duplicationRatingConverter } from '../utils';
describe('duplicationRatingConverter', () => {
- it('should work correctly for different use cases', () => {
- expect(duplicationRatingConverter(-10)).toEqual('A');
- expect(duplicationRatingConverter(2)).toEqual('A');
- expect(duplicationRatingConverter(4)).toEqual('B');
- expect(duplicationRatingConverter(8)).toEqual('C');
- expect(duplicationRatingConverter(18)).toEqual('D');
- expect(duplicationRatingConverter(20)).toEqual('E');
- expect(duplicationRatingConverter(25)).toEqual('E');
+ it.each([
+ [-10, 'A'],
+ [2, 'A'],
+ [4, 'B'],
+ [8, 'C'],
+ [18, 'D'],
+ [20, 'E'],
+ [25, 'E'],
+ ])('should work correctly when value is %s', (value: number, result: string) => {
+ expect(duplicationRatingConverter(value)).toEqual(result);
});
});