]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19020 Create indicator-type components for the new UI
authorKevin Silva <kevin.silva@sonarsource.com>
Fri, 14 Apr 2023 15:49:21 +0000 (17:49 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 19 Apr 2023 20:02:47 +0000 (20:02 +0000)
20 files changed:
server/sonar-web/design-system/config/jest/SetupReactTestingLibrary.ts
server/sonar-web/design-system/package.json
server/sonar-web/design-system/src/components/CoverageIndicator.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/DonutChart.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/DuplicationsIndicator.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/MetricsRatingBadge.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/SizeIndicator.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/__tests__/CoverageIndicator-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/__tests__/DuplicationsIndicator-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/__tests__/MetricsRatingBadge-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/__tests__/SizeIndicator-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/__tests__/__snapshots__/CoverageIndicator-test.tsx.snap [new file with mode: 0644]
server/sonar-web/design-system/src/components/__tests__/__snapshots__/DuplicationsIndicator-test.tsx.snap [new file with mode: 0644]
server/sonar-web/design-system/src/components/icons/Icon.tsx
server/sonar-web/design-system/src/components/icons/NoDataIcon.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/icons/index.ts
server/sonar-web/design-system/src/components/index.ts
server/sonar-web/design-system/src/theme/light.ts
server/sonar-web/design-system/src/types/measures.ts [new file with mode: 0644]
server/sonar-web/yarn.lock

index afaa0a4fcfb6d0ca85e4e5312fad5d178f78ed56..a8e15b082c5f37290e400edfa3a25c718eacc7f7 100644 (file)
@@ -19,7 +19,9 @@
  */
 import '@testing-library/jest-dom';
 import { configure } from '@testing-library/react';
+import React from 'react';
 
 configure({
   asyncUtilTimeout: 3000,
 });
+global.React = React;
index 610c32ffd8ab78c6c4d0bdf8d4ea224b0b140904..397174a46a8b51ab6c5df24aa7edd39eead3dfcb 100644 (file)
@@ -50,6 +50,7 @@
     "@primer/octicons-react": "18.3.0",
     "classnames": "2.3.2",
     "clipboard": "2.0.11",
+    "d3-shape": "3.2.0",
     "lodash": "4.17.21",
     "react": "17.0.2",
     "react-dom": "17.0.2",
diff --git a/server/sonar-web/design-system/src/components/CoverageIndicator.tsx b/server/sonar-web/design-system/src/components/CoverageIndicator.tsx
new file mode 100644 (file)
index 0000000..83489d2
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * 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 { useTheme } from '@emotion/react';
+import { themeColor } from '../helpers/theme';
+import { DonutChart } from './DonutChart';
+import { NoDataIcon } from './icons';
+
+const SIZE_TO_WIDTH_MAPPING = { xs: 16, sm: 24, md: 36 };
+const SIZE_TO_THICKNESS_MAPPING = { xs: 2, sm: 3, md: 4 };
+const FULL_PERCENT = 100;
+const PAD_ANGLE = 0.1;
+
+export interface CoverageIndicatorProps {
+  size?: 'xs' | 'sm' | 'md';
+  value?: number | string;
+}
+
+export function CoverageIndicator({ size = 'sm', value }: CoverageIndicatorProps) {
+  const theme = useTheme();
+  const width = SIZE_TO_WIDTH_MAPPING[size];
+  const thickness = SIZE_TO_THICKNESS_MAPPING[size];
+
+  if (value === undefined) {
+    return <NoDataIcon height={width} width={width} />;
+  }
+
+  const themeRed = themeColor('coverageRed')({ theme });
+  const themeGreen = themeColor('coverageGreen')({ theme });
+
+  let padAngle = 0;
+  const numberValue = Number(value || 0);
+  const data = [
+    { value: numberValue, fill: themeGreen },
+    {
+      value: FULL_PERCENT - numberValue,
+      fill: themeRed,
+    },
+  ];
+  if (numberValue !== 0 && numberValue < FULL_PERCENT) {
+    padAngle = PAD_ANGLE; // Same for all sizes, because it scales automatically
+  }
+
+  return (
+    <DonutChart
+      data={data}
+      height={width}
+      padAngle={padAngle}
+      thickness={thickness}
+      width={width}
+    />
+  );
+}
diff --git a/server/sonar-web/design-system/src/components/DonutChart.tsx b/server/sonar-web/design-system/src/components/DonutChart.tsx
new file mode 100644 (file)
index 0000000..883b258
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * 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 { arc as d3Arc, pie as d3Pie, PieArcDatum } from 'd3-shape';
+
+interface DataPoint {
+  fill: string;
+  value: number;
+}
+
+export interface DonutChartProps {
+  data: DataPoint[];
+  height: number;
+  padAngle?: number;
+  padding?: [number, number, number, number];
+  thickness: number;
+  width: number;
+}
+
+export function DonutChart(props: DonutChartProps) {
+  const { height, padding = [0, 0, 0, 0], width } = props;
+
+  const availableWidth = width - padding[1] - padding[3];
+  const availableHeight = height - padding[0] - padding[2];
+
+  const size = Math.min(availableWidth, availableHeight);
+  const radius = Math.floor(size / 2);
+
+  const pie = d3Pie<any, DataPoint>()
+    .sort(null)
+    .value((d) => d.value);
+
+  if (props.padAngle !== undefined) {
+    pie.padAngle(props.padAngle);
+  }
+
+  const sectors = pie(props.data).map((d, i) => {
+    return (
+      <Sector
+        data={d}
+        fill={props.data[i].fill}
+        key={i}
+        radius={radius}
+        thickness={props.thickness}
+      />
+    );
+  });
+
+  return (
+    <svg className="donut-chart" height={height} role="img" width={width}>
+      <g transform={`translate(${padding[3]}, ${padding[0]})`}>
+        <g transform={`translate(${radius}, ${radius})`}>{sectors}</g>
+      </g>
+    </svg>
+  );
+}
+
+interface SectorProps {
+  data: PieArcDatum<DataPoint>;
+  fill: string;
+  radius: number;
+  thickness: number;
+}
+
+function Sector(props: SectorProps) {
+  const arc = d3Arc<any, PieArcDatum<DataPoint>>()
+    .outerRadius(props.radius)
+    .innerRadius(props.radius - props.thickness);
+  const d = arc(props.data) as string;
+  return <path d={d} style={{ fill: props.fill }} />;
+}
diff --git a/server/sonar-web/design-system/src/components/DuplicationsIndicator.tsx b/server/sonar-web/design-system/src/components/DuplicationsIndicator.tsx
new file mode 100644 (file)
index 0000000..2286c89
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * 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 { useTheme } from '@emotion/react';
+import { themeColor } from '../helpers/theme';
+import { isDefined } from '../helpers/types';
+import { DuplicationEnum, DuplicationLabel } from '../types/measures';
+import { NoDataIcon } from './icons';
+
+interface Props {
+  rating?: DuplicationLabel;
+  size?: 'xs' | 'sm' | 'md';
+}
+
+const SIZE_TO_PX_MAPPING = { xs: 16, sm: 24, md: 36 };
+
+export function DuplicationsIndicator({ size = 'sm', rating }: Props) {
+  const theme = useTheme();
+  const sizePX = SIZE_TO_PX_MAPPING[size];
+
+  if (rating === undefined) {
+    return <NoDataIcon height={sizePX} width={sizePX} />;
+  }
+
+  const primaryColor = themeColor(`duplicationsIndicator.${rating}`)({ theme });
+  const secondaryColor = themeColor('duplicationsIndicatorSecondary')({ theme });
+
+  return (
+    <RatingSVG
+      primaryColor={primaryColor}
+      rating={rating}
+      secondaryColor={secondaryColor}
+      size={sizePX}
+    />
+  );
+}
+
+interface SVGProps {
+  primaryColor: string;
+  rating: DuplicationLabel;
+  secondaryColor: string;
+  size: number;
+}
+
+function RatingSVG({ primaryColor, rating, secondaryColor, size }: SVGProps) {
+  return (
+    <svg height={size} role="img" viewBox="0 0 16 16" width={size}>
+      <circle cx="8" cy="8" fill={primaryColor} r="2" />
+      {isDefined(rating) &&
+        {
+          [DuplicationEnum.A]: (
+            <>
+              <path
+                clipRule="evenodd"
+                d="M8 14c3.3137 0 6-2.6863 6-6 0-3.31371-2.6863-6-6-6-3.31371 0-6 2.68629-6 6 0 3.3137 2.68629 6 6 6Zm0 2c4.4183 0 8-3.5817 8-8 0-4.41828-3.5817-8-8-8-4.41828 0-8 3.58172-8 8 0 4.4183 3.58172 8 8 8Z"
+                fill={secondaryColor}
+                fillRule="evenodd"
+              />
+              <circle cx="8" cy="8" fill={primaryColor} r="2" />
+            </>
+          ),
+          [DuplicationEnum.B]: (
+            <>
+              <path
+                clipRule="evenodd"
+                d="M8 14c3.3137 0 6-2.6863 6-6 0-3.31371-2.6863-6-6-6-3.31371 0-6 2.68629-6 6 0 3.3137 2.68629 6 6 6Zm0 2c4.4183 0 8-3.5817 8-8 0-4.41828-3.5817-8-8-8-4.41828 0-8 3.58172-8 8 0 4.4183 3.58172 8 8 8Z"
+                fill={secondaryColor}
+                fillRule="evenodd"
+              />
+              <path
+                d="M8 0c.81879 0 1.63272.125698 2.4134.372702L9.81002 2.27953A5.99976 5.99976 0 0 0 8 2V0Z"
+                fill={primaryColor}
+              />
+            </>
+          ),
+          [DuplicationEnum.C]: (
+            <>
+              <path
+                clipRule="evenodd"
+                d="M8 14c3.3137 0 6-2.6863 6-6 0-3.31371-2.6863-6-6-6-3.31371 0-6 2.68629-6 6 0 3.3137 2.68629 6 6 6Zm0 2c4.4183 0 8-3.5817 8-8 0-4.41828-3.5817-8-8-8-4.41828 0-8 3.58172-8 8 0 4.4183 3.58172 8 8 8Z"
+                fill={secondaryColor}
+                fillRule="evenodd"
+              />
+              <path
+                d="M8 0c1.89071 2e-8 3.7203.669649 5.1643 1.89017l-1.2911 1.52746C10.7902 2.50224 9.41803 2 8 2V0Z"
+                fill={primaryColor}
+              />
+            </>
+          ),
+          [DuplicationEnum.D]: (
+            <>
+              <path
+                clipRule="evenodd"
+                d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14ZM8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16Z"
+                fill={secondaryColor}
+                fillRule="evenodd"
+              />
+              <path
+                d="M8 0a7.9999 7.9999 0 0 1 4.5815 1.44181 7.99949 7.99949 0 0 1 2.9301 3.80574l-1.8779.68811A6.00009 6.00009 0 0 0 8 2V0Z"
+                fill={primaryColor}
+              />
+            </>
+          ),
+          [DuplicationEnum.E]: (
+            <>
+              <path
+                clipRule="evenodd"
+                d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14ZM8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16Z"
+                fill={secondaryColor}
+                fillRule="evenodd"
+              />
+              <path
+                d="M8 0a8 8 0 0 1 5.0686 1.81054 8.00033 8.00033 0 0 1 2.7744 4.61211l-1.9608.39434a5.99958 5.99958 0 0 0-2.0808-3.45908A5.99972 5.99972 0 0 0 8 2V0Z"
+                fill={primaryColor}
+              />
+            </>
+          ),
+          [DuplicationEnum.F]: (
+            <>
+              <path
+                clipRule="evenodd"
+                d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14ZM8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16Z"
+                fill={secondaryColor}
+                fillRule="evenodd"
+              />
+              <path
+                d="M8 0a8.0002 8.0002 0 0 1 5.6569 13.6569l-1.4143-1.4143a5.9993 5.9993 0 0 0 1.3007-6.5387A5.9999 5.9999 0 0 0 8 2V0Z"
+                fill={primaryColor}
+              />
+            </>
+          ),
+        }[rating]}
+    </svg>
+  );
+}
diff --git a/server/sonar-web/design-system/src/components/MetricsRatingBadge.tsx b/server/sonar-web/design-system/src/components/MetricsRatingBadge.tsx
new file mode 100644 (file)
index 0000000..f917589
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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 styled from '@emotion/styled';
+import tw from 'twin.macro';
+import { getProp, themeColor, themeContrast } from '../helpers/theme';
+import { MetricsLabel } from '../types/measures';
+
+interface Props extends React.AriaAttributes {
+  className?: string;
+  label: string;
+  rating?: MetricsLabel;
+  size?: 'xs' | 'sm' | 'md';
+}
+
+const SIZE_MAPPING = {
+  xs: '1rem',
+  sm: '1.5rem',
+  md: '2rem',
+};
+
+export function MetricsRatingBadge({ className, size = 'sm', label, rating, ...ariaAttrs }: Props) {
+  if (!rating) {
+    return (
+      <span aria-label={label} className={className} {...ariaAttrs}>
+        â€“
+      </span>
+    );
+  }
+  return (
+    <MetricsRatingBadgeStyled
+      aria-label={label}
+      className={className}
+      rating={rating}
+      size={SIZE_MAPPING[size]}
+      {...ariaAttrs}
+    >
+      {rating}
+    </MetricsRatingBadgeStyled>
+  );
+}
+
+const MetricsRatingBadgeStyled = styled.div<{ rating: MetricsLabel; size: string }>`
+  width: ${getProp('size')};
+  height: ${getProp('size')};
+  color: ${({ rating }) => themeContrast(`rating.${rating}`)};
+  font-size: ${({ size }) => (size === '2rem' ? '0.875rem' : '0.75rem')};
+  background-color: ${({ rating }) => themeColor(`rating.${rating}`)};
+
+  ${tw`sw-inline-flex sw-items-center sw-justify-center`};
+  ${tw`sw-rounded-pill`};
+  ${tw`sw-font-semibold`};
+`;
diff --git a/server/sonar-web/design-system/src/components/SizeIndicator.tsx b/server/sonar-web/design-system/src/components/SizeIndicator.tsx
new file mode 100644 (file)
index 0000000..62cc685
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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 styled from '@emotion/styled';
+import tw from 'twin.macro';
+import { getProp, themeColor, themeContrast } from '../helpers/theme';
+import { SizeLabel } from '../types/measures';
+
+export interface Props {
+  size?: 'xs' | 'sm' | 'md';
+  value: SizeLabel;
+}
+
+const SIZE_MAPPING = {
+  xs: '1rem',
+  sm: '1.5rem',
+  md: '2rem',
+};
+
+export function SizeIndicator({ size = 'sm', value }: Props) {
+  return (
+    <StyledContainer aria-hidden="true" size={SIZE_MAPPING[size]}>
+      {value}
+    </StyledContainer>
+  );
+}
+
+const StyledContainer = styled.div<{ size: string }>`
+  width: ${getProp('size')};
+  height: ${getProp('size')};
+  font-size: ${({ size }) => (size === '2rem' ? '0.875rem' : '0.75rem')};
+  color: ${themeContrast('sizeIndicator')};
+  background-color: ${themeColor('sizeIndicator')};
+
+  ${tw`sw-inline-flex sw-items-center sw-justify-center`};
+  ${tw`sw-leading-4`};
+  ${tw`sw-rounded-pill`};
+  ${tw`sw-font-semibold`};
+`;
diff --git a/server/sonar-web/design-system/src/components/__tests__/CoverageIndicator-test.tsx b/server/sonar-web/design-system/src/components/__tests__/CoverageIndicator-test.tsx
new file mode 100644 (file)
index 0000000..1b14752
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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 { screen } from '@testing-library/react';
+import { render } from '../../helpers/testUtils';
+import { FCProps } from '../../types/misc';
+
+import { CoverageIndicator } from '../CoverageIndicator';
+
+it('should display CoverageIndicator', () => {
+  setupWithProps({ value: 10 });
+  expect(screen.getByRole('img', { hidden: true })).toMatchSnapshot();
+});
+
+it('should display CoverageIndicator without value', () => {
+  setupWithProps();
+  expect(screen.getByRole('img', { hidden: true })).toMatchSnapshot();
+});
+
+function setupWithProps(props: Partial<FCProps<typeof CoverageIndicator>> = {}) {
+  return render(<CoverageIndicator {...props} />);
+}
diff --git a/server/sonar-web/design-system/src/components/__tests__/DuplicationsIndicator-test.tsx b/server/sonar-web/design-system/src/components/__tests__/DuplicationsIndicator-test.tsx
new file mode 100644 (file)
index 0000000..c491f94
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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 { screen } from '@testing-library/react';
+import { render } from '../../helpers/testUtils';
+import { FCProps } from '../../types/misc';
+
+import { DuplicationLabel } from '../../types/measures';
+import { DuplicationsIndicator } from '../DuplicationsIndicator';
+
+it('should display DuplicationsIndicator without rating', () => {
+  setupWithProps();
+  expect(screen.getByRole('img', { hidden: true })).toMatchSnapshot();
+});
+
+it.each(['A', 'B', 'C', 'D', 'E', 'F'])(
+  'should display DuplicationsIndicator with rating',
+  (variant: DuplicationLabel) => {
+    setupWithProps({ rating: variant });
+    expect(screen.getByRole('img', { hidden: true })).toMatchSnapshot();
+  }
+);
+
+function setupWithProps(props: Partial<FCProps<typeof DuplicationsIndicator>> = {}) {
+  return render(<DuplicationsIndicator {...props} />);
+}
diff --git a/server/sonar-web/design-system/src/components/__tests__/MetricsRatingBadge-test.tsx b/server/sonar-web/design-system/src/components/__tests__/MetricsRatingBadge-test.tsx
new file mode 100644 (file)
index 0000000..8055a3b
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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 { screen } from '@testing-library/react';
+import { render } from '../../helpers/testUtils';
+import { FCProps } from '../../types/misc';
+
+import { MetricsRatingBadge } from '../MetricsRatingBadge';
+
+it('should display RatingIndicator', () => {
+  setupWithProps();
+  expect(screen.getByLabelText('New label')).toBeInTheDocument();
+});
+
+it('should display RatingIndicator with value', () => {
+  setupWithProps({ rating: 'A' });
+  expect(screen.getByText('A')).toBeInTheDocument();
+});
+
+function setupWithProps(props: Partial<FCProps<typeof MetricsRatingBadge>> = {}) {
+  return render(<MetricsRatingBadge label="New label" {...props} />);
+}
diff --git a/server/sonar-web/design-system/src/components/__tests__/SizeIndicator-test.tsx b/server/sonar-web/design-system/src/components/__tests__/SizeIndicator-test.tsx
new file mode 100644 (file)
index 0000000..bdd8b8c
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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 { screen } from '@testing-library/react';
+import { render } from '../../helpers/testUtils';
+import { FCProps } from '../../types/misc';
+
+import { SizeLabel } from '../../types/measures';
+import { SizeIndicator } from '../SizeIndicator';
+
+it.each(['XS', 'S', 'M', 'L', 'XL'])(
+  'should display SizeIndicator with size',
+  (value: SizeLabel) => {
+    setupWithProps({ value });
+    expect(screen.getByText(value)).toBeInTheDocument();
+  }
+);
+
+function setupWithProps(props: Partial<FCProps<typeof SizeIndicator>> = {}) {
+  return render(<SizeIndicator value="XS" {...props} />);
+}
diff --git a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CoverageIndicator-test.tsx.snap b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CoverageIndicator-test.tsx.snap
new file mode 100644 (file)
index 0000000..8a49b54
--- /dev/null
@@ -0,0 +1,49 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display CoverageIndicator 1`] = `
+<svg
+  class="donut-chart"
+  height="24"
+  role="img"
+  width="24"
+>
+  <g
+    transform="translate(0, 0)"
+  >
+    <g
+      transform="translate(12, 12)"
+    >
+      <path
+        d="M0.75,-11.977A12,12,0,0,1,7.222,-9.583L5.265,-7.299A9,9,0,0,0,0.75,-8.969Z"
+        style="fill: rgb(18,183,106);"
+      />
+      <path
+        d="M8.361,-8.608A12,12,0,1,1,-0.75,-11.977L-0.75,-8.969A9,9,0,1,0,6.404,-6.324Z"
+        style="fill: rgb(180,35,24);"
+      />
+    </g>
+  </g>
+</svg>
+`;
+
+exports[`should display CoverageIndicator without value 1`] = `
+<svg
+  aria-hidden="true"
+  fill="none"
+  height="24"
+  role="img"
+  style="clip-rule: evenodd; display: inline-block; fill-rule: evenodd; user-select: none; vertical-align: middle; stroke-linejoin: round; stroke-miterlimit: 1.414;"
+  version="1.1"
+  viewBox="0 0 16 16"
+  width="24"
+  xml:space="preserve"
+  xmlns:xlink="http://www.w3.org/1999/xlink"
+>
+  <path
+    clip-rule="evenodd"
+    d="M16 8C16 12.4183 12.4183 16 8 16C5.5106 16 3.28676 14.863 1.81951 13.0799L15.4913 5.1865C15.8201 6.06172 16 7.00986 16 8ZM14.5574 3.41624L0.750565 11.3876C0.269025 10.3589 0 9.21089 0 8C0 3.58172 3.58172 0 8 0C10.7132 0 13.1109 1.35064 14.5574 3.41624Z"
+    fill="#E1E6F3"
+    fill-rule="evenodd"
+  />
+</svg>
+`;
diff --git a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/DuplicationsIndicator-test.tsx.snap b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/DuplicationsIndicator-test.tsx.snap
new file mode 100644 (file)
index 0000000..21ac97b
--- /dev/null
@@ -0,0 +1,181 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display DuplicationsIndicator with rating 1`] = `
+<svg
+  height="24"
+  role="img"
+  viewBox="0 0 16 16"
+  width="24"
+>
+  <circle
+    cx="8"
+    cy="8"
+    fill="rgb(18,183,106)"
+    r="2"
+  />
+  <path
+    clip-rule="evenodd"
+    d="M8 14c3.3137 0 6-2.6863 6-6 0-3.31371-2.6863-6-6-6-3.31371 0-6 2.68629-6 6 0 3.3137 2.68629 6 6 6Zm0 2c4.4183 0 8-3.5817 8-8 0-4.41828-3.5817-8-8-8-4.41828 0-8 3.58172-8 8 0 4.4183 3.58172 8 8 8Z"
+    fill="rgb(239,242,249)"
+    fill-rule="evenodd"
+  />
+  <circle
+    cx="8"
+    cy="8"
+    fill="rgb(18,183,106)"
+    r="2"
+  />
+</svg>
+`;
+
+exports[`should display DuplicationsIndicator with rating 2`] = `
+<svg
+  height="24"
+  role="img"
+  viewBox="0 0 16 16"
+  width="24"
+>
+  <circle
+    cx="8"
+    cy="8"
+    fill="rgb(18,183,106)"
+    r="2"
+  />
+  <path
+    clip-rule="evenodd"
+    d="M8 14c3.3137 0 6-2.6863 6-6 0-3.31371-2.6863-6-6-6-3.31371 0-6 2.68629-6 6 0 3.3137 2.68629 6 6 6Zm0 2c4.4183 0 8-3.5817 8-8 0-4.41828-3.5817-8-8-8-4.41828 0-8 3.58172-8 8 0 4.4183 3.58172 8 8 8Z"
+    fill="rgb(239,242,249)"
+    fill-rule="evenodd"
+  />
+  <path
+    d="M8 0c.81879 0 1.63272.125698 2.4134.372702L9.81002 2.27953A5.99976 5.99976 0 0 0 8 2V0Z"
+    fill="rgb(18,183,106)"
+  />
+</svg>
+`;
+
+exports[`should display DuplicationsIndicator with rating 3`] = `
+<svg
+  height="24"
+  role="img"
+  viewBox="0 0 16 16"
+  width="24"
+>
+  <circle
+    cx="8"
+    cy="8"
+    fill="rgb(110,183,18)"
+    r="2"
+  />
+  <path
+    clip-rule="evenodd"
+    d="M8 14c3.3137 0 6-2.6863 6-6 0-3.31371-2.6863-6-6-6-3.31371 0-6 2.68629-6 6 0 3.3137 2.68629 6 6 6Zm0 2c4.4183 0 8-3.5817 8-8 0-4.41828-3.5817-8-8-8-4.41828 0-8 3.58172-8 8 0 4.4183 3.58172 8 8 8Z"
+    fill="rgb(239,242,249)"
+    fill-rule="evenodd"
+  />
+  <path
+    d="M8 0c1.89071 2e-8 3.7203.669649 5.1643 1.89017l-1.2911 1.52746C10.7902 2.50224 9.41803 2 8 2V0Z"
+    fill="rgb(110,183,18)"
+  />
+</svg>
+`;
+
+exports[`should display DuplicationsIndicator with rating 4`] = `
+<svg
+  height="24"
+  role="img"
+  viewBox="0 0 16 16"
+  width="24"
+>
+  <circle
+    cx="8"
+    cy="8"
+    fill="rgb(245,184,64)"
+    r="2"
+  />
+  <path
+    clip-rule="evenodd"
+    d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14ZM8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16Z"
+    fill="rgb(239,242,249)"
+    fill-rule="evenodd"
+  />
+  <path
+    d="M8 0a7.9999 7.9999 0 0 1 4.5815 1.44181 7.99949 7.99949 0 0 1 2.9301 3.80574l-1.8779.68811A6.00009 6.00009 0 0 0 8 2V0Z"
+    fill="rgb(245,184,64)"
+  />
+</svg>
+`;
+
+exports[`should display DuplicationsIndicator with rating 5`] = `
+<svg
+  height="24"
+  role="img"
+  viewBox="0 0 16 16"
+  width="24"
+>
+  <circle
+    cx="8"
+    cy="8"
+    fill="rgb(247,95,9)"
+    r="2"
+  />
+  <path
+    clip-rule="evenodd"
+    d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14ZM8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16Z"
+    fill="rgb(239,242,249)"
+    fill-rule="evenodd"
+  />
+  <path
+    d="M8 0a8 8 0 0 1 5.0686 1.81054 8.00033 8.00033 0 0 1 2.7744 4.61211l-1.9608.39434a5.99958 5.99958 0 0 0-2.0808-3.45908A5.99972 5.99972 0 0 0 8 2V0Z"
+    fill="rgb(247,95,9)"
+  />
+</svg>
+`;
+
+exports[`should display DuplicationsIndicator with rating 6`] = `
+<svg
+  height="24"
+  role="img"
+  viewBox="0 0 16 16"
+  width="24"
+>
+  <circle
+    cx="8"
+    cy="8"
+    fill="rgb(240,68,56)"
+    r="2"
+  />
+  <path
+    clip-rule="evenodd"
+    d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14ZM8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16Z"
+    fill="rgb(239,242,249)"
+    fill-rule="evenodd"
+  />
+  <path
+    d="M8 0a8.0002 8.0002 0 0 1 5.6569 13.6569l-1.4143-1.4143a5.9993 5.9993 0 0 0 1.3007-6.5387A5.9999 5.9999 0 0 0 8 2V0Z"
+    fill="rgb(240,68,56)"
+  />
+</svg>
+`;
+
+exports[`should display DuplicationsIndicator without rating 1`] = `
+<svg
+  aria-hidden="true"
+  fill="none"
+  height="24"
+  role="img"
+  style="clip-rule: evenodd; display: inline-block; fill-rule: evenodd; user-select: none; vertical-align: middle; stroke-linejoin: round; stroke-miterlimit: 1.414;"
+  version="1.1"
+  viewBox="0 0 16 16"
+  width="24"
+  xml:space="preserve"
+  xmlns:xlink="http://www.w3.org/1999/xlink"
+>
+  <path
+    clip-rule="evenodd"
+    d="M16 8C16 12.4183 12.4183 16 8 16C5.5106 16 3.28676 14.863 1.81951 13.0799L15.4913 5.1865C15.8201 6.06172 16 7.00986 16 8ZM14.5574 3.41624L0.750565 11.3876C0.269025 10.3589 0 9.21089 0 8C0 3.58172 3.58172 0 8 0C10.7132 0 13.1109 1.35064 14.5574 3.41624Z"
+    fill="#E1E6F3"
+    fill-rule="evenodd"
+  />
+</svg>
+`;
index a9a246a0c217945c9a3d4ec2d824ace7a227f40f..3a90211b5257da75e08717f1b90b51971e85a0dd 100644 (file)
@@ -33,6 +33,8 @@ interface Props {
 
 export interface IconProps extends Omit<Props, 'children'> {
   fill?: ThemeColors | CSSColor;
+  height?: number;
+  width?: number;
 }
 
 export function CustomIcon(props: Props) {
diff --git a/server/sonar-web/design-system/src/components/icons/NoDataIcon.tsx b/server/sonar-web/design-system/src/components/icons/NoDataIcon.tsx
new file mode 100644 (file)
index 0000000..7b357c5
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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 { CustomIcon, IconProps } from './Icon';
+
+export function NoDataIcon({ fill = 'currentColor', ...iconProps }: IconProps) {
+  return (
+    <CustomIcon {...iconProps}>
+      <path
+        clipRule="evenodd"
+        d="M16 8C16 12.4183 12.4183 16 8 16C5.5106 16 3.28676 14.863 1.81951 13.0799L15.4913 5.1865C15.8201 6.06172 16 7.00986 16 8ZM14.5574 3.41624L0.750565 11.3876C0.269025 10.3589 0 9.21089 0 8C0 3.58172 3.58172 0 8 0C10.7132 0 13.1109 1.35064 14.5574 3.41624Z"
+        fill="#E1E6F3"
+        fillRule="evenodd"
+      />
+    </CustomIcon>
+  );
+}
index fd76e9c1719d72017adaca6ec05a1462bb946dec..343b06054645ac8151f78e7e84cf7ca9a3cde3e8 100644 (file)
@@ -34,6 +34,7 @@ export { HomeIcon } from './HomeIcon';
 export { MainBranchIcon } from './MainBranchIcon';
 export { MenuHelpIcon } from './MenuHelpIcon';
 export { MenuSearchIcon } from './MenuSearchIcon';
+export { NoDataIcon } from './NoDataIcon';
 export { OpenCloseIndicator } from './OpenCloseIndicator';
 export { OpenNewTabIcon } from './OpenNewTabIcon';
 export { OverviewQGNotComputedIcon } from './OverviewQGNotComputedIcon';
index 31a1c322c40bd488c4eeabf37bd72a377f7e945e..93058100cf37e58680f20485d27c6f3a9c80d4b7 100644 (file)
@@ -22,10 +22,12 @@ export * from './Accordion';
 export * from './Avatar';
 export { Badge } from './Badge';
 export * from './Card';
+export * from './CoverageIndicator';
 export { DeferredSpinner } from './DeferredSpinner';
 export { Dropdown } from './Dropdown';
 export * from './DropdownMenu';
 export { DropdownToggler } from './DropdownToggler';
+export * from './DuplicationsIndicator';
 export { FailedQGConditionLink } from './FailedQGConditionLink';
 export { FlagMessage } from './FlagMessage';
 export * from './GenericAvatar';
@@ -36,8 +38,10 @@ export { StandoutLink as Link } from './Link';
 export * from './MainAppBar';
 export * from './MainMenu';
 export * from './MainMenuItem';
+export * from './MetricsRatingBadge';
 export * from './NavBarTabs';
 export { QualityGateIndicator } from './QualityGateIndicator';
+export * from './SizeIndicator';
 export * from './SonarQubeLogo';
 export * from './Text';
 export { ToggleButton } from './ToggleButton';
index ea031787166ef8e4950cdb0df2a82c3973e1782f..df8bb271834b60dcba3db47ed487592d1cbbda16 100644 (file)
@@ -296,12 +296,13 @@ const lightTheme = {
     coverageRed: danger.dark,
 
     // duplications indicators
-    'duplicationsRating.A': COLORS.green[500],
-    'duplicationsRating.B': COLORS.yellowGreen[500],
-    'duplicationsRating.C': COLORS.yellow[500],
-    'duplicationsRating.D': COLORS.orange[500],
-    'duplicationsRating.E': COLORS.red[500],
-    duplicationsRatingSecondary: secondary.light,
+    'duplicationsIndicator.A': COLORS.green[500],
+    'duplicationsIndicator.B': COLORS.green[500],
+    'duplicationsIndicator.C': COLORS.yellowGreen[500],
+    'duplicationsIndicator.D': COLORS.yellow[500],
+    'duplicationsIndicator.E': COLORS.orange[500],
+    'duplicationsIndicator.F': COLORS.red[500],
+    duplicationsIndicatorSecondary: secondary.light,
 
     // size indicators
     sizeIndicator: COLORS.blue[500],
diff --git a/server/sonar-web/design-system/src/types/measures.ts b/server/sonar-web/design-system/src/types/measures.ts
new file mode 100644 (file)
index 0000000..807118a
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+export enum DuplicationEnum {
+  A = 'A',
+  B = 'B',
+  C = 'C',
+  D = 'D',
+  E = 'E',
+  F = 'F',
+}
+
+export type DuplicationLabel = keyof typeof DuplicationEnum;
+
+export enum MetricsEnum {
+  A = 'A',
+  B = 'B',
+  C = 'C',
+  D = 'D',
+  E = 'E',
+}
+
+export type MetricsLabel = keyof typeof MetricsEnum;
+
+export enum SizeEnum {
+  XS = 'XS',
+  S = 'S',
+  M = 'M',
+  L = 'L',
+  XL = 'XL',
+}
+export type SizeLabel = keyof typeof SizeEnum;
index 2e4be312cf8d651e21af01118c6e12f53eff99fc..921ef8e55b6f53460279a06067caec63f4ae5eed 100644 (file)
@@ -6150,6 +6150,7 @@ __metadata:
     "@primer/octicons-react": 18.3.0
     classnames: 2.3.2
     clipboard: 2.0.11
+    d3-shape: 3.2.0
     lodash: 4.17.21
     react: 17.0.2
     react-dom: 17.0.2