]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20742 Implement Overview header
authorstanislavh <stanislav.honcharov@sonarsource.com>
Fri, 13 Oct 2023 11:47:03 +0000 (13:47 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 20 Oct 2023 20:02:41 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/overview/components/MetaTopBar.tsx
server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx
server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/PullRequestOverview-it.tsx
server/sonar-web/src/main/js/apps/overview/utils.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 25a1d43e52f06d4d46b7556101bc4f17f040d1fb..a7ea045a6773c30b8b3158725e98d1fc798e5efe 100644 (file)
  * 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';
+import { getLeakValue } from '../../../components/measure/utils';
+import { isPullRequest } from '../../../helpers/branch-like';
+import { findMeasure, formatMeasure } from '../../../helpers/measures';
+import { BranchLike } from '../../../types/branch-like';
+import { MetricKey, MetricType } from '../../../types/metrics';
+import { MeasureEnhanced } from '../../../types/types';
 
-export default function MetaTopBar() {
-  return <div>Meta top bar</div>;
+interface Props {
+  branchLike: BranchLike;
+  measures: MeasureEnhanced[];
+}
+
+export default function MetaTopBar({ branchLike, measures }: Readonly<Props>) {
+  const intl = useIntl();
+  const isPR = isPullRequest(branchLike);
+
+  const leftSection = (
+    <div>
+      {isPR ? (
+        <>
+          <strong className="sw-body-sm-highlight sw-mr-1">
+            {formatMeasure(
+              getLeakValue(findMeasure(measures, MetricKey.new_lines)),
+              MetricType.ShortInteger,
+            ) ?? '0'}
+          </strong>
+          {intl.formatMessage({ id: 'metric.new_lines.name' })}
+        </>
+      ) : null}
+    </div>
+  );
+  const rightSection = (
+    <div>
+      {branchLike.analysisDate
+        ? intl.formatMessage(
+            {
+              id: 'overview.last_analysis_x',
+            },
+            {
+              date: (
+                <strong className="sw-body-sm-highlight">
+                  <DateFromNow date={branchLike.analysisDate} />
+                </strong>
+              ),
+            },
+          )
+        : null}
+    </div>
+  );
+
+  return (
+    <div className="sw-flex sw-justify-between sw-whitespace-nowrap sw-body-sm">
+      {leftSection}
+      {rightSection}
+    </div>
+  );
 }
index c02df843b49bc8c4806feaff3ccff491c40f79dd..ae2114dac0fb5e6bfca68522127cec7affb27434 100644 (file)
@@ -53,7 +53,7 @@ import QualityGateStatusHeader from '../components/QualityGateStatusHeader';
 import QualityGateStatusPassedView from '../components/QualityGateStatusPassedView';
 import SonarLintPromotion from '../components/SonarLintPromotion';
 import '../styles.css';
-import { MeasurementType, PR_METRICS } from '../utils';
+import { MeasurementType, PR_METRICS, Status } from '../utils';
 
 interface Props {
   branchLike: PullRequest;
@@ -74,7 +74,10 @@ export default function PullRequestOverview(props: Props) {
     const metricKeys =
       conditions !== undefined
         ? // Also load metrics that apply to failing QG conditions.
-          uniq([...PR_METRICS, ...conditions.filter((c) => c.level !== 'OK').map((c) => c.metric)])
+          uniq([
+            ...PR_METRICS,
+            ...conditions.filter((c) => c.level !== Status.OK).map((c) => c.metric),
+          ])
         : PR_METRICS;
 
     getMeasuresWithMetrics(component.key, metricKeys, getBranchLikeQuery(branchLike)).then(
@@ -117,9 +120,11 @@ export default function PullRequestOverview(props: Props) {
   return (
     <CenteredLayout>
       <div className="it__pr-overview sw-mt-12">
-        <MetaTopBar />
+        <MetaTopBar branchLike={branchLike} measures={measures} />
         <BasicSeparator className="sw-my-4" />
 
+        {ignoredConditions && <IgnoredConditionWarning />}
+
         <div className="sw-flex sw-flex-col sw-mr-12 width-30">
           <Card>
             {status && (
@@ -147,11 +152,11 @@ export default function PullRequestOverview(props: Props) {
               </HelpTooltip>
             </div>
 
-            {ignoredConditions && <IgnoredConditionWarning />}
-
-            {status === 'OK' && failedConditions.length === 0 && <QualityGateStatusPassedView />}
+            {status === Status.OK && failedConditions.length === 0 && (
+              <QualityGateStatusPassedView />
+            )}
 
-            {status !== 'OK' && <BasicSeparator />}
+            {status !== Status.OK && <BasicSeparator />}
 
             {failedConditions.length > 0 && (
               <div>
index a7260b8320b31ef917374c112d8cc07014b88eb3..26af400499a8af5b4cf13f1db21cb2797f6e1f40 100644 (file)
@@ -23,15 +23,12 @@ import { getQualityGateProjectStatus } from '../../../../api/quality-gates';
 import CurrentUserContextProvider from '../../../../app/components/current-user/CurrentUserContextProvider';
 import { mockPullRequest } from '../../../../helpers/mocks/branch-like';
 import { mockComponent } from '../../../../helpers/mocks/component';
-import {
-  mockQualityGateProjectCondition,
-  mockQualityGateStatusCondition,
-} from '../../../../helpers/mocks/quality-gates';
-import { mockLoggedInUser, mockMetric, mockPeriod } from '../../../../helpers/testMocks';
+import { mockQualityGateProjectCondition } from '../../../../helpers/mocks/quality-gates';
+import { mockLoggedInUser, mockMeasure, mockMetric } from '../../../../helpers/testMocks';
 import { renderComponent } from '../../../../helpers/testReactTestingUtils';
 import { ComponentPropsType } from '../../../../helpers/testUtils';
 import { ComponentQualifier } from '../../../../types/component';
-import { MetricKey } from '../../../../types/metrics';
+import { MetricKey, MetricType } from '../../../../types/metrics';
 import { CaycStatus } from '../../../../types/types';
 import PullRequestOverview from '../PullRequestOverview';
 
@@ -44,20 +41,17 @@ jest.mock('../../../../api/measures', () => {
         name: '',
         qualifier: ComponentQualifier.Project,
         measures: [
-          mockQualityGateStatusCondition({
-            error: '1.0',
+          mockMeasure({
             metric: MetricKey.new_coverage,
-            period: 1,
           }),
-          mockQualityGateStatusCondition({
-            error: '1.0',
+          mockMeasure({
             metric: MetricKey.duplicated_lines,
-            period: 1,
           }),
-          mockQualityGateStatusCondition({
-            error: '3',
+          mockMeasure({
             metric: MetricKey.new_bugs,
-            period: 1,
+          }),
+          mockMeasure({
+            metric: MetricKey.new_lines,
           }),
         ],
       },
@@ -66,12 +60,12 @@ jest.mock('../../../../api/measures', () => {
         mockMetric({
           key: MetricKey.duplicated_lines,
         }),
+        mockMetric({ key: MetricKey.new_lines, type: MetricType.ShortInteger }),
         mockMetric({
           key: MetricKey.new_bugs,
-          type: 'INT',
+          type: MetricType.Integer,
         }),
       ],
-      period: mockPeriod(),
     }),
   };
 });
@@ -127,6 +121,8 @@ it('should render correctly for a passed QG', async () => {
   renderPullRequestOverview();
 
   await waitFor(async () => expect(await screen.findByText('metric.level.OK')).toBeInTheDocument());
+  expect(screen.getByText('metric.new_lines.name')).toBeInTheDocument();
+  expect(screen.getByText(/overview.last_analysis_x/)).toBeInTheDocument();
 });
 
 it('should render correctly if conditions are ignored', async () => {
@@ -172,11 +168,11 @@ it('should render correctly for a failed QG', async () => {
     expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument(),
   );
 
-  expect(await screen.findByText('metric.new_coverage.name')).toBeInTheDocument();
+  expect(await screen.findByText('1.0% metric.new_coverage.name')).toBeInTheDocument();
   expect(await screen.findByText('quality_gates.operator.GT 2.0%')).toBeInTheDocument();
 
   expect(
-    await screen.findByText('metric.duplicated_lines.name quality_gates.conditions.new_code'),
+    await screen.findByText('1.0% metric.duplicated_lines.name quality_gates.conditions.new_code'),
   ).toBeInTheDocument();
   expect(await screen.findByText('quality_gates.operator.GT 1.0%')).toBeInTheDocument();
 
index d67c362653c133b235d01625ebfcbebd03229cf8..abca784fab816f002cb6a984bbf23d966fb5ff92 100644 (file)
@@ -122,6 +122,11 @@ export enum MeasurementType {
   Duplication = 'DUPLICATION',
 }
 
+export enum Status {
+  OK = 'OK',
+  ERROR = 'ERROR',
+}
+
 const MEASUREMENTS_MAP = {
   [MeasurementType.Coverage]: {
     metric: MetricKey.coverage,
index 4751ffc5ec9ae0d84962267c0712a94721e33c44..fc35a833d05eb1b64d827564ff169283b2da03cd 100644 (file)
@@ -3766,6 +3766,7 @@ overview.max_new_code_period_from_x=Max New Code from: {0}
 overview.started_x=Started {0}
 overview.new_code=New Code
 overview.overall_code=Overall Code
+overview.last_analysis_x=Last analysis {date}
 overview.previous_analysis_x=Previous analysis was {0}
 overview.started_on_x=Started on {0}
 overview.previous_analysis_on_x=Previous analysis on {0}