]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22728 Portfolio overview
authorViktor Vorona <viktor.vorona@sonarsource.com>
Fri, 16 Aug 2024 10:16:11 +0000 (12:16 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 26 Aug 2024 20:03:07 +0000 (20:03 +0000)
server/sonar-web/design-system/src/components/DonutChart.tsx
server/sonar-web/design-system/src/theme/light.ts
server/sonar-web/src/main/js/helpers/constants.ts
server/sonar-web/src/main/js/helpers/doc-links.ts
server/sonar-web/src/main/js/queries/measures.ts

index f8443d8d2c36886093a7c60e45695ed78e27b918..14c4ec3782b8bb32536b36b41ab7557f62d39b08 100644 (file)
@@ -25,8 +25,10 @@ export interface DataPoint {
 }
 
 export interface DonutChartProps {
+  cornerRadius?: number;
   data: DataPoint[];
   height: number;
+  minPercent?: number;
   padAngle?: number;
   padding?: [number, number, number, number];
   thickness: number;
@@ -34,7 +36,7 @@ export interface DonutChartProps {
 }
 
 export function DonutChart(props: DonutChartProps) {
-  const { height, padding = [0, 0, 0, 0], width } = props;
+  const { height, cornerRadius, minPercent = 0, padding = [0, 0, 0, 0], width } = props;
 
   const availableWidth = width - padding[1] - padding[3];
   const availableHeight = height - padding[0] - padding[2];
@@ -42,9 +44,11 @@ export function DonutChart(props: DonutChartProps) {
   const size = Math.min(availableWidth, availableHeight);
   const radius = Math.floor(size / 2);
 
+  const total = props.data.reduce((acc, d) => acc + d.value, 0);
+
   const pie = d3Pie<any, DataPoint>()
     .sort(null)
-    .value((d) => d.value);
+    .value((d) => Math.max(d.value, (total / 100) * minPercent));
 
   if (props.padAngle !== undefined) {
     pie.padAngle(props.padAngle);
@@ -53,6 +57,7 @@ export function DonutChart(props: DonutChartProps) {
   const sectors = pie(props.data).map((d, i) => {
     return (
       <Sector
+        cornerRadius={cornerRadius}
         data={d}
         fill={props.data[i].fill}
         key={i}
@@ -72,6 +77,7 @@ export function DonutChart(props: DonutChartProps) {
 }
 
 interface SectorProps {
+  cornerRadius?: number;
   data: PieArcDatum<DataPoint>;
   fill: string;
   radius: number;
@@ -82,6 +88,10 @@ function Sector(props: SectorProps) {
   const arc = d3Arc<any, PieArcDatum<DataPoint>>()
     .outerRadius(props.radius)
     .innerRadius(props.radius - props.thickness);
+
+  if (props.cornerRadius) {
+    arc.cornerRadius(props.cornerRadius);
+  }
   const d = arc(props.data) as string;
   return <path d={d} style={{ fill: props.fill }} />;
 }
index dce350b51b6b1f96edde23f39e0f39e5e11e95a2..8c8304dcc1d4766ca74d72b75e68ca7145a70389 100644 (file)
@@ -478,6 +478,44 @@ export const lightTheme = {
     'rating.D': COLORS.red[200],
     'rating.E': COLORS.red[200],
 
+    'portfolio.rating.A.text': COLORS.green[900],
+    'portfolio.rating.A.background': COLORS.green[100],
+    'portfolio.rating.A.border': COLORS.green[400],
+    'portfolio.rating.B.text': COLORS.yellowGreen[900],
+    'portfolio.rating.B.background': COLORS.yellowGreen[100],
+    'portfolio.rating.B.border': COLORS.yellowGreen[400],
+    'portfolio.rating.C.text': COLORS.yellow[900],
+    'portfolio.rating.C.background': COLORS.yellow[100],
+    'portfolio.rating.C.border': COLORS.yellow[500],
+    'portfolio.rating.D.text': COLORS.red[900],
+    'portfolio.rating.D.background': COLORS.red[100],
+    'portfolio.rating.D.border': COLORS.red[400],
+    'portfolio.rating.E.text': COLORS.red[900],
+    'portfolio.rating.E.background': COLORS.red[100],
+    'portfolio.rating.E.border': COLORS.red[400],
+    'portfolio.rating.NONE.text': COLORS.blueGrey[300],
+    'portfolio.rating.NONE.background': COLORS.blueGrey[50],
+    'portfolio.rating.NONE.border': COLORS.blueGrey[200],
+
+    'portfolio.rating.legacy.A.text': COLORS.green[900],
+    'portfolio.rating.legacy.A.background': COLORS.green[100],
+    'portfolio.rating.legacy.A.border': COLORS.green[400],
+    'portfolio.rating.legacy.B.text': COLORS.yellowGreen[900],
+    'portfolio.rating.legacy.B.background': COLORS.yellowGreen[100],
+    'portfolio.rating.legacy.B.border': COLORS.yellowGreen[400],
+    'portfolio.rating.legacy.C.text': COLORS.yellow[900],
+    'portfolio.rating.legacy.C.background': COLORS.yellow[100],
+    'portfolio.rating.legacy.C.border': COLORS.yellow[500],
+    'portfolio.rating.legacy.D.text': COLORS.orange[900],
+    'portfolio.rating.legacy.D.background': COLORS.orange[100],
+    'portfolio.rating.legacy.D.border': COLORS.orange[300],
+    'portfolio.rating.legacy.E.text': COLORS.red[900],
+    'portfolio.rating.legacy.E.background': COLORS.red[100],
+    'portfolio.rating.legacy.E.border': COLORS.red[400],
+    'portfolio.rating.legacy.NONE.text': COLORS.blueGrey[300],
+    'portfolio.rating.legacy.NONE.background': COLORS.blueGrey[50],
+    'portfolio.rating.legacy.NONE.border': COLORS.blueGrey[200],
+
     // rating donut outside circle indicators
     'ratingDonut.A': COLORS.green[400],
     'ratingDonut.B': COLORS.yellowGreen[400],
index 82c0fd1ca6e2a09eb34518b02421b246800fd2ca..64f92f5c95ab4cb43a84db452ce801e780d5241c 100644 (file)
@@ -192,10 +192,19 @@ export const DEPRECATED_ACTIVITY_METRICS = [
 
 export const SOFTWARE_QUALITY_RATING_METRICS_MAP: Record<string, MetricKey> = {
   [MetricKey.releasability_rating]: MetricKey.software_quality_releasability_rating,
+  [MetricKey.releasability_rating_distribution]:
+    MetricKey.software_quality_releasability_rating_distribution,
   [MetricKey.sqale_rating]: MetricKey.software_quality_maintainability_rating,
+  [MetricKey.maintainability_rating_distribution]:
+    MetricKey.software_quality_maintainability_rating_distribution,
   [MetricKey.security_rating]: MetricKey.software_quality_security_rating,
+  [MetricKey.security_rating_distribution]: MetricKey.software_quality_security_rating_distribution,
   [MetricKey.reliability_rating]: MetricKey.software_quality_reliability_rating,
+  [MetricKey.reliability_rating_distribution]:
+    MetricKey.software_quality_reliability_rating_distribution,
   [MetricKey.security_review_rating]: MetricKey.software_quality_security_review_rating,
+  [MetricKey.security_review_rating_distribution]:
+    MetricKey.software_quality_security_review_rating_distribution,
   [MetricKey.reliability_remediation_effort]:
     MetricKey.software_quality_reliability_remediation_effort,
   [MetricKey.security_remediation_effort]: MetricKey.software_quality_security_remediation_effort,
@@ -204,9 +213,17 @@ export const SOFTWARE_QUALITY_RATING_METRICS_MAP: Record<string, MetricKey> = {
   [MetricKey.effort_to_reach_maintainability_rating_a]:
     MetricKey.effort_to_reach_software_quality_maintainability_rating_a,
   [MetricKey.new_maintainability_rating]: MetricKey.new_software_quality_maintainability_rating,
+  [MetricKey.new_maintainability_rating_distribution]:
+    MetricKey.new_software_quality_maintainability_rating_distribution,
   [MetricKey.new_security_rating]: MetricKey.new_software_quality_security_rating,
+  [MetricKey.new_security_rating_distribution]:
+    MetricKey.new_software_quality_security_rating_distribution,
   [MetricKey.new_reliability_rating]: MetricKey.new_software_quality_reliability_rating,
+  [MetricKey.new_reliability_rating_distribution]:
+    MetricKey.new_software_quality_reliability_rating_distribution,
   [MetricKey.new_security_review_rating]: MetricKey.new_software_quality_security_review_rating,
+  [MetricKey.new_security_review_rating_distribution]:
+    MetricKey.new_software_quality_security_review_rating_distribution,
   [MetricKey.new_technical_debt]: MetricKey.new_software_quality_maintainability_remediation_effort,
   [MetricKey.new_reliability_remediation_effort]:
     MetricKey.new_software_quality_reliability_remediation_effort,
index 0f4c5a2f83b2d90b10a5e84ad6c7b497556b3825..b02acce18ad3bd70d09a218f6e5eef2a0207f9ac 100644 (file)
@@ -63,6 +63,7 @@ export enum DocLink {
   Issues = '/user-guide/issues/introduction/',
   IssueStatuses = '/user-guide/issues/solution-overview/#life-cycle',
   MainBranchAnalysis = '/project-administration/maintaining-the-branches-of-your-project/',
+  ManagingPortfolios = '/project-administration/managing-portfolios/',
   MetricDefinitions = '/user-guide/code-metrics/metrics-definition/',
   Monorepos = '/project-administration/monorepos/',
   NewCodeDefinition = '/project-administration/clean-as-you-code-settings/defining-new-code/',
index 57a5f6fb182bd610b783f264a43b99db360ad8e7..68cc602ea8921281e068b0276c1e40efaa062c53 100644 (file)
@@ -38,7 +38,7 @@ import { getBranchLikeQuery } from '../sonar-aligned/helpers/branch-like';
 import { MetricKey } from '../sonar-aligned/types/metrics';
 import { BranchLike } from '../types/branch-like';
 import { Measure } from '../types/types';
-import { createInfiniteQueryHook, createQueryHook } from './common';
+import { createInfiniteQueryHook, createQueryHook, StaleTime } from './common';
 
 export function useAllMeasuresHistoryQuery(
   component: string | undefined,
@@ -268,3 +268,56 @@ export const useMeasureQuery = createQueryHook(
     });
   },
 );
+
+const PORTFOLIO_OVERVIEW_METRIC_KEYS = [
+  MetricKey.software_quality_releasability_rating,
+  MetricKey.software_quality_releasability_rating_distribution,
+  MetricKey.software_quality_security_rating_distribution,
+  MetricKey.software_quality_security_review_rating_distribution,
+  MetricKey.software_quality_maintainability_rating_distribution,
+  MetricKey.software_quality_reliability_rating_distribution,
+  MetricKey.new_software_quality_security_rating_distribution,
+  MetricKey.new_software_quality_security_review_rating_distribution,
+  MetricKey.new_software_quality_maintainability_rating_distribution,
+  MetricKey.new_software_quality_reliability_rating_distribution,
+];
+
+export const useMeasuresQuery = createQueryHook(
+  ({
+    componentKey,
+    metricKeys,
+    branchLike,
+  }: {
+    branchLike?: BranchLike;
+    componentKey: string;
+    metricKeys: string;
+  }) => {
+    const queryClient = useQueryClient();
+    const branchLikeQuery = getBranchLikeQuery(branchLike);
+
+    return queryOptions({
+      queryKey: ['measures', 'list', componentKey, 'branchLike', branchLikeQuery, metricKeys],
+      queryFn: async () => {
+        const measures = await getMeasures({
+          component: componentKey,
+          // TODO Remove once BE is ready
+          metricKeys: metricKeys
+            .split(',')
+            .filter((key) => !PORTFOLIO_OVERVIEW_METRIC_KEYS.includes(key as MetricKey))
+            .join(),
+        });
+
+        const measuresMapByMetricKey = groupBy(measures, 'metric');
+        metricKeys.split(',').forEach((metricKey) => {
+          const measure = measuresMapByMetricKey[metricKey]?.[0] ?? null;
+          queryClient.setQueryData<Measure>(
+            ['measures', 'details', componentKey, 'branchLike', branchLike, metricKey],
+            measure,
+          );
+        });
+        return measures;
+      },
+      staleTime: StaleTime.LONG,
+    });
+  },
+);