]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20665 Add rich Quality Profile event component to be displayed in project's...
authorAmbroise C <ambroise.christea@sonarsource.com>
Wed, 11 Oct 2023 15:13:02 +0000 (17:13 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 18 Oct 2023 20:03:04 +0000 (20:03 +0000)
server/sonar-web/src/main/js/apps/overview/branches/Event.tsx
server/sonar-web/src/main/js/apps/overview/branches/__tests__/ActivityPanel-it.tsx
server/sonar-web/src/main/js/components/activity-graph/EventInner.tsx
server/sonar-web/src/main/js/components/activity-graph/RichQualityProfileEventInner.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/types/project-activity.ts

index baba62bdd5a09a64b032795e87a275e34bc2e13c..45211c2fe2c28836db6118e6bc47a058a445da88 100644 (file)
@@ -22,6 +22,10 @@ import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { isDefinitionChangeEvent } from '../../../components/activity-graph/DefinitionChangeEventInner';
 import { isRichQualityGateEvent } from '../../../components/activity-graph/RichQualityGateEventInner';
+import {
+  RichQualityProfileEventInner,
+  isRichQualityProfileEvent,
+} from '../../../components/activity-graph/RichQualityProfileEventInner';
 import { SqUpgradeActivityEventMessage } from '../../../components/activity-graph/SqUpgradeActivityEventMessage';
 import { translate } from '../../../helpers/l10n';
 import { AnalysisEvent, ProjectAnalysisEventCategory } from '../../../types/project-activity';
@@ -45,6 +49,10 @@ export function Event({ event }: Props) {
     return <SqUpgradeActivityEventMessage event={event} />;
   }
 
+  if (isRichQualityProfileEvent(event)) {
+    return <RichQualityProfileEventInner event={event} />;
+  }
+
   const eventCategory = translate('event.category', event.category);
   if (isDefinitionChangeEvent(event)) {
     return <div className="sw-mb-1">{eventCategory}</div>;
index 5c83b9abd724bf5e8056b69d0630e782a14bd8f8..1f45cd7d397ef0320833b12df14c4eaf6c0cd73a 100644 (file)
@@ -28,8 +28,10 @@ import {
 } from '../../../../helpers/mocks/project-activity';
 import { mockMetric } from '../../../../helpers/testMocks';
 
+import userEvent from '@testing-library/user-event';
+import { Route, useSearchParams } from 'react-router-dom';
 import { parseDate } from '../../../../helpers/dates';
-import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { renderAppRoutes } from '../../../../helpers/testReactTestingUtils';
 import { MetricKey } from '../../../../types/metrics';
 import {
   ApplicationAnalysisEventCategory,
@@ -39,7 +41,9 @@ import {
 import ActivityPanel, { ActivityPanelProps } from '../ActivityPanel';
 
 it('should render correctly', async () => {
+  const user = userEvent.setup();
   renderActivityPanel();
+
   expect(await screen.findAllByText('metric.level.ERROR')).toHaveLength(2);
   expect(screen.getAllByText('metric.level.OK')).toHaveLength(2);
   expect(screen.getByRole('status', { name: 'v1.0' })).toBeInTheDocument();
@@ -64,9 +68,22 @@ it('should render correctly', async () => {
   expect(screen.getByText(/^502 project_activity\.graphs\.issues$/)).toBeInTheDocument();
   expect(screen.getByText(/^0\.0% project_activity\.graphs\.coverage$/)).toBeInTheDocument();
   expect(screen.getByText(/^10\.0% project_activity\.graphs\.duplications$/)).toBeInTheDocument();
+
+  // Rich Quality Profile event
+  expect(
+    screen.getByLabelText(
+      /Quality profile QP-test has been updated with 1 new rule, 2 modified rules, and 3 removed rules/,
+    ),
+  ).toBeInTheDocument();
+
+  await user.click(
+    screen.getByRole('link', { name: 'quality_profiles.page_title_changelog_x.QP-test' }),
+  );
+
+  expect(await screen.findByText('QP-test java')).toBeInTheDocument();
 });
 
-function renderActivityPanel(props: Partial<ActivityPanelProps> = {}) {
+function renderActivityPanel() {
   const mockedMeasureHistory = [
     mockMeasureHistory({
       metric: MetricKey.code_smells,
@@ -157,6 +174,17 @@ function renderActivityPanel(props: Partial<ActivityPanelProps> = {}) {
           category: ProjectAnalysisEventCategory.SqUpgrade,
           name: '10.2',
         }),
+        mockAnalysisEvent({
+          key: '6',
+          category: ProjectAnalysisEventCategory.QualityProfile,
+          name: 'Quality profile QP-test has been updated with 1 new rule, 2 modified rules, and 3 removed rules',
+          description: '1 new rule, 2 modified rules, and 3 removed rules',
+          qualityProfile: {
+            languageKey: 'java',
+            name: 'QP-test',
+            key: 'testkey',
+          },
+        }),
       ],
     }),
     mockAnalysis({ key: 'bar' }),
@@ -173,5 +201,20 @@ function renderActivityPanel(props: Partial<ActivityPanelProps> = {}) {
     loading: false,
   };
 
-  return renderComponent(<ActivityPanel {...mockedProps} {...props} />);
+  return renderAppRoutes('overview', () => (
+    <>
+      <Route path="/overview" element={<ActivityPanel {...mockedProps} />} />
+      <Route
+        path="/profiles/changelog"
+        Component={() => {
+          const [searchParams] = useSearchParams();
+          return (
+            <div>
+              {searchParams.get('name')} {searchParams.get('language')}
+            </div>
+          );
+        }}
+      />
+    </>
+  ));
 }
index 306ef35873a535d03c428fcc4158faa228483a1e..48114b675527ab1ad7def404e51a9988e25f6e39 100644 (file)
@@ -26,6 +26,10 @@ import { AnalysisEvent, ProjectAnalysisEventCategory } from '../../types/project
 import Tooltip from '../controls/Tooltip';
 import { DefinitionChangeEventInner, isDefinitionChangeEvent } from './DefinitionChangeEventInner';
 import { RichQualityGateEventInner, isRichQualityGateEvent } from './RichQualityGateEventInner';
+import {
+  RichQualityProfileEventInner,
+  isRichQualityProfileEvent,
+} from './RichQualityProfileEventInner';
 import { SqUpgradeActivityEventMessage } from './SqUpgradeActivityEventMessage';
 
 export interface EventInnerProps {
@@ -40,6 +44,8 @@ export default function EventInner({ event, readonly }: EventInnerProps) {
     return <RichQualityGateEventInner event={event} readonly={readonly} />;
   } else if (isDefinitionChangeEvent(event)) {
     return <DefinitionChangeEventInner branchLike={branchLike} event={event} readonly={readonly} />;
+  } else if (isRichQualityProfileEvent(event)) {
+    return <RichQualityProfileEventInner event={event} />;
   } else if (event.category === ProjectAnalysisEventCategory.SqUpgrade) {
     return <SqUpgradeActivityEventMessage event={event} />;
   }
diff --git a/server/sonar-web/src/main/js/components/activity-graph/RichQualityProfileEventInner.tsx b/server/sonar-web/src/main/js/components/activity-graph/RichQualityProfileEventInner.tsx
new file mode 100644 (file)
index 0000000..657cd5e
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { Link } from 'design-system';
+import React from 'react';
+import { useIntl } from 'react-intl';
+import { getProfileChangelogPath } from '../../apps/quality-profiles/utils';
+import { AnalysisEvent, ProjectAnalysisEventCategory } from '../../types/project-activity';
+
+type RichQualityGateEvent = AnalysisEvent &
+  Required<Pick<AnalysisEvent, 'description' | 'qualityProfile'>>;
+
+interface RichQualityProfileEventInnerProps {
+  event: RichQualityGateEvent;
+}
+
+export function isRichQualityProfileEvent(event: AnalysisEvent): event is RichQualityGateEvent {
+  return (
+    event.category === ProjectAnalysisEventCategory.QualityProfile &&
+    event.description !== undefined &&
+    event.qualityProfile !== undefined
+  );
+}
+
+export function RichQualityProfileEventInner({
+  event,
+}: Readonly<RichQualityProfileEventInnerProps>) {
+  const {
+    description,
+    name,
+    qualityProfile: { languageKey, name: qualityProfileName },
+  } = event;
+  const intl = useIntl();
+
+  const truncatedName = name.split(description)[0];
+
+  return (
+    <span aria-label={name}>
+      {truncatedName}
+      <Link
+        aria-label={intl.formatMessage(
+          { id: 'quality_profiles.page_title_changelog_x' },
+          { 0: qualityProfileName },
+        )}
+        to={getProfileChangelogPath(qualityProfileName, languageKey)}
+      >
+        {description}
+      </Link>
+    </span>
+  );
+}
index 1a82a555ea097353928b466d5b0bc1528b4f39ec..b657c831c5ac71a8a568e8d2065c821f43cded15 100644 (file)
@@ -57,6 +57,11 @@ export interface AnalysisEvent {
       oldBranch?: string;
     }>;
   };
+  qualityProfile?: {
+    key: string;
+    languageKey: string;
+    name: string;
+  };
 }
 
 export enum GraphType {