]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21467 Adopt new 2 column layout for branch overview
authorstanislavh <stanislav.honcharov@sonarsource.com>
Tue, 23 Jan 2024 10:41:55 +0000 (11:41 +0100)
committersonartech <sonartech@sonarsource.com>
Fri, 26 Jan 2024 20:02:46 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx
server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx
server/sonar-web/src/main/js/apps/overview/branches/TabsPanel.tsx
server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx
server/sonar-web/src/main/js/apps/overview/components/LastAnalysisLabel.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/components/QualityGateStatusHeader.tsx
server/sonar-web/src/main/js/apps/overview/components/QualityGateStatusTitle.tsx
server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestMetaTopBar.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 0dca8d3241eafc2946014cd544b1755c849e121b..9c6e441576641a179bbd6bc5a63405687d279668 100644 (file)
@@ -17,7 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { BasicSeparator, LargeCenteredLayout, PageContentFontWrapper } from 'design-system';
+import { BasicSeparator, Card, LargeCenteredLayout, PageContentFontWrapper } from 'design-system';
 import * as React from 'react';
 import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import { useLocation } from '../../../components/hoc/withRouter';
@@ -31,6 +31,7 @@ import { Analysis, GraphType, MeasureHistory } from '../../../types/project-acti
 import { QualityGateStatus } from '../../../types/quality-gates';
 import { Component, MeasureEnhanced, Metric, Period, QualityGate } from '../../../types/types';
 import { AnalysisStatus } from '../components/AnalysisStatus';
+import SonarLintPromotion from '../components/SonarLintPromotion';
 import { MeasuresTabs } from '../utils';
 import AcceptedIssuesPanel from './AcceptedIssuesPanel';
 import ActivityPanel from './ActivityPanel';
@@ -125,24 +126,30 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
                   </>
                 )}
                 <AnalysisStatus className="sw-mt-6" component={component} />
-                <div className="sw-flex">
-                  <div className="sw-w-1/3 sw-mr-12 sw-pt-6">
-                    <QualityGatePanel
-                      component={component}
-                      loading={loadingStatus}
-                      qgStatuses={qgStatuses}
-                      qualityGate={qualityGate}
+                <div className="sw-flex sw-mt-6">
+                  <div className="sw-w-1/4 sw-mr-3">
+                    <Card className=" sw-h-max">
+                      <QualityGatePanel
+                        component={component}
+                        loading={loadingStatus}
+                        qgStatuses={qgStatuses}
+                        qualityGate={qualityGate}
+                      />
+                    </Card>
+                    <SonarLintPromotion
+                      qgConditions={qgStatuses?.flatMap((qg) => qg.failedConditions)}
                     />
                   </div>
 
-                  <div className="sw-flex-1">
-                    <div className="sw-flex sw-flex-col sw-pt-6">
+                  <Card className="sw-flex-1 sw-pt-4">
+                    <div className="sw-flex sw-flex-col">
                       <TabsPanel
                         analyses={analyses}
                         appLeak={appLeak}
                         component={component}
                         loading={loadingStatus}
                         period={period}
+                        branch={branch}
                         qgStatuses={qgStatuses}
                         isNewCode={isNewCodeTab}
                         onTabSelect={selectTab}
@@ -185,7 +192,7 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
                         onGraphChange={onGraphChange}
                       />
                     </div>
-                  </div>
+                  </Card>
                 </div>
               </div>
             )}
index e621fdc3b32af5b023f1e9eb4c6e0d7cdd762637..0118908120d05abe26b5767f1bb8a2933c46d3e0 100644 (file)
@@ -18,7 +18,6 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { BasicSeparator, Card, Spinner } from 'design-system';
-import { flatMap } from 'lodash';
 import * as React from 'react';
 import { ComponentQualifier, isApplication } from '../../../types/component';
 import { QualityGateStatus } from '../../../types/quality-gates';
@@ -27,7 +26,6 @@ import IgnoredConditionWarning from '../components/IgnoredConditionWarning';
 import QualityGateStatusHeader from '../components/QualityGateStatusHeader';
 import QualityGateStatusPassedView from '../components/QualityGateStatusPassedView';
 import { QualityGateStatusTitle } from '../components/QualityGateStatusTitle';
-import SonarLintPromotion from '../components/SonarLintPromotion';
 import ApplicationNonCaycProjectWarning from './ApplicationNonCaycProjectWarning';
 import CleanAsYouCodeWarning from './CleanAsYouCodeWarning';
 import QualityGatePanelSection from './QualityGatePanelSection';
@@ -71,41 +69,33 @@ export function QualityGatePanel(props: QualityGatePanelProps) {
   return (
     <div data-test="overview__quality-gate-panel">
       <QualityGateStatusTitle />
-      <Card>
-        <div>
-          {loading ? (
-            <div className="sw-p-6">
-              <Spinner loading={loading} />
+      <div className="sw-pt-5">
+        <Spinner loading={loading}>
+          <QualityGateStatusHeader
+            status={overallLevel}
+            failedConditionCount={overallFailedConditionsCount}
+          />
+          {success && <QualityGateStatusPassedView />}
+
+          {showIgnoredConditionWarning && <IgnoredConditionWarning />}
+
+          {!success && <BasicSeparator />}
+
+          {overallFailedConditionsCount > 0 && (
+            <div data-test="overview__quality-gate-conditions">
+              {failedQgStatuses.map((qgStatus, qgStatusIdx) => (
+                <QualityGatePanelSection
+                  isApplication={isApp}
+                  isLastStatus={qgStatusIdx === failedQgStatuses.length - 1}
+                  key={qgStatus.key}
+                  qgStatus={qgStatus}
+                  qualityGate={qualityGate}
+                />
+              ))}
             </div>
-          ) : (
-            <>
-              <QualityGateStatusHeader
-                status={overallLevel}
-                failedConditionCount={overallFailedConditionsCount}
-              />
-              {success && <QualityGateStatusPassedView />}
-
-              {showIgnoredConditionWarning && <IgnoredConditionWarning />}
-
-              {!success && <BasicSeparator />}
-
-              {overallFailedConditionsCount > 0 && (
-                <div data-test="overview__quality-gate-conditions">
-                  {failedQgStatuses.map((qgStatus, qgStatusIdx) => (
-                    <QualityGatePanelSection
-                      isApplication={isApp}
-                      isLastStatus={qgStatusIdx === failedQgStatuses.length - 1}
-                      key={qgStatus.key}
-                      qgStatus={qgStatus}
-                      qualityGate={qualityGate}
-                    />
-                  ))}
-                </div>
-              )}
-            </>
           )}
-        </div>
-      </Card>
+        </Spinner>
+      </div>
 
       {nonCaycProjectsInApp.length > 0 && (
         <ApplicationNonCaycProjectWarning projects={nonCaycProjectsInApp} />
@@ -119,10 +109,6 @@ export function QualityGatePanel(props: QualityGatePanelProps) {
             <CleanAsYouCodeWarning component={component} />
           </Card>
         )}
-
-      <SonarLintPromotion
-        qgConditions={flatMap(qgStatuses, (qgStatus) => qgStatus.failedConditions)}
-      />
     </div>
   );
 }
index 9756c817ae9d60da6ba19c103a58a5fcf0b2d8d5..87a370b243b5b5f93fbb6301a852e43efd600d7a 100644 (file)
@@ -20,6 +20,7 @@
 import { isBefore, sub } from 'date-fns';
 import {
   ButtonLink,
+  CardSeparator,
   FlagMessage,
   LightLabel,
   PageTitle,
@@ -27,15 +28,17 @@ import {
   ToggleButton,
 } from 'design-system';
 import * as React from 'react';
-import { FormattedMessage } from 'react-intl';
+import { FormattedMessage, useIntl } from 'react-intl';
 import DocLink from '../../../components/common/DocLink';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { translate } from '../../../helpers/l10n';
 import { isDiffMetric } from '../../../helpers/measures';
 import { ApplicationPeriod } from '../../../types/application';
+import { Branch } from '../../../types/branch-like';
 import { ComponentQualifier } from '../../../types/component';
 import { Analysis, ProjectAnalysisEventCategory } from '../../../types/project-activity';
 import { QualityGateStatus } from '../../../types/quality-gates';
 import { Component, Period } from '../../../types/types';
+import LastAnalysisLabel from '../components/LastAnalysisLabel';
 import { MeasuresTabs } from '../utils';
 import { MAX_ANALYSES_NB } from './ActivityPanel';
 import { LeakPeriodInfo } from './LeakPeriodInfo';
@@ -46,6 +49,7 @@ export interface MeasuresPanelProps {
   component: Component;
   loading?: boolean;
   period?: Period;
+  branch?: Branch;
   qgStatuses?: QualityGateStatus[];
   isNewCode: boolean;
   onTabSelect: (tab: MeasuresTabs) => void;
@@ -62,9 +66,10 @@ export function TabsPanel(props: React.PropsWithChildren<MeasuresPanelProps>) {
     period,
     qgStatuses = [],
     isNewCode,
+    branch,
     children,
   } = props;
-
+  const intl = useIntl();
   const isApp = component.qualifier === ComponentQualifier.Application;
   const leakPeriod = isApp ? appLeak : period;
 
@@ -124,9 +129,11 @@ export function TabsPanel(props: React.PropsWithChildren<MeasuresPanelProps>) {
 
   return (
     <div data-test="overview__measures-panel">
-      <div className="sw-flex sw-mb-4">
+      <div className="sw-flex sw-justify-between sw-items-center sw-mb-4">
         <PageTitle as="h2" text={translate('overview.measures')} />
+        <LastAnalysisLabel analysisDate={branch?.analysisDate} />
       </div>
+      <CardSeparator className="sw--mx-6 sw-mb-3" />
 
       {loading ? (
         <div>
@@ -160,9 +167,10 @@ export function TabsPanel(props: React.PropsWithChildren<MeasuresPanelProps>) {
             />
             {failingConditions > 0 && (
               <LightLabel className="sw-body-sm-highlight sw-ml-8">
-                {failingConditions === 1
-                  ? translate('overview.1_condition_failed')
-                  : translateWithParameters('overview.X_conditions_failed', failingConditions)}
+                {intl.formatMessage(
+                  { id: 'overview.X_conditions_failed' },
+                  { conditions: failingConditions },
+                )}
               </LightLabel>
             )}
           </div>
index b06c4671c1e985ef7ac7780268fa2ec2843d1aee..75e12de4e5ce82fa27a346251d3a49918011cd11 100644 (file)
@@ -308,7 +308,7 @@ describe('project overview', () => {
     renderBranchOverview();
 
     expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument();
-    expect(screen.getAllByText('overview.X_conditions_failed.2')).toHaveLength(2);
+    expect(screen.getAllByText(/overview.X_conditions_failed/)).toHaveLength(2);
   });
 
   it('should correctly show a project as empty', async () => {
diff --git a/server/sonar-web/src/main/js/apps/overview/components/LastAnalysisLabel.tsx b/server/sonar-web/src/main/js/apps/overview/components/LastAnalysisLabel.tsx
new file mode 100644 (file)
index 0000000..5fcb961
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import React from 'react';
+import { useIntl } from 'react-intl';
+import DateFromNow from '../../../components/intl/DateFromNow';
+
+interface Props {
+  analysisDate?: string;
+}
+
+export default function LastAnalysisLabel({ analysisDate }: Readonly<Props>) {
+  const intl = useIntl();
+
+  return analysisDate ? (
+    <span>
+      {intl.formatMessage(
+        {
+          id: 'overview.last_analysis_x',
+        },
+        {
+          date: <DateFromNow className="sw-body-sm-highlight" date={analysisDate} />,
+        },
+      )}
+    </span>
+  ) : null;
+}
index 7b0358799b2202ca50cd9983762a28482180559b..c4eee2fd3ae55334989bae5adc298759b9f1160c 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 { QualityGateIndicator, TextError, TextMuted } from 'design-system';
+import { QualityGateIndicator, TextError } from 'design-system';
 import React from 'react';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { useIntl } from 'react-intl';
+import { translate } from '../../../helpers/l10n';
 import { Status } from '../../../types/types';
 
 interface Props {
@@ -29,26 +30,22 @@ interface Props {
 
 export default function QualityGateStatusHeader(props: Props) {
   const { status, failedConditionCount } = props;
+  const intl = useIntl();
 
   return (
     <div className="sw-flex sw-items-center sw-mb-4">
       <QualityGateIndicator status={status} className="sw-mr-2" size="xl" />
       <div className="sw-flex sw-flex-col">
-        <div>
-          <TextMuted text={translate('overview.quality_gate')} />
-        </div>
-        <div>
-          <span className="sw-heading-lg">{translate('metric.level', status)}</span>
-        </div>
-      </div>
-      <div className="sw-flex sw-flex-1 sw-justify-end">
+        <span className="sw-heading-lg">{translate('metric.level', status)}</span>
         {failedConditionCount > 0 && (
           <TextError
-            text={
-              failedConditionCount === 1
-                ? translate('overview.1_condition_failed')
-                : translateWithParameters('overview.X_conditions_failed', failedConditionCount)
-            }
+            className="sw-font-regular"
+            text={intl.formatMessage(
+              { id: 'overview.X_conditions_failed' },
+              {
+                conditions: <strong>{failedConditionCount}</strong>,
+              },
+            )}
           />
         )}
       </div>
index 57bc32771a0ffc0aebfdc74102fae97dcc40ecd4..4e2e543581c3348ab444901fb0c3d99f9ce8a577 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 { HelperHintIcon, PageTitle } from 'design-system';
+import { CardSeparator, HelperHintIcon, PageTitle } from 'design-system';
 import React from 'react';
 import HelpTooltip from '../../../components/controls/HelpTooltip';
 import { translate } from '../../../helpers/l10n';
 
 export function QualityGateStatusTitle() {
   return (
-    <div className="sw-flex sw-items-center sw-mb-4">
-      <div className="sw-flex sw-items-center">
-        <PageTitle as="h2" text={translate('overview.quality_gate.status')} />
-        <HelpTooltip
-          className="sw-ml-2"
-          overlay={<div className="sw-my-4">{translate('overview.quality_gate.help')}</div>}
-        >
-          <HelperHintIcon aria-label="help-tooltip" />
-        </HelpTooltip>
+    <>
+      <div className="sw-flex sw-items-center sw-mb-4 sw--mt-2">
+        <div className="sw-flex sw-items-center">
+          <PageTitle as="h2" text={translate('overview.quality_gate.status')} />
+          <HelpTooltip
+            className="sw-ml-2"
+            overlay={<div className="sw-my-4">{translate('overview.quality_gate.help')}</div>}
+          >
+            <HelperHintIcon aria-label="help-tooltip" />
+          </HelpTooltip>
+        </div>
       </div>
-    </div>
+      <CardSeparator className="sw--mx-6" />
+    </>
   );
 }
index 72c4a6a35cc555e833ec8d96e039ed3b95b2ad54..0ce4a8aae1fab5c11952bef6f520993fb68db881 100644 (file)
@@ -21,12 +21,12 @@ import { SeparatorCircleIcon } from 'design-system';
 import React from 'react';
 import { useIntl } from 'react-intl';
 import CurrentBranchLikeMergeInformation from '../../../app/components/nav/component/branch-like/CurrentBranchLikeMergeInformation';
-import DateFromNow from '../../../components/intl/DateFromNow';
 import { getLeakValue } from '../../../components/measure/utils';
 import { findMeasure, formatMeasure } from '../../../helpers/measures';
 import { PullRequest } from '../../../types/branch-like';
 import { MetricKey, MetricType } from '../../../types/metrics';
 import { MeasureEnhanced } from '../../../types/types';
+import LastAnalysisLabel from '../components/LastAnalysisLabel';
 
 interface Props {
   pullRequest: PullRequest;
@@ -54,18 +54,7 @@ export default function PullRequestMetaTopBar({ pullRequest, measures }: Readonl
       {pullRequest.analysisDate && (
         <>
           <SeparatorCircleIcon />
-          {intl.formatMessage(
-            {
-              id: 'overview.last_analysis_x',
-            },
-            {
-              date: (
-                <strong className="sw-body-sm-highlight">
-                  <DateFromNow date={pullRequest.analysisDate} />
-                </strong>
-              ),
-            },
-          )}
+          <LastAnalysisLabel analysisDate={pullRequest.analysisDate} />
         </>
       )}
     </div>
index 2d31837134be8c5d165216560ce50f42cb8d2707..c48bc626eda5ff28b12961073c0a06c8a46d20d8 100644 (file)
@@ -3848,8 +3848,7 @@ system.version_is_availble={version} is available
 # OVERVIEW
 #
 #------------------------------------------------------------------------------
-overview.1_condition_failed=1 failed condition
-overview.X_conditions_failed={0} failed conditions
+overview.X_conditions_failed={conditions} {conditions, plural, one {failed condition} other {failed conditions}}
 overview.failed_condition.x_rating_required={rating} is {value}. Required {threshold}
 overview.failed_condition.x_required={metric}. Required {threshold}
 overview.fix_failed_conditions_with_sonarlint=Fix issues before they fail your Quality Gate with {link} in your IDE. Power up with connected mode!