From: Viktor Vorona Date: Thu, 3 Aug 2023 13:59:01 +0000 (+0200) Subject: SONAR-20023 CCT guide for issues list page X-Git-Tag: 10.2.0.77647~196 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=3c949c01785f9edcc81097484ef92ec54922dcd9;p=sonarqube.git SONAR-20023 CCT guide for issues list page --- diff --git a/server/sonar-web/design-system/package.json b/server/sonar-web/design-system/package.json index 52f053c905b..5eced492114 100644 --- a/server/sonar-web/design-system/package.json +++ b/server/sonar-web/design-system/package.json @@ -73,6 +73,7 @@ "react-helmet-async": "1.3.0", "react-highlight-words": "0.20.0", "react-intl": "6.4.4", + "react-joyride": "2.5.5", "react-modal": "3.16.1", "react-router-dom": "6.11.2", "react-select": "5.7.3", diff --git a/server/sonar-web/design-system/src/components/Guide.tsx b/server/sonar-web/design-system/src/components/Guide.tsx new file mode 100644 index 00000000000..6aaaf1596b2 --- /dev/null +++ b/server/sonar-web/design-system/src/components/Guide.tsx @@ -0,0 +1,100 @@ +/* + * 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 { Suspense } from 'react'; +import ReactJoyride, { Props as JoyrideProps, TooltipRenderProps } from 'react-joyride'; +import tw from 'twin.macro'; +import { PopupZLevel } from '../helpers'; +import { Spinner } from './DeferredSpinner'; +import { ButtonPrimary, ButtonSecondary, WrapperButton } from './buttons'; +import { CloseIcon } from './icons'; +import { PopupWrapper } from './popups'; + +type Props = JoyrideProps; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access +(window as any).global = (window as any).global ?? {}; + +const Popup = styled(PopupWrapper)` + position: relative; + width: 300px; + border: none; + ${tw`sw-body-sm`} + ${tw`sw-p-3`} +`; + +function TooltipComponent({ + continuous, + index, + step, + size, + isLastStep, + backProps, + skipProps, + closeProps, + primaryProps, + tooltipProps, +}: TooltipRenderProps) { + return ( + +
+ {step.title} + + + +
+
{step.content}
+
+ + {index + 1} of {size} + +
+ {index > 0 && ( + + {backProps.title} + + )} + {continuous && !isLastStep && ( + {primaryProps.title} + )} + {(!continuous || isLastStep) && ( + {closeProps.title} + )} +
+
+
+ ); +} + +export function Guide(props: Props) { + return ( + }> + + + ); +} diff --git a/server/sonar-web/design-system/src/components/buttons/Button.tsx b/server/sonar-web/design-system/src/components/buttons/Button.tsx index 2d2ee37f744..bfcf1788f66 100644 --- a/server/sonar-web/design-system/src/components/buttons/Button.tsx +++ b/server/sonar-web/design-system/src/components/buttons/Button.tsx @@ -39,7 +39,7 @@ export interface ButtonProps extends AllowedButtonAttributes { icon?: React.ReactNode; innerRef?: React.Ref; isExternal?: LinkProps['isExternal']; - onClick?: (event?: React.MouseEvent) => unknown; + onClick?: (event: React.MouseEvent) => unknown; preventDefault?: boolean; reloadDocument?: LinkProps['reloadDocument']; diff --git a/server/sonar-web/design-system/src/components/index.ts b/server/sonar-web/design-system/src/components/index.ts index 92aa59aca20..b839c227262 100644 --- a/server/sonar-web/design-system/src/components/index.ts +++ b/server/sonar-web/design-system/src/components/index.ts @@ -43,6 +43,7 @@ export * from './FavoriteButton'; export { FlagMessage } from './FlagMessage'; export * from './FlowStep'; export * from './GenericAvatar'; +export * from './Guide'; export * from './HighlightedSection'; export { Histogram } from './Histogram'; export { HotspotRating } from './HotspotRating'; diff --git a/server/sonar-web/design-system/src/components/popups.tsx b/server/sonar-web/design-system/src/components/popups.tsx index a80d9b949ea..d24f691777b 100644 --- a/server/sonar-web/design-system/src/components/popups.tsx +++ b/server/sonar-web/design-system/src/components/popups.tsx @@ -200,7 +200,7 @@ export class Popup extends React.PureComponent { } } -const PopupWrapper = styled.div<{ zLevel: PopupZLevel }>` +export const PopupWrapper = styled.div<{ zLevel: PopupZLevel }>` position: ${({ zLevel }) => (zLevel === PopupZLevel.Global ? 'fixed' : 'absolute')}; background-color: ${themeColor('popup')}; color: ${themeContrast('popup')}; diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json index c7808145a30..2540b442595 100644 --- a/server/sonar-web/package.json +++ b/server/sonar-web/package.json @@ -36,6 +36,7 @@ "react-helmet-async": "1.3.0", "react-highlight-words": "0.20.0", "react-intl": "6.4.4", + "react-joyride": "2.5.5", "react-modal": "3.16.1", "react-router-dom": "6.11.2", "react-select": "5.7.3", diff --git a/server/sonar-web/src/main/js/api/mocks/LocalStorageMock.ts b/server/sonar-web/src/main/js/api/mocks/LocalStorageMock.ts new file mode 100644 index 00000000000..b5d42008f66 --- /dev/null +++ b/server/sonar-web/src/main/js/api/mocks/LocalStorageMock.ts @@ -0,0 +1,51 @@ +/* + * 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 default class LocalStorageMock { + store: Record; + + constructor() { + this.store = {}; + } + + clear() { + this.store = {}; + } + + getItem(key: string) { + return this.store[key] || null; + } + + setItem(key: string, value: string) { + this.store[key] = String(value); + } + + removeItem(key: string) { + delete this.store[key]; + } + + get length() { + return Object.keys(this.store).length; + } + + key(index: number) { + return Object.keys(this.store)[index] || null; + } +} diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueListGuide.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueListGuide.tsx new file mode 100644 index 00000000000..da88c12e22d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/components/IssueListGuide.tsx @@ -0,0 +1,145 @@ +/* + * 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 { Guide } from 'design-system'; +import React, { useEffect } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { dismissNotice } from '../../../api/users'; +import { CurrentUserContext } from '../../../app/components/current-user/CurrentUserContext'; +import DocLink from '../../../components/common/DocLink'; +import { translate } from '../../../helpers/l10n'; +import { NoticeType } from '../../../types/users'; + +interface Props { + run?: boolean; +} + +export default function IssueListGuide({ run }: Props) { + const { currentUser } = React.useContext(CurrentUserContext); + const hasLocalStorageValue = localStorage.getItem(NoticeType.ISSUE_GUIDE) === 'true'; + + useEffect(() => { + if (!currentUser.dismissedNotices.IssueListGuiding && hasLocalStorageValue) { + dismissNotice(NoticeType.ISSUE_GUIDE); + } + }, [currentUser, hasLocalStorageValue]); + + if (hasLocalStorageValue || currentUser.dismissedNotices.IssueListGuiding) { + return null; + } + + const onToggle = (props: { action: string }) => { + if (props.action === 'reset') { + if (currentUser.isLoggedIn) { + dismissNotice(NoticeType.ISSUE_GUIDE); + } else { + localStorage.setItem(NoticeType.ISSUE_GUIDE, 'true'); + } + } + }; + + const constructContent = ( + first: string, + second: string, + extraContent?: string | React.ReactNode + ) => ( + <> + {translate(first)} +
+
+ {translate(second)} + {extraContent ?? null} + + ); + + const steps = [ + { + target: '[data-guiding-id="issuelist-1"]', + content: constructContent('guiding.issue_list.1.content.1', 'guiding.issue_list.1.content.2'), + title: translate('guiding.issue_list.1.title'), + placement: 'right' as const, + disableBeacon: true, + floaterProps: { + disableAnimation: true, + }, + }, + { + target: '[data-guiding-id="issuelist-2"]', + content: constructContent('guiding.issue_list.2.content.1', 'guiding.issue_list.2.content.2'), + title: translate('guiding.issue_list.2.title'), + placement: 'right' as const, + disableBeacon: true, + floaterProps: { + disableAnimation: true, + }, + }, + { + target: '[data-guiding-id="issuelist-3"]', + content: constructContent('guiding.issue_list.2.content.1', 'guiding.issue_list.2.content.2'), + title: translate('guiding.issue_list.3.title'), + placement: 'right' as const, + disableBeacon: true, + floaterProps: { + disableAnimation: true, + }, + }, + { + target: '[data-guiding-id="issuelist-4"]', + content: constructContent( + 'guiding.issue_list.4.content.1', + 'guiding.issue_list.4.content.2', +
    +
  • {translate('guiding.issue_list.4.content.list.1')}
  • +
  • {translate('guiding.issue_list.4.content.list.2')}
  • +
  • {translate('guiding.issue_list.4.content.list.3')}
  • +
+ ), + title: translate('guiding.issue_list.4.title'), + disableScrolling: true, + disableBeacon: true, + floaterProps: { + disableAnimation: true, + }, + }, + { + target: 'body', + content: ( + + {translate('documentation')} + + ), + }} + /> + ), + title: translate('guiding.issue_list.5.title'), + placement: 'center' as const, + disableBeacon: true, + floaterProps: { + disableAnimation: true, + }, + }, + ]; + + return ; +} diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx index f9bf57cbff1..1975ae716da 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx @@ -100,6 +100,7 @@ import { shouldOpenStandardsFacet, } from '../utils'; import BulkChangeModal, { MAX_PAGE_SIZE } from './BulkChangeModal'; +import IssueListGuide from './IssueListGuide'; import IssueReviewHistoryAndComments from './IssueReviewHistoryAndComments'; import IssuesList from './IssuesList'; import IssuesSourceViewer from './IssuesSourceViewer'; @@ -1303,6 +1304,8 @@ export class App extends React.PureComponent { render() { const { openIssue } = this.state; + const { component, location } = this.props; + const open = getOpen(location.query); return ( @@ -1310,6 +1313,7 @@ export class App extends React.PureComponent {
+ {openIssue ? ( +
+ +
); } diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/SoftwareQualityFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/SoftwareQualityFacet.tsx index 2851cc06a60..d0dec615114 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/SoftwareQualityFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/SoftwareQualityFacet.tsx @@ -32,12 +32,14 @@ export function SoftwareQualityFacet(props: Props) { const { qualities = [], ...rest } = props; return ( - +
+ +
); } diff --git a/server/sonar-web/src/main/js/apps/issues/test-utils.tsx b/server/sonar-web/src/main/js/apps/issues/test-utils.tsx index fc243e293f1..42af36dee89 100644 --- a/server/sonar-web/src/main/js/apps/issues/test-utils.tsx +++ b/server/sonar-web/src/main/js/apps/issues/test-utils.tsx @@ -127,6 +127,8 @@ export const ui = { ruleFacetList: byRole('list', { name: 'issues.facet.rules' }), ruleFacetSearch: byPlaceholderText('search.search_for_rules'), tagFacetSearch: byPlaceholderText('search.search_for_tags'), + + guidePopup: byRole('alertdialog'), }; export async function waitOnDataLoaded() { diff --git a/server/sonar-web/src/main/js/types/users.ts b/server/sonar-web/src/main/js/types/users.ts index ab2421f700e..d7acd57f448 100644 --- a/server/sonar-web/src/main/js/types/users.ts +++ b/server/sonar-web/src/main/js/types/users.ts @@ -32,6 +32,7 @@ export interface Notice { export enum NoticeType { EDUCATION_PRINCIPLES = 'educationPrinciples', SONARLINT_AD = 'sonarlintAd', + ISSUE_GUIDE = 'issueCleanCodeGuide', } export interface LoggedInUser extends CurrentUser, UserActive { diff --git a/server/sonar-web/yarn.lock b/server/sonar-web/yarn.lock index 50ecf6de2b4..bd24a6320eb 100644 --- a/server/sonar-web/yarn.lock +++ b/server/sonar-web/yarn.lock @@ -3074,6 +3074,13 @@ __metadata: languageName: node linkType: hard +"@gilbarbara/deep-equal@npm:^0.1.1": + version: 0.1.2 + resolution: "@gilbarbara/deep-equal@npm:0.1.2" + checksum: 78d4e76d36cbee639c008a63be52c1ac803212ff2560e55f68d2b8b2a6ac5e746c1976854cf101483ca18a9911aed2349da147b7756be43e75efb95e3f24468b + languageName: node + linkType: hard + "@humanwhocodes/config-array@npm:^0.11.10": version: 0.11.10 resolution: "@humanwhocodes/config-array@npm:0.11.10" @@ -4995,6 +5002,7 @@ __metadata: react-helmet-async: 1.3.0 react-highlight-words: 0.20.0 react-intl: 6.4.4 + react-joyride: 2.5.5 react-modal: 3.16.1 react-router-dom: 6.11.2 react-select: 5.7.3 @@ -6499,6 +6507,13 @@ __metadata: languageName: node linkType: hard +"deepmerge@npm:^4.3.1": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 2024c6a980a1b7128084170c4cf56b0fd58a63f2da1660dcfe977415f27b17dbe5888668b59d0b063753f3220719d5e400b7f113609489c90160bb9a5518d052 + languageName: node + linkType: hard + "define-properties@npm:^1.1.2, define-properties@npm:^1.1.3": version: 1.1.3 resolution: "define-properties@npm:1.1.3" @@ -6608,6 +6623,7 @@ __metadata: react-helmet-async: 1.3.0 react-highlight-words: 0.20.0 react-intl: 6.4.4 + react-joyride: 2.5.5 react-modal: 3.16.1 react-router-dom: 6.11.2 react-select: 5.7.3 @@ -7707,7 +7723,7 @@ __metadata: languageName: node linkType: hard -"exenv@npm:^1.2.0": +"exenv@npm:^1.2.0, exenv@npm:^1.2.2": version: 1.2.2 resolution: "exenv@npm:1.2.2" checksum: a894f3b60ab8419e0b6eec99c690a009c8276b4c90655ccaf7d28faba2de3a6b93b3d92210f9dc5efd36058d44f04098f6bbccef99859151104bfd49939904dc @@ -8875,6 +8891,20 @@ __metadata: languageName: node linkType: hard +"is-lite@npm:^0.8.2": + version: 0.8.2 + resolution: "is-lite@npm:0.8.2" + checksum: 0ee62cb238c2a044f58d1cd139fb0b48026c407ec8625ee6572b417f164e17ec937f0a0785f466e320749a796c316a3b78dcb4b520f7ddd4b9de38ad5a23d70f + languageName: node + linkType: hard + +"is-lite@npm:^0.9.2": + version: 0.9.2 + resolution: "is-lite@npm:0.9.2" + checksum: 8c4d2c58cf99a8289715925c0c3175dadf63e5ad293ad395ce650430ce90afe533a84ad0ffdeed0ce277dabdae63acdd3dac5d9b629bd61c3c0c620e2376f26e + languageName: node + linkType: hard + "is-map@npm:^2.0.1, is-map@npm:^2.0.2": version: 2.0.2 resolution: "is-map@npm:2.0.2" @@ -11050,6 +11080,13 @@ __metadata: languageName: node linkType: hard +"popper.js@npm:^1.16.0": + version: 1.16.1 + resolution: "popper.js@npm:1.16.1" + checksum: c56ae5001ec50a77ee297a8061a0221d99d25c7348d2e6bcd3e45a0d0f32a1fd81bca29d46cb0d4bdf13efb77685bd6a0ce93f9eb3c608311a461f945fffedbe + languageName: node + linkType: hard + "postcss-calc@npm:9.0.1": version: 9.0.1 resolution: "postcss-calc@npm:9.0.1" @@ -11388,6 +11425,24 @@ __metadata: languageName: node linkType: hard +"react-floater@npm:^0.7.6": + version: 0.7.6 + resolution: "react-floater@npm:0.7.6" + dependencies: + deepmerge: ^4.2.2 + exenv: ^1.2.2 + is-lite: ^0.8.2 + popper.js: ^1.16.0 + prop-types: ^15.8.1 + react-proptype-conditional-require: ^1.0.4 + tree-changes: ^0.9.1 + peerDependencies: + react: 15 - 18 + react-dom: 15 - 18 + checksum: 8268e14fbdf9393b39300f3c90ea2de382782f1d959176579e30841095a73f9240ec05fce3ec89e8b4e58cbc46c9b043b2ad0b338f5c5d3b91168b16a4282ac1 + languageName: node + linkType: hard + "react-helmet-async@npm:1.3.0": version: 1.3.0 resolution: "react-helmet-async@npm:1.3.0" @@ -11476,6 +11531,26 @@ __metadata: languageName: node linkType: hard +"react-joyride@npm:2.5.5": + version: 2.5.5 + resolution: "react-joyride@npm:2.5.5" + dependencies: + deepmerge: ^4.3.1 + exenv: ^1.2.2 + is-lite: ^0.9.2 + prop-types: ^15.8.1 + react-floater: ^0.7.6 + react-is: ^16.13.1 + scroll: ^3.0.1 + scrollparent: ^2.1.0 + tree-changes: ^0.9.2 + peerDependencies: + react: 15 - 18 + react-dom: 15 - 18 + checksum: ca4a18631e12638e10f7ebb063880f66a984a4edc0fe81f87649ac1ec32c3790a03c6cbe10f189c029d64af52eef4173042c676024987accb0aeaa4e1216bb04 + languageName: node + linkType: hard + "react-lifecycles-compat@npm:^3.0.0, react-lifecycles-compat@npm:^3.0.4": version: 3.0.4 resolution: "react-lifecycles-compat@npm:3.0.4" @@ -11498,6 +11573,13 @@ __metadata: languageName: node linkType: hard +"react-proptype-conditional-require@npm:^1.0.4": + version: 1.0.4 + resolution: "react-proptype-conditional-require@npm:1.0.4" + checksum: 78f82d15b2c77c14fd8fbcbbed279850df3a856984aacd519ee6c2162e034b114b8ac47c00157b84ef7c98c0711d933a0177d9d54555629cf381f54341bb0e8f + languageName: node + linkType: hard + "react-refresh@npm:^0.14.0": version: 0.14.0 resolution: "react-refresh@npm:0.14.0" @@ -12051,6 +12133,20 @@ __metadata: languageName: node linkType: hard +"scroll@npm:^3.0.1": + version: 3.0.1 + resolution: "scroll@npm:3.0.1" + checksum: e6b045347adace30035073882e6ef2af7e1c81dd611faf3a578ca8cd0d1a3a9da54932dd97ed6fd99c9573351c758fa50e6d8ed4afb5bd3a33794b6c48d25922 + languageName: node + linkType: hard + +"scrollparent@npm:^2.1.0": + version: 2.1.0 + resolution: "scrollparent@npm:2.1.0" + checksum: 646cfdaf981f94b52f1020cc26b18ff1a751c2fa4e40777f6dfac314500d365b4db3fec39fd06be7b683f0a1dbd29df8ce1629db50381e9072433465b2f446fa + languageName: node + linkType: hard + "select@npm:^1.1.2": version: 1.1.2 resolution: "select@npm:1.1.2" @@ -12798,6 +12894,16 @@ __metadata: languageName: node linkType: hard +"tree-changes@npm:^0.9.1, tree-changes@npm:^0.9.2": + version: 0.9.3 + resolution: "tree-changes@npm:0.9.3" + dependencies: + "@gilbarbara/deep-equal": ^0.1.1 + is-lite: ^0.8.2 + checksum: 86d890b18e83f2a20e7257982aec62efa186abbb08de4cead1c8062c50793f5b3c5fd09f98d9f4b8784b921e830687c0ea9bdb42ef4abb2eb9d6782d8c56a673 + languageName: node + linkType: hard + "ts-interface-checker@npm:^0.1.9": version: 0.1.13 resolution: "ts-interface-checker@npm:0.1.13" diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 0e7e5ffa120..c5a3b5ab7df 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -4951,3 +4951,29 @@ component_report.unsubscribe.description=If you no longer wish to receive these component_report.subscribe_x_success=Subscription successful. You will receive a {0} report for this {1} by email. component_report.unsubscribe_x_success=Subscription successfully canceled. You won't receive a {0} report for this {1} by email. component_report.unsubscribe_success=Subscription successfully canceled. You won't receive these reports by email anymore. + + +#------------------------------------------------------------------------------ +# +# GUIDING +# +#------------------------------------------------------------------------------ +guiding.issue_list.1.title=Introducing Clean Code attributes +guiding.issue_list.1.content.1=Clean Code attributes are the characteristic that your code must have to be considered Clean Code. +guiding.issue_list.1.content.2=You can now filter by these attributes to evaluate why your code is breaking away from being clean. +guiding.issue_list.2.title=Introducing Software Qualities +guiding.issue_list.2.content.1=A software quality is a characteristic of software that contributes to its lasting value. +guiding.issue_list.2.content.2=You can now filter by these qualities to evaluate the areas in your software that are impacted by the introduction of code that isn't clean. +guiding.issue_list.3.title=Severity and Software Qualities +guiding.issue_list.3.content.1=Severities are now directly tied to the software quality impacted. This means that one software quality impacted has one severity. +guiding.issue_list.3.content.2=There are three only 3 levels: high, medium, and low. +guiding.issue_list.4.title=Type and old severity deprecated +guiding.issue_list.4.content.1=Issue types and the old severities are deprecated and can no longer be modified. +guiding.issue_list.4.content.2=You can now filter issues by: +guiding.issue_list.4.content.list.1=Clean Code attributes +guiding.issue_list.4.content.list.2=Software qualities +guiding.issue_list.4.content.list.3=The severity of the software quality +guiding.issue_list.5.title=Learn more +guiding.issue_list.5.content=You can learn more about the approach to Clean Code in the {link} + +