]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20023 Modify Issues List to reflect CCT
author7PH <benjamin.raymond@sonarsource.com>
Mon, 31 Jul 2023 09:29:13 +0000 (11:29 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 18 Aug 2023 20:02:47 +0000 (20:02 +0000)
28 files changed:
server/sonar-web/design-system/src/components/Breadcrumbs.tsx
server/sonar-web/design-system/src/components/BubbleChart.tsx
server/sonar-web/design-system/src/components/ColorsLegend.tsx
server/sonar-web/design-system/src/components/DropdownMenu.tsx
server/sonar-web/design-system/src/components/FacetBox.tsx
server/sonar-web/design-system/src/components/Histogram.tsx
server/sonar-web/design-system/src/components/NavBarTabs.tsx
server/sonar-web/design-system/src/components/Pill.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/Tags.tsx
server/sonar-web/design-system/src/components/Text.tsx
server/sonar-web/design-system/src/components/Tooltip.tsx
server/sonar-web/design-system/src/components/TreeMapRect.tsx
server/sonar-web/design-system/src/components/__tests__/DropdownMenu-test.tsx
server/sonar-web/design-system/src/components/__tests__/Pill-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/__tests__/Tooltip-test.tsx
server/sonar-web/design-system/src/components/clipboard.tsx
server/sonar-web/design-system/src/components/code-line/LineCoverage.tsx
server/sonar-web/design-system/src/components/index.ts
server/sonar-web/design-system/src/theme/light.ts
server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/issues/utils.ts
server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx
server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx
server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx
server/sonar-web/src/main/js/components/issue/components/IssueView.tsx
server/sonar-web/src/main/js/components/shared/CleanCodeAttributePill.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/shared/SoftwareImpactPill.tsx [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index c4a4c77693ae05865d155a81d41cc0e69a1b6939..2bc0a92d81d43096962be03c1c21c18fbc38e5b9 100644 (file)
@@ -32,7 +32,7 @@ import {
 import { useResizeObserver } from '../hooks/useResizeObserver';
 import { Dropdown } from './Dropdown';
 import { InteractiveIcon } from './InteractiveIcon';
-import Tooltip from './Tooltip';
+import { Tooltip } from './Tooltip';
 import { ChevronDownIcon, ChevronRightIcon } from './icons';
 
 const WIDTH_OF_BREADCRUMB_DROPDOWN = 32;
index 7a04942488f38defa61174bf42a34d26519927c6..e1bd883e04d4378489d41afba4a0a1b0761f6118 100644 (file)
@@ -31,7 +31,7 @@ import tw from 'twin.macro';
 import { themeColor, themeContrast } from '../helpers';
 import { BubbleColorVal } from '../types/charts';
 import { Note } from './Text';
-import Tooltip from './Tooltip';
+import { Tooltip } from './Tooltip';
 import { ButtonSecondary } from './buttons';
 
 const TICKS_COUNT = 5;
index 5b5ed6d8555ec71b120faf84744bf032ac976a4f..5fefef0ab4f8e0a4daf08331ac5e4f4674da54e1 100644 (file)
@@ -22,7 +22,7 @@ import styled from '@emotion/styled';
 import tw from 'twin.macro';
 import { themeBorder, themeColor, themeContrast } from '../helpers';
 import { BubbleColorVal } from '../types/charts';
-import Tooltip from './Tooltip';
+import { Tooltip } from './Tooltip';
 import { Checkbox } from './input/Checkbox';
 
 export interface ColorFilterOption {
index d6cd692bac0385dfa93085ecd96c05b50c463bc6..e87d873e219e5183e207a22cf0ddefd1122f2148 100644 (file)
@@ -27,7 +27,7 @@ import { themeBorder, themeColor, themeContrast } from '../helpers/theme';
 import { InputSizeKeys, ThemedProps } from '../types/theme';
 import { BaseLink, LinkProps } from './Link';
 import NavLink from './NavLink';
-import Tooltip from './Tooltip';
+import { Tooltip } from './Tooltip';
 import { ClipboardBase } from './clipboard';
 import { Checkbox } from './input/Checkbox';
 import { RadioButton } from './input/RadioButton';
index 845ec380ecb8fdcedfe05c44e4a8b2c6a018b6b7..ae0cd7412d51849cd73938c5a7fe6891fe4d40d9 100644 (file)
@@ -27,7 +27,7 @@ import { themeColor } from '../helpers';
 import { Badge } from './Badge';
 import { DestructiveIcon } from './InteractiveIcon';
 import { Spinner } from './Spinner';
-import Tooltip from './Tooltip';
+import { Tooltip } from './Tooltip';
 import { BareButton } from './buttons';
 import { OpenCloseIndicator } from './icons';
 import { CloseIcon } from './icons/CloseIcon';
index 7298c690c87a6c62915218f44c47d78798e79b98..f8a30c097dda12288d62a1907e94077405240650 100644 (file)
@@ -24,7 +24,7 @@ import { scaleBand, ScaleBand, scaleLinear, ScaleLinear } from 'd3-scale';
 import React from 'react';
 import tw from 'twin.macro';
 import { themeColor, themeContrast } from '../helpers';
-import Tooltip, { TooltipWrapper } from './Tooltip';
+import { Tooltip, TooltipWrapper } from './Tooltip';
 
 interface Props {
   bars: number[];
index 3de2b0b64f0a4c5e4ff85a40692f7da61ee7e549..8865016006dc2f6893b1531551dfe83ffaac56f6 100644 (file)
@@ -24,9 +24,9 @@ import React from 'react';
 import tw, { theme } from 'twin.macro';
 import { themeBorder, themeColor, themeContrast } from '../helpers/theme';
 import { isDefined } from '../helpers/types';
-import { ChevronDownIcon } from './icons/ChevronDownIcon';
 import NavLink, { NavLinkProps } from './NavLink';
-import Tooltip from './Tooltip';
+import { Tooltip } from './Tooltip';
+import { ChevronDownIcon } from './icons/ChevronDownIcon';
 
 interface Props extends React.HTMLAttributes<HTMLUListElement> {
   children?: React.ReactNode;
diff --git a/server/sonar-web/design-system/src/components/Pill.tsx b/server/sonar-web/design-system/src/components/Pill.tsx
new file mode 100644 (file)
index 0000000..c6b5cb0
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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 { ReactNode } from 'react';
+import tw from 'twin.macro';
+import { themeColor, themeContrast } from '../helpers/theme';
+import { ThemeColors } from '../types/theme';
+
+type PillVariant = 'danger' | 'warning' | 'info' | 'neutral';
+
+const variantThemeColors: Record<PillVariant, ThemeColors> = {
+  danger: 'pillDanger',
+  warning: 'pillWarning',
+  info: 'pillInfo',
+  neutral: 'pillNeutral',
+};
+
+interface PillProps {
+  children: ReactNode;
+  className?: string;
+  title?: string;
+  variant: PillVariant;
+}
+
+export function Pill({ className, children, title, variant }: PillProps) {
+  const commonProps = {
+    'aria-label': title ?? children?.toString(),
+    className,
+    role: 'status',
+    title,
+  };
+  return (
+    <StyledPill color={variantThemeColors[variant]} {...commonProps}>
+      {children}
+    </StyledPill>
+  );
+}
+
+const StyledPill = styled.span<{
+  color: ThemeColors;
+}>`
+  ${tw`sw-cursor-pointer`};
+  ${tw`sw-w-fit`};
+  ${tw`sw-inline-block`};
+  ${tw`sw-whitespace-nowrap`};
+  ${tw`sw-px-[8px] sw-py-[2px]`};
+  ${tw`sw-rounded-pill`};
+
+  color: ${({ color }) => themeContrast(color)};
+  background-color: ${({ color }) => themeColor(color)};
+
+  &:empty {
+    ${tw`sw-hidden`}
+  }
+`;
index a20757ea8c54df050ba19bd419dd95cacb4992fb..88c613bc9a93eb8e73842662094584844fe3698a 100644 (file)
@@ -95,7 +95,7 @@ export function Tags({
         >
           {({ a11yAttrs, onToggleClick, open }) => (
             <WrapperButton
-              className="sw-flex sw-items-center sw-gap-1 sw-p-1 sw-h-auto sw-rounded-0"
+              className="sw-flex sw-items-center sw-gap-1 sw-p-0 sw-h-auto sw-rounded-0"
               onClick={onToggleClick}
               {...a11yAttrs}
             >
index e17950ab7cdf7db07b411ca9cd2a248b20256357..153fb3488c14a43cb2825fcc407866156d255ef9 100644 (file)
@@ -104,6 +104,11 @@ const StyledTextError = styled(StyledText)`
   color: ${themeColor('danger')};
 `;
 
+export const DisabledText = styled.span`
+  ${tw`sw-font-regular`};
+  color: ${themeColor('pageContentLight')};
+`;
+
 export const LightLabel = styled.span`
   color: ${themeColor('pageContentLight')};
 `;
index 2a5d3925957f06db9099ccfc8670b90c3ec6952b..209a8fdddd2346683627670eac76ae2cab5f993b 100644 (file)
@@ -67,7 +67,7 @@ function isMeasured(state: State): state is OwnState & Measurements {
   return state.height !== undefined;
 }
 
-export default function Tooltip(props: TooltipProps) {
+export function Tooltip(props: TooltipProps) {
   // overlay is a ReactNode, so it can be a boolean, `undefined` or `null`
   // this allows to easily render a tooltip conditionally
   // more generaly we avoid rendering empty tooltips
index 8fb0d3dcc5c48b00b48bf6e64817cf3fa9557783..f18f62816bf4fd2f8b5613b24f0e172854262b99 100644 (file)
@@ -23,7 +23,7 @@ import React from 'react';
 import tw from 'twin.macro';
 import { themeColor } from '../helpers';
 import { BasePlacement, PopupPlacement } from '../helpers/positioning';
-import Tooltip from './Tooltip';
+import { Tooltip } from './Tooltip';
 
 const SIZE_SCALE = scaleLinear().domain([3, 15]).range([11, 18]).clamp(true);
 const TEXT_VISIBLE_AT_WIDTH = 40;
index 7ae2d0d1728caf4a40d4a3a6034c42e362605d9b..e7e9776cf55e5523732056bb01a0e13f2f16cd4a 100644 (file)
@@ -32,7 +32,7 @@ import {
   ItemNavLink,
   ItemRadioButton,
 } from '../DropdownMenu';
-import Tooltip from '../Tooltip';
+import { Tooltip } from '../Tooltip';
 import { MenuIcon } from '../icons/MenuIcon';
 
 beforeEach(() => {
diff --git a/server/sonar-web/design-system/src/components/__tests__/Pill-test.tsx b/server/sonar-web/design-system/src/components/__tests__/Pill-test.tsx
new file mode 100644 (file)
index 0000000..e76446c
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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 { Pill } from '../Pill';
+
+it('should render correctly', () => {
+  render(<Pill variant="neutral">23</Pill>);
+  expect(screen.getByRole('status')).toBeInTheDocument();
+  expect(screen.getByRole('status')).toHaveAttribute('aria-label', '23');
+});
+
+it('should accept overriding label', () => {
+  render(
+    <Pill title="23 foo in bucket" variant="danger">
+      23
+    </Pill>
+  );
+  expect(screen.getByRole('status')).toHaveAttribute('aria-label', '23 foo in bucket');
+});
index 76385492db0311e1ac710ac681fe1b6e642f2856..72b493f6cc648ea538e80aa801b71587c3c8bf0f 100644 (file)
@@ -20,7 +20,7 @@
 import { screen } from '@testing-library/react';
 import { render } from '../../helpers/testUtils';
 import { FCProps } from '../../types/misc';
-import Tooltip, { TooltipInner } from '../Tooltip';
+import { Tooltip, TooltipInner } from '../Tooltip';
 
 jest.mock('react-dom', () => {
   const reactDom = jest.requireActual('react-dom') as object;
index 47135a16bf2740463ebc36bb46e485c7076d6e5f..460a8bf803e01005802293ec8770f5ee671a7351 100644 (file)
@@ -23,7 +23,7 @@ import Clipboard from 'clipboard';
 import React from 'react';
 import { INTERACTIVE_TOOLTIP_DELAY } from '../helpers/constants';
 import { DiscreetInteractiveIcon, InteractiveIcon, InteractiveIconSize } from './InteractiveIcon';
-import Tooltip from './Tooltip';
+import { Tooltip } from './Tooltip';
 import { ButtonSecondary } from './buttons';
 import { CopyIcon } from './icons/CopyIcon';
 import { IconProps } from './icons/Icon';
index 726b3543a49cd8e71eb0566a690c89473dda89b7..448d1fb4d0e933c88ab2754b76a2472c081ec531 100644 (file)
@@ -23,7 +23,7 @@ import React, { memo } from 'react';
 import tw from 'twin.macro';
 import { PopupPlacement } from '../../helpers/positioning';
 import { themeColor } from '../../helpers/theme';
-import Tooltip from '../Tooltip';
+import { Tooltip } from '../Tooltip';
 import { LineMeta } from './LineStyles';
 
 interface Props {
index bc774d6885a6cc71c784249df1e7a865bd040c3b..92aa59aca202fa13f993f72ee932f64e6b507d1c 100644 (file)
@@ -61,6 +61,7 @@ export * from './MetricsRatingBadge';
 export * from './NavBarTabs';
 export * from './NewCodeLegend';
 export * from './OutsideClickHandler';
+export { Pill } from './Pill';
 export { QualityGateIndicator } from './QualityGateIndicator';
 export * from './SearchHighlighter';
 export * from './SelectionCard';
@@ -75,6 +76,7 @@ export * from './TagsSelector';
 export * from './Text';
 export * from './Title';
 export { ToggleButton } from './ToggleButton';
+export { Tooltip } from './Tooltip';
 export { TopBar } from './TopBar';
 export * from './TreeMap';
 export * from './TreeMapRect';
index 05e74288b537841de661d6e0d6e520da62f7b774..8be0dc341c6b0731f09e35da728de4cad1c7ccf6 100644 (file)
@@ -276,6 +276,12 @@ export const lightTheme = {
     badgeDeleted: COLORS.red[100],
     badgeCounter: COLORS.blueGrey[100],
 
+    // pills
+    pillDanger: COLORS.red[100],
+    pillWarning: COLORS.yellowGreen[500],
+    pillInfo: COLORS.indigo[100],
+    pillNeutral: COLORS.blueGrey[50],
+
     // input select
     selectOptionSelected: secondary.light,
 
@@ -323,6 +329,8 @@ export const lightTheme = {
     iconSeverityMajor: danger.light,
     iconSeverityMinor: COLORS.yellowGreen[400],
     iconSeverityInfo: COLORS.blue[400],
+    iconSeverityDisabled: COLORS.blueGrey[300],
+    iconTypeDisabled: COLORS.blueGrey[300],
     iconDirectory: COLORS.orange[300],
     iconFile: COLORS.blueGrey[300],
     iconProject: COLORS.blueGrey[300],
@@ -637,6 +645,12 @@ export const lightTheme = {
     badgeDeleted: COLORS.red[900],
     badgeCounter: secondary.darker,
 
+    // pills
+    pillDanger: COLORS.red[800],
+    pillWarning: COLORS.yellowGreen[900],
+    pillInfo: COLORS.indigo[900],
+    pillNeutral: COLORS.blueGrey[500],
+
     // breadcrumbs
     breadcrumb: secondary.dark,
 
@@ -732,6 +746,8 @@ export const lightTheme = {
 
     // issue box
     issueTypeIcon: COLORS.red[900],
+    iconSeverityDisabled: COLORS.white,
+    iconTypeDisabled: COLORS.white,
 
     // selection card
     selectionCardDisabled: secondary.dark,
index 6985751a43c11e86e689ffdb2d2fc43d7e0afbc6..6546cbc0b20b5bf44a95803fdb90c492d2509fa3 100644 (file)
@@ -24,6 +24,7 @@ import {
 } from '../../../types/issues';
 import { SecurityStandard } from '../../../types/security';
 import {
+  parseQuery,
   serializeQuery,
   shouldOpenSonarSourceSecurityFacet,
   shouldOpenStandardsChildFacet,
@@ -100,7 +101,6 @@ describe('serialize/deserialize', () => {
       rules: 'a,b',
       s: 'rules',
       scopes: 'a,b',
-      severities: 'a,b',
       inNewCodePeriod: 'true',
       sonarsourceSecurity: 'a,b',
       statuses: 'a,b',
@@ -108,6 +108,59 @@ describe('serialize/deserialize', () => {
       types: 'a,b',
     });
   });
+
+  it('should deserialize correctly', () => {
+    expect(
+      parseQuery({
+        assigned: 'true',
+        assignees: 'first,second',
+        author: ['author'],
+        cleanCodeAttributeCategory: 'CONSISTENT',
+        impactSeverity: 'LOW',
+        severities: 'CRITICAL,MAJOR',
+        impactSoftwareQuality: 'MAINTAINABILITY',
+      })
+    ).toStrictEqual({
+      assigned: true,
+      assignees: ['first', 'second'],
+      author: ['author'],
+      cleanCodeAttributeCategory: [CleanCodeAttributeCategory.Consistent],
+      codeVariants: [],
+      createdAfter: undefined,
+      createdAt: '',
+      createdBefore: undefined,
+      createdInLast: '',
+      cwe: [],
+      directories: [],
+      files: [],
+      impactSeverity: [
+        SoftwareImpactSeverity.Low,
+        SoftwareImpactSeverity.High,
+        SoftwareImpactSeverity.Medium,
+      ],
+      impactSoftwareQuality: [SoftwareQuality.Maintainability],
+      inNewCodePeriod: false,
+      issues: [],
+      languages: [],
+      'owaspAsvs-4.0': [],
+      owaspAsvsLevel: '',
+      owaspTop10: [],
+      'owaspTop10-2021': [],
+      'pciDss-3.2': [],
+      'pciDss-4.0': [],
+      projects: [],
+      resolutions: [],
+      resolved: true,
+      rules: [],
+      scopes: [],
+      severities: [],
+      sonarsourceSecurity: [],
+      sort: '',
+      statuses: [],
+      tags: [],
+      types: [],
+    });
+  });
 });
 
 describe('shouldOpenStandardsFacet', () => {
index 72dbf2fa062abe19b8a17d38897df4735b9ebbdf..e2344d58013fb82a321ab687d2913effc5b1241f 100644 (file)
@@ -17,7 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { isArray } from 'lodash';
+import { compact, isArray, uniq } from 'lodash';
 import { getUsers } from '../../api/users';
 import { formatMeasure } from '../../helpers/measures';
 import {
@@ -106,7 +106,7 @@ export function parseQuery(query: RawQuery): Query {
     cwe: parseAsArray(query.cwe, parseAsString),
     directories: parseAsArray(query.directories, parseAsString),
     files: parseAsArray(query.files, parseAsString),
-    impactSeverity: parseAsArray<SoftwareImpactSeverity>(query.impactSeverity, parseAsString),
+    impactSeverity: parseImpactSeverityQuery(query.impactSeverity, query.severities),
     impactSoftwareQuality: parseAsArray<SoftwareQuality>(
       query.impactSoftwareQuality,
       parseAsString
@@ -125,7 +125,7 @@ export function parseQuery(query: RawQuery): Query {
     resolved: parseAsBoolean(query.resolved),
     rules: parseAsArray(query.rules, parseAsString),
     scopes: parseAsArray(query.scopes, parseAsString),
-    severities: parseAsArray(query.severities, parseAsString),
+    severities: [],
     sonarsourceSecurity: parseAsArray(query.sonarsourceSecurity, parseAsString),
     sort: parseAsSort(query.s),
     statuses: parseAsArray(query.statuses, parseAsString),
@@ -135,6 +135,29 @@ export function parseQuery(query: RawQuery): Query {
   };
 }
 
+function parseImpactSeverityQuery(
+  newSeverities: string,
+  oldSeverities?: string
+): SoftwareImpactSeverity[] {
+  const OLD_TO_NEW_MAPPER = {
+    BLOCKER: SoftwareImpactSeverity.High,
+    CRITICAL: SoftwareImpactSeverity.High,
+    MAJOR: SoftwareImpactSeverity.Medium,
+    MINOR: SoftwareImpactSeverity.Low,
+    INFO: SoftwareImpactSeverity.Low,
+  };
+
+  // Merging new and old severities includes mapping for old to new
+  return compact(
+    uniq([
+      ...parseAsArray<SoftwareImpactSeverity>(newSeverities, parseAsString),
+      ...parseAsArray(oldSeverities, parseAsString).map(
+        (oldSeverity: string) => OLD_TO_NEW_MAPPER[oldSeverity as keyof typeof OLD_TO_NEW_MAPPER]
+      ),
+    ])
+  );
+}
+
 export function getOpen(query: RawQuery): string | undefined {
   return query.open;
 }
@@ -173,7 +196,7 @@ export function serializeQuery(query: Query): RawQuery {
     rules: serializeStringArray(query.rules),
     s: serializeString(query.sort),
     scopes: serializeStringArray(query.scopes),
-    severities: serializeStringArray(query.severities),
+    severities: undefined,
     impactSeverity: serializeStringArray(query.impactSeverity),
     impactSoftwareQuality: serializeStringArray(query.impactSoftwareQuality),
     inNewCodePeriod: query.inNewCodePeriod ? 'true' : undefined,
index 6eb078119d4350650226f0b950df36fcb532fa53..b1116843b621489858b2c16fd365f7220a2acef0 100644 (file)
@@ -59,7 +59,7 @@ describe('rendering', () => {
     expect(ui.effort('2 days').get()).toBeInTheDocument();
     expect(ui.issueMessageLink.get()).toHaveAttribute(
       'href',
-      '/issues?scopes=MAIN&severities=MINOR&types=VULNERABILITY&open=AVsae-CQS-9G3txfbFN2'
+      '/issues?scopes=MAIN&impactSeverities=MINOR&types=VULNERABILITY&open=AVsae-CQS-9G3txfbFN2'
     );
 
     await ui.clickIssueMessage();
@@ -441,7 +441,7 @@ function renderIssue(
   }
 
   return renderAppRoutes(
-    'issues?scopes=MAIN&severities=MINOR&types=VULNERABILITY',
+    'issues?scopes=MAIN&impactSeverity=LOW&types=VULNERABILITY',
     () => (
       <Route
         path="issues"
index ec51b6f62e954ef57ce096b0b94e0a2d0270504b..6665b3e6639595d6ad805d2f67fadbec2b26ab35 100644 (file)
@@ -34,6 +34,7 @@ import { RuleStatus } from '../../../types/rules';
 import { Issue, RawQuery } from '../../../types/types';
 import Tooltip from '../../controls/Tooltip';
 import DateFromNow from '../../intl/DateFromNow';
+import SoftwareImpactPill from '../../shared/SoftwareImpactPill';
 import { WorkspaceContext } from '../../workspace/context';
 import { updateIssue } from '../actions';
 import IssueAssign from './IssueAssign';
@@ -130,13 +131,12 @@ export default function IssueActionsBar(props: Props) {
 
   return (
     <div
-      className={classNames(className, 'sw-flex sw-flex-wrap sw-items-center sw-justify-between')}
+      className={classNames(
+        className,
+        'sw-flex sw-gap-2 sw-flex-wrap sw-items-center sw-justify-between'
+      )}
     >
       <ul className="it__issue-header-actions sw-flex sw-items-center sw-gap-3 sw-body-sm">
-        <li>
-          <IssueType canSetType={canSetType} issue={issue} setIssueProperty={setIssueProperty} />
-        </li>
-
         <li>
           <IssueTransition
             isOpen={currentPopup === 'transition'}
@@ -147,6 +147,21 @@ export default function IssueActionsBar(props: Props) {
           />
         </li>
 
+        <li className="sw-flex sw-gap-3">
+          {issue.impacts.map(({ severity, softwareQuality }, index) => (
+            <SoftwareImpactPill
+              key={index}
+              cleanCodeAttributeCategory={issue.cleanCodeAttributeCategory}
+              severity={severity}
+              quality={softwareQuality}
+            />
+          ))}
+        </li>
+
+        <li>
+          <IssueType canSetType={canSetType} issue={issue} setIssueProperty={setIssueProperty} />
+        </li>
+
         <li>
           <IssueSeverity
             isOpen={currentPopup === 'set-severity'}
index 8959b7fb4c3eb75fbf4409e95b1d4a799487605c..06fe08be293eef3adcff3346ac82059563427076 100644 (file)
@@ -22,6 +22,7 @@ import * as React from 'react';
 import { BranchLike } from '../../../types/branch-like';
 import { IssueActions } from '../../../types/issues';
 import { Issue } from '../../../types/types';
+import { CleanCodeAttributePill } from '../../shared/CleanCodeAttributePill';
 import IssueMessage from './IssueMessage';
 import IssueTags from './IssueTags';
 
@@ -39,13 +40,19 @@ export default function IssueTitleBar(props: IssueTitleBarProps) {
   const canSetTags = issue.actions.includes(IssueActions.SetTags);
 
   return (
-    <div className="sw-flex sw-items-center">
-      <div className="sw-w-full">
-        <IssueMessage
-          issue={issue}
-          branchLike={props.branchLike}
-          displayWhyIsThisAnIssue={displayWhyIsThisAnIssue}
+    <div className="sw-flex sw-items-end">
+      <div className="sw-w-full sw-flex sw-flex-col">
+        <CleanCodeAttributePill
+          className="sw-mb-1"
+          cleanCodeAttributeCategory={issue.cleanCodeAttributeCategory}
         />
+        <div className="sw-w-fit">
+          <IssueMessage
+            issue={issue}
+            branchLike={props.branchLike}
+            displayWhyIsThisAnIssue={displayWhyIsThisAnIssue}
+          />
+        </div>
       </div>
       <div className="js-issue-tags sw-body-sm sw-grow-0 sw-whitespace-nowrap">
         <IssueTags
index e04d48f276227d11eb762572dacf08dd09ab272c..61acc232fe841117d3a4c6940621ce3d7fd2c2e4 100644 (file)
@@ -99,7 +99,7 @@ export default class IssueView extends React.PureComponent<Props> {
       >
         <div className="sw-flex sw-w-full sw-px-2 sw-gap-4">
           {hasCheckbox && (
-            <span className="sw-mt-1/2 sw-self-start">
+            <span className="sw-mt-6 sw-self-start">
               <Checkbox
                 checked={checked ?? false}
                 onCheck={this.handleCheck}
diff --git a/server/sonar-web/src/main/js/components/shared/CleanCodeAttributePill.tsx b/server/sonar-web/src/main/js/components/shared/CleanCodeAttributePill.tsx
new file mode 100644 (file)
index 0000000..838379d
--- /dev/null
@@ -0,0 +1,52 @@
+import classNames from 'classnames';
+import { Link, Pill } from 'design-system';
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { useDocUrl } from '../../helpers/docs';
+import { translate } from '../../helpers/l10n';
+import { CleanCodeAttributeCategory } from '../../types/issues';
+import Tooltip from '../controls/Tooltip';
+
+export interface Props {
+  className?: string;
+  cleanCodeAttributeCategory: CleanCodeAttributeCategory;
+}
+
+export function CleanCodeAttributePill(props: Props) {
+  const { className, cleanCodeAttributeCategory } = props;
+
+  const docUrl = useDocUrl('/');
+
+  return (
+    <Tooltip
+      overlay={
+        <>
+          <p className="sw-mb-4">
+            {translate('issue.clean_code_attribute_category', cleanCodeAttributeCategory, 'title')}
+          </p>
+          <p>
+            {translate('issue.clean_code_attribute_category', cleanCodeAttributeCategory, 'advice')}
+          </p>
+          <hr className="sw-w-full sw-mx-0 sw-my-4" />
+          <FormattedMessage
+            defaultMessage={translate('learn_more_x')}
+            id="learn_more_x"
+            values={{
+              link: (
+                <Link isExternal to={docUrl}>
+                  {translate('issue.type.deprecation.documentation')}
+                </Link>
+              ),
+            }}
+          />
+        </>
+      }
+    >
+      <span className="sw-w-fit">
+        <Pill variant="neutral" className={classNames('sw-mr-2', className)}>
+          {translate('issue.clean_code_attribute_category', cleanCodeAttributeCategory, 'issue')}
+        </Pill>
+      </span>
+    </Tooltip>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/shared/SoftwareImpactPill.tsx b/server/sonar-web/src/main/js/components/shared/SoftwareImpactPill.tsx
new file mode 100644 (file)
index 0000000..6746200
--- /dev/null
@@ -0,0 +1,82 @@
+import classNames from 'classnames';
+import { Link, Pill } from 'design-system';
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { useDocUrl } from '../../helpers/docs';
+import { translate } from '../../helpers/l10n';
+import {
+  CleanCodeAttributeCategory,
+  SoftwareImpactSeverity,
+  SoftwareQuality,
+} from '../../types/issues';
+import Tooltip from '../controls/Tooltip';
+import SoftwareImpactSeverityIcon from '../icons/SoftwareImpactSeverityIcon';
+
+export interface Props {
+  className?: string;
+  cleanCodeAttributeCategory: CleanCodeAttributeCategory;
+  severity: SoftwareImpactSeverity;
+  quality: SoftwareQuality;
+}
+
+export default function SoftwareImpactPill(props: Props) {
+  const { cleanCodeAttributeCategory, className, severity, quality } = props;
+
+  const docUrl = useDocUrl('/');
+
+  const variant = {
+    [SoftwareImpactSeverity.High]: 'danger',
+    [SoftwareImpactSeverity.Medium]: 'warning',
+    [SoftwareImpactSeverity.Low]: 'info',
+  }[severity] as 'danger' | 'warning' | 'info';
+
+  return (
+    <div>
+      <Tooltip
+        overlay={
+          <>
+            <p className="sw-mb-4">
+              {translate(
+                'issue.clean_code_attribute_category',
+                cleanCodeAttributeCategory,
+                'title'
+              )}
+            </p>
+            <p>
+              <FormattedMessage
+                id="issue.impact.severity.tooltip"
+                defaultMessage={translate('issue.impact.severity.tooltip')}
+                values={{
+                  severity: translate('severity', severity).toLowerCase(),
+                  quality: translate('issue.software_quality', quality).toLowerCase(),
+                }}
+              />
+            </p>
+            <hr className="sw-w-full sw-mx-0 sw-my-4" />
+            <FormattedMessage
+              defaultMessage={translate('learn_more_x')}
+              id="learn_more_x"
+              values={{
+                link: (
+                  <Link isExternal to={docUrl}>
+                    {translate('issue.type.deprecation.documentation')}
+                  </Link>
+                ),
+              }}
+            />
+          </>
+        }
+      >
+        <span>
+          <Pill
+            className={classNames('sw-flex sw-gap-1 sw-items-center', className)}
+            variant={variant}
+          >
+            {translate('issue.software_quality', quality)}
+            <SoftwareImpactSeverityIcon severity={severity} />
+          </Pill>
+        </span>
+      </Tooltip>
+    </div>
+  );
+}
index 19e0c8e146fe1a6204fadbfaa8964b3157c88d3f..4bebe9bff0d00e6961c3fc90228eed4162187b0a 100644 (file)
@@ -109,6 +109,7 @@ key=Key
 language=Language
 last_analysis=Last Analysis
 learn_more=Learn More
+learn_more_x=Learn More: {link}
 library=Library
 line_number=Line Number
 links=Links
@@ -964,22 +965,39 @@ issue.type.BUG.plural=Bugs
 issue.type.VULNERABILITY.plural=Vulnerabilities
 issue.type.SECURITY_HOTSPOT.plural=Security Hotspots
 
+issue.type.deprecation.title=Issue types are deprecated and can no longer be modified.
+issue.type.deprecation.filter_by=You can now filter issues by:
+issue.type.deprecation.documentation=Documentation
+
+issue.severity.deprecation.title=Severities are now directly tied to the software quality impacted. This old severity is deprecated and can no longer be modified.
+issue.severity.deprecation.filter_by=You can now filter issues by:
+issue.severity.deprecation.documentation=Documentation
+
+issue.software_qualities=Software qualities
 issue.software_quality.SECURITY=Security
 issue.software_quality.RELIABILITY=Reliability
 issue.software_quality.MAINTAINABILITY=Maintainability
 
+issue.impact.severity.tooltip=This issue has a {severity} impact on the {quality} of your software.
 
 issue.clean_code_attribute_category.CONSISTENT=Consistency
+issue.clean_code_attribute_category.CONSISTENT.title=This is a consistency issue.
+issue.clean_code_attribute_category.CONSISTENT.advice=To be consistent, the code needs to be written in a uniform and conventional way.
+issue.clean_code_attribute_category.CONSISTENT.issue=Consistency issue
 issue.clean_code_attribute_category.INTENTIONAL=Intentionality
+issue.clean_code_attribute_category.INTENTIONAL.title=This is an intentionality issue.
+issue.clean_code_attribute_category.INTENTIONAL.advice=To be intentional, the code content needs to be precise and purposeful.
+issue.clean_code_attribute_category.INTENTIONAL.issue=Intentionality issue
 issue.clean_code_attribute_category.ADAPTABLE=Adaptability
 issue.clean_code_attribute_category.ADAPTABLE.title=This is an adaptability issue.
-issue.clean_code_attribute_category.ADAPTABLE.advice=To be adaptable, code needs to be be structured to be easy to evolve with confidence.
+issue.clean_code_attribute_category.ADAPTABLE.advice=To be adaptable, code needs to be structured to be easy to evolve with confidence.
 issue.clean_code_attribute_category.ADAPTABLE.issue=Adaptability issue
 issue.clean_code_attribute_category.RESPONSIBLE=Responsibility
 issue.clean_code_attribute_category.RESPONSIBLE.title=This is a responsibility issue.
 issue.clean_code_attribute_category.RESPONSIBLE.advice=To be responsible, the code must take into account its ethical obligations on data and potential impact of societal norms.
-issue.clean_code_attribute_category.RESPONSIBLE.issue=Responsability issue
+issue.clean_code_attribute_category.RESPONSIBLE.issue=Responsibility issue
 
+issue.clean_code_attributes=Clean Code attributes
 issue.clean_code_attribute.CLEAR=Clear
 issue.clean_code_attribute.COMPLETE=Complete
 issue.clean_code_attribute.CONVENTIONAL=Conventional