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;
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;
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 {
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';
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';
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[];
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;
--- /dev/null
+/*
+ * 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`}
+ }
+`;
>
{({ 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}
>
color: ${themeColor('danger')};
`;
+export const DisabledText = styled.span`
+ ${tw`sw-font-regular`};
+ color: ${themeColor('pageContentLight')};
+`;
+
export const LightLabel = styled.span`
color: ${themeColor('pageContentLight')};
`;
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
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;
ItemNavLink,
ItemRadioButton,
} from '../DropdownMenu';
-import Tooltip from '../Tooltip';
+import { Tooltip } from '../Tooltip';
import { MenuIcon } from '../icons/MenuIcon';
beforeEach(() => {
--- /dev/null
+/*
+ * 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');
+});
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;
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';
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 {
export * from './NavBarTabs';
export * from './NewCodeLegend';
export * from './OutsideClickHandler';
+export { Pill } from './Pill';
export { QualityGateIndicator } from './QualityGateIndicator';
export * from './SearchHighlighter';
export * from './SelectionCard';
export * from './Text';
export * from './Title';
export { ToggleButton } from './ToggleButton';
+export { Tooltip } from './Tooltip';
export { TopBar } from './TopBar';
export * from './TreeMap';
export * from './TreeMapRect';
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,
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],
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,
// issue box
issueTypeIcon: COLORS.red[900],
+ iconSeverityDisabled: COLORS.white,
+ iconTypeDisabled: COLORS.white,
// selection card
selectionCardDisabled: secondary.dark,
} from '../../../types/issues';
import { SecurityStandard } from '../../../types/security';
import {
+ parseQuery,
serializeQuery,
shouldOpenSonarSourceSecurityFacet,
shouldOpenStandardsChildFacet,
rules: 'a,b',
s: 'rules',
scopes: 'a,b',
- severities: 'a,b',
inNewCodePeriod: 'true',
sonarsourceSecurity: 'a,b',
statuses: 'a,b',
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', () => {
* 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 {
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
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),
};
}
+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;
}
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,
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();
}
return renderAppRoutes(
- 'issues?scopes=MAIN&severities=MINOR&types=VULNERABILITY',
+ 'issues?scopes=MAIN&impactSeverity=LOW&types=VULNERABILITY',
() => (
<Route
path="issues"
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';
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'}
/>
</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'}
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';
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
>
<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}
--- /dev/null
+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>
+ );
+}
--- /dev/null
+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>
+ );
+}
language=Language
last_analysis=Last Analysis
learn_more=Learn More
+learn_more_x=Learn More: {link}
library=Library
line_number=Line Number
links=Links
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