diff options
author | Kevin Silva <kevin.silva@sonarsource.com> | 2023-04-14 17:49:21 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-04-19 20:02:47 +0000 |
commit | 4e767e0f8f19a19a20a4f9faa8190201c8ea3eae (patch) | |
tree | 8150308d9cdb082e17af15070cd23dd5513b66af /server | |
parent | b0fa5854cdf8d593a307d08891c454b8014e3041 (diff) | |
download | sonarqube-4e767e0f8f19a19a20a4f9faa8190201c8ea3eae.tar.gz sonarqube-4e767e0f8f19a19a20a4f9faa8190201c8ea3eae.zip |
SONAR-19020 Create indicator-type components for the new UI
Diffstat (limited to 'server')
20 files changed, 923 insertions, 6 deletions
diff --git a/server/sonar-web/design-system/config/jest/SetupReactTestingLibrary.ts b/server/sonar-web/design-system/config/jest/SetupReactTestingLibrary.ts index afaa0a4fcfb..a8e15b082c5 100644 --- a/server/sonar-web/design-system/config/jest/SetupReactTestingLibrary.ts +++ b/server/sonar-web/design-system/config/jest/SetupReactTestingLibrary.ts @@ -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; diff --git a/server/sonar-web/design-system/package.json b/server/sonar-web/design-system/package.json index 610c32ffd8a..397174a46a8 100644 --- a/server/sonar-web/design-system/package.json +++ b/server/sonar-web/design-system/package.json @@ -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 index 00000000000..83489d29d1b --- /dev/null +++ b/server/sonar-web/design-system/src/components/CoverageIndicator.tsx @@ -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 index 00000000000..883b258c714 --- /dev/null +++ b/server/sonar-web/design-system/src/components/DonutChart.tsx @@ -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 index 00000000000..2286c89b211 --- /dev/null +++ b/server/sonar-web/design-system/src/components/DuplicationsIndicator.tsx @@ -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 index 00000000000..f91758932d7 --- /dev/null +++ b/server/sonar-web/design-system/src/components/MetricsRatingBadge.tsx @@ -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 index 00000000000..62cc685c2e7 --- /dev/null +++ b/server/sonar-web/design-system/src/components/SizeIndicator.tsx @@ -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 index 00000000000..1b14752eac6 --- /dev/null +++ b/server/sonar-web/design-system/src/components/__tests__/CoverageIndicator-test.tsx @@ -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 index 00000000000..c491f942829 --- /dev/null +++ b/server/sonar-web/design-system/src/components/__tests__/DuplicationsIndicator-test.tsx @@ -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 index 00000000000..8055a3b7ef3 --- /dev/null +++ b/server/sonar-web/design-system/src/components/__tests__/MetricsRatingBadge-test.tsx @@ -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 index 00000000000..bdd8b8c6d85 --- /dev/null +++ b/server/sonar-web/design-system/src/components/__tests__/SizeIndicator-test.tsx @@ -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 index 00000000000..8a49b54ab50 --- /dev/null +++ b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CoverageIndicator-test.tsx.snap @@ -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 index 00000000000..21ac97b1eea --- /dev/null +++ b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/DuplicationsIndicator-test.tsx.snap @@ -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> +`; diff --git a/server/sonar-web/design-system/src/components/icons/Icon.tsx b/server/sonar-web/design-system/src/components/icons/Icon.tsx index a9a246a0c21..3a90211b525 100644 --- a/server/sonar-web/design-system/src/components/icons/Icon.tsx +++ b/server/sonar-web/design-system/src/components/icons/Icon.tsx @@ -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 index 00000000000..7b357c56449 --- /dev/null +++ b/server/sonar-web/design-system/src/components/icons/NoDataIcon.tsx @@ -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> + ); +} diff --git a/server/sonar-web/design-system/src/components/icons/index.ts b/server/sonar-web/design-system/src/components/icons/index.ts index fd76e9c1719..343b0605464 100644 --- a/server/sonar-web/design-system/src/components/icons/index.ts +++ b/server/sonar-web/design-system/src/components/icons/index.ts @@ -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'; diff --git a/server/sonar-web/design-system/src/components/index.ts b/server/sonar-web/design-system/src/components/index.ts index 31a1c322c40..93058100cf3 100644 --- a/server/sonar-web/design-system/src/components/index.ts +++ b/server/sonar-web/design-system/src/components/index.ts @@ -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'; diff --git a/server/sonar-web/design-system/src/theme/light.ts b/server/sonar-web/design-system/src/theme/light.ts index ea031787166..df8bb271834 100644 --- a/server/sonar-web/design-system/src/theme/light.ts +++ b/server/sonar-web/design-system/src/theme/light.ts @@ -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 index 00000000000..807118abe94 --- /dev/null +++ b/server/sonar-web/design-system/src/types/measures.ts @@ -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; diff --git a/server/sonar-web/yarn.lock b/server/sonar-web/yarn.lock index 2e4be312cf8..921ef8e55b6 100644 --- a/server/sonar-web/yarn.lock +++ b/server/sonar-web/yarn.lock @@ -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 |