@@ -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; |
@@ -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; |
@@ -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 { |
@@ -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'; |
@@ -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'; |
@@ -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[]; |
@@ -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; |
@@ -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`} | |||
} | |||
`; |
@@ -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} | |||
> |
@@ -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')}; | |||
`; |
@@ -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 |
@@ -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; |
@@ -32,7 +32,7 @@ import { | |||
ItemNavLink, | |||
ItemRadioButton, | |||
} from '../DropdownMenu'; | |||
import Tooltip from '../Tooltip'; | |||
import { Tooltip } from '../Tooltip'; | |||
import { MenuIcon } from '../icons/MenuIcon'; | |||
beforeEach(() => { |
@@ -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'); | |||
}); |
@@ -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; |
@@ -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'; |
@@ -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 { |
@@ -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'; |
@@ -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, |
@@ -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', () => { |
@@ -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, |
@@ -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" |
@@ -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'} |
@@ -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 |
@@ -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} |
@@ -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> | |||
); | |||
} |
@@ -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> | |||
); | |||
} |
@@ -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 |