From: Viktor Vorona Date: Wed, 22 May 2024 07:46:10 +0000 (+0200) Subject: SONAR-22224 Allow changing the rules severity from the rule list X-Git-Tag: 10.6.0.92116~81 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=c0dfa636f67a4ac994b019e05edabddd4817c9ee;p=sonarqube.git SONAR-22224 Allow changing the rules severity from the rule list --- diff --git a/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts index 71cc1cbfe22..acbd1771565 100644 --- a/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts @@ -251,7 +251,7 @@ export default class CodingRulesServiceMock { filteredRules = filteredRules.filter((r) => matchingRules.includes(r.key)); } if (q && q.length > 2) { - filteredRules = filteredRules.filter((r) => r.name.includes(q)); + filteredRules = filteredRules.filter((r) => r.name.includes(q) || r.key.includes(q)); } if (tags) { filteredRules = filteredRules.filter((r) => r.tags && r.tags.some((t) => tags.includes(t))); diff --git a/server/sonar-web/src/main/js/api/mocks/data/qualityProfiles.ts b/server/sonar-web/src/main/js/api/mocks/data/qualityProfiles.ts index 48c31a14b94..677e9d4e04e 100644 --- a/server/sonar-web/src/main/js/api/mocks/data/qualityProfiles.ts +++ b/server/sonar-web/src/main/js/api/mocks/data/qualityProfiles.ts @@ -29,7 +29,14 @@ export function mockQualityProfilesList() { languageName: 'Java', actions: { edit: true }, }), - mockQualityProfile({ key: QP_2, name: 'QP Bar', language: 'py', languageName: 'Python' }), + mockQualityProfile({ + key: QP_2, + name: 'QP Bar', + language: 'py', + languageName: 'Python', + parentKey: 'sonar_way', + parentName: 'Sonar Way', + }), mockQualityProfile({ key: QP_3, name: 'QP FooBar', language: 'java', languageName: 'Java' }), mockQualityProfile({ key: QP_4, diff --git a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts index 87d5ae87ac4..0e3c07ffcb7 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts +++ b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts @@ -21,7 +21,7 @@ import { fireEvent, screen, within } from '@testing-library/react'; import selectEvent from 'react-select-event'; import CodingRulesServiceMock, { RULE_TAGS_MOCK } from '../../../api/mocks/CodingRulesServiceMock'; import SettingsServiceMock from '../../../api/mocks/SettingsServiceMock'; -import { QP_2, RULE_1 } from '../../../api/mocks/data/ids'; +import { QP_2, RULE_1, RULE_10 } from '../../../api/mocks/data/ids'; import { CLEAN_CODE_CATEGORIES, SOFTWARE_QUALITIES } from '../../../helpers/constants'; import { mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks'; import { @@ -298,7 +298,7 @@ describe('Rules app list', () => { }); }); - it('can activate/deactivate specific rule for quality profile', async () => { + it('can activate/change/deactivate specific rule for quality profile', async () => { const { ui, user } = getPageObjects(); rulesHandler.setIsAdmin(); renderCodingRulesApp(mockLoggedInUser()); @@ -314,15 +314,29 @@ describe('Rules app list', () => { await user.click(ui.qpInactiveRadio.get(ui.facetItem('QP Bar Python').get())); expect(ui.getAllRuleListItems()).toHaveLength(2); expect(ui.activateButton.getAll()).toHaveLength(2); + expect(ui.changeButton(QP_2).query()).not.toBeInTheDocument(); // Activate Rule for qp await user.click(ui.activateButton.getAll()[0]); + expect(ui.selectValue.get(ui.activateQPDialog.get())).toHaveTextContent('severity.MAJOR'); await selectEvent.select(ui.oldSeveritySelect.get(), 'severity.MINOR'); await user.click(ui.activateButton.get(ui.activateQPDialog.get())); expect(ui.activateButton.getAll()).toHaveLength(1); + expect(ui.changeButton('QP Bar').get()).toBeInTheDocument(); expect(ui.deactivateButton.getAll()).toHaveLength(1); + // Change Rule for qp + await user.click(ui.changeButton('QP Bar').get()); + expect(ui.selectValue.get(ui.changeQPDialog.get())).toHaveTextContent('severity.MINOR'); + await selectEvent.select(ui.oldSeveritySelect.get(), 'severity.BLOCKER'); + await user.click(ui.saveButton.get(ui.changeQPDialog.get())); + + // Check that new severity is saved + await user.click(ui.changeButton('QP Bar').get()); + expect(ui.selectValue.get(ui.changeQPDialog.get())).toHaveTextContent('severity.BLOCKER'); + await user.click(ui.cancelButton.get(ui.changeQPDialog.get())); + // Deactivate activated rule await user.click(ui.deactivateButton.get()); await user.click(ui.yesButton.get()); @@ -330,6 +344,50 @@ describe('Rules app list', () => { expect(ui.activateButton.getAll()).toHaveLength(2); }); + it('can revert to parent definition specific rule for quality profile', async () => { + const { ui, user } = getPageObjects(); + settingsHandler.set(SettingsKey.QPAdminCanDisableInheritedRules, 'false'); + rulesHandler.setIsAdmin(); + renderCodingRulesApp(mockLoggedInUser()); + await ui.appLoaded(); + + await user.click(ui.qpFacet.get()); + await user.click(ui.facetItem('QP Bar Python').get()); + + // Only 4 rules are activated in selected QP + expect(ui.getAllRuleListItems()).toHaveLength(4); + + // 3 rules have deactivate button and 1 rule has revert to parent definition button + expect(ui.deactivateButton.getAll()).toHaveLength(3); + expect(ui.revertToParentDefinitionButton.get()).toBeInTheDocument(); + + await user.type(ui.searchInput.get(), RULE_10); + + // Only 1 rule left after search + expect(ui.getAllRuleListItems()).toHaveLength(1); + expect(ui.revertToParentDefinitionButton.get()).toBeInTheDocument(); + expect(ui.changeButton('QP Bar').get()).toBeInTheDocument(); + + // Check that severity is reflected correctly + await user.click(ui.changeButton('QP Bar').get()); + expect(ui.selectValue.get(ui.changeQPDialog.get())).toHaveTextContent('severity.MAJOR'); + await user.click(ui.cancelButton.get(ui.changeQPDialog.get())); + + await user.click(ui.revertToParentDefinitionButton.get()); + await user.click(ui.yesButton.get()); + + expect(ui.getAllRuleListItems()).toHaveLength(1); + expect(ui.revertToParentDefinitionButton.query()).not.toBeInTheDocument(); + expect(ui.deactivateButton.get()).toBeInTheDocument(); + expect(ui.deactivateButton.get()).toBeDisabled(); + expect(ui.changeButton('QP Bar').get()).toBeInTheDocument(); + + // Check that severity is reflected correctly + await user.click(ui.changeButton('QP Bar').get()); + expect(ui.selectValue.get(ui.changeQPDialog.get())).toHaveTextContent('severity.MINOR'); + await user.click(ui.cancelButton.get(ui.changeQPDialog.get())); + }); + it('can not deactivate rules for quality profile if setting is false', async () => { const { ui } = getPageObjects(); rulesHandler.setIsAdmin(); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/ActivatedRuleActions.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/ActivatedRuleActions.tsx new file mode 100644 index 00000000000..c697a903ac6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/ActivatedRuleActions.tsx @@ -0,0 +1,135 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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 { DangerButtonSecondary, Tooltip } from 'design-system'; +import * as React from 'react'; +import { Profile } from '../../../api/quality-profiles'; +import ConfirmButton from '../../../components/controls/ConfirmButton'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { Rule, RuleActivation } from '../../../types/types'; +import ActivationButton from './ActivationButton'; + +interface Props { + activation: RuleActivation; + profile: Profile; + ruleDetails: Rule; + onActivate: (severity: string) => Promise | void; + handleRevert: (key?: string) => void; + handleDeactivate: (key?: string) => void; + showDeactivated?: boolean; + canDeactivateInherited?: boolean; +} + +export default function ActivatedRuleActions(props: Readonly) { + const { + activation, + profile, + ruleDetails, + onActivate, + handleRevert, + handleDeactivate, + showDeactivated, + canDeactivateInherited, + } = props; + + const canEdit = profile.actions?.edit && !profile.isBuiltIn; + const hasParent = activation.inherit !== 'NONE' && profile.parentKey !== undefined; + + return ( + <> + {canEdit && ( + <> + {!ruleDetails.isTemplate && ( + + )} + + {hasParent && activation.inherit === 'OVERRIDES' && profile.parentName && ( + + {({ onClick }) => ( + + {translate('coding_rules.revert_to_parent_definition')} + + )} + + )} + + {(!hasParent || canDeactivateInherited) && ( + + {({ onClick }) => ( + + {translate('coding_rules.deactivate')} + + )} + + )} + + {showDeactivated && + hasParent && + !canDeactivateInherited && + activation.inherit !== 'OVERRIDES' && ( + + + {translate('coding_rules.deactivate')} + + + )} + + )} + + ); +} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx index 970b3a959e6..2ed87f2e2cb 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx @@ -57,7 +57,6 @@ import { shouldOpenStandardsFacet, } from '../../issues/utils'; import { - Activation, Actives, FacetKey, Facets, @@ -512,7 +511,7 @@ export class CodingRulesApp extends React.PureComponent { } }; - handleRuleActivate = (profile: string, rule: string, activation: Activation) => + handleRuleActivate = (profile: string, rule: string, activation: RuleActivation) => this.setState((state: State) => { const { actives = {} } = state; if (!actives[rule]) { @@ -701,8 +700,8 @@ function parseActives(rawActives: Dict) { const actives: Actives = {}; for (const [rule, activations] of Object.entries(rawActives)) { actives[rule] = {}; - for (const { inherit, qProfile, severity } of activations) { - actives[rule][qProfile] = { inherit, severity }; + for (const activation of activations) { + actives[rule][activation.qProfile] = { ...activation }; } } return actives; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx index 1d975ba4494..09e0dda08ff 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx @@ -38,8 +38,7 @@ import { useRuleDetailsQuery, useUpdateRuleMutation, } from '../../../queries/rules'; -import { Dict } from '../../../types/types'; -import { Activation } from '../query'; +import { Dict, RuleActivation } from '../../../types/types'; import CustomRuleButton from './CustomRuleButton'; import RuleDetailsCustomRules from './RuleDetailsCustomRules'; import RuleDetailsDescription from './RuleDetailsDescription'; @@ -52,7 +51,7 @@ interface Props { allowCustomRules?: boolean; canWrite?: boolean; canDeactivateInherited?: boolean; - onActivate: (profile: string, rule: string, activation: Activation) => void; + onActivate: (profile: string, rule: string, activation: RuleActivation) => void; onDeactivate: (profile: string, rule: string) => void; onDelete: (rule: string) => void; referencedProfiles: Dict; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx index abe0965eda6..ffeadf6c055 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx @@ -23,7 +23,6 @@ import { ActionCell, CellComponent, ContentCell, - DangerButtonSecondary, DiscreetLink, InheritanceIcon, Link, @@ -36,8 +35,7 @@ import { filter } from 'lodash'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { Profile } from '../../../api/quality-profiles'; -import ConfirmButton from '../../../components/controls/ConfirmButton'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { translate } from '../../../helpers/l10n'; import { getQualityProfileUrl } from '../../../helpers/urls'; import { useActivateRuleMutation, @@ -45,6 +43,7 @@ import { } from '../../../queries/quality-profiles'; import { Dict, RuleActivation, RuleDetails } from '../../../types/types'; import BuiltInQualityProfileBadge from '../../quality-profiles/components/BuiltInQualityProfileBadge'; +import ActivatedRuleActions from './ActivatedRuleActions'; import ActivationButton from './ActivationButton'; interface Props { @@ -90,69 +89,17 @@ export default function RuleDetailsProfiles(props: Readonly) { }; const renderRowActions = (activation: RuleActivation, profile: Profile) => { - const canEdit = profile.actions?.edit && !profile.isBuiltIn; - const hasParent = activation.inherit !== 'NONE' && profile.parentKey; - return ( - {canEdit && ( - <> - {!ruleDetails.isTemplate && ( - - )} - - {hasParent && activation.inherit === 'OVERRIDES' && profile.parentName && ( - - {({ onClick }) => ( - - {translate('coding_rules.revert_to_parent_definition')} - - )} - - )} - - {(!hasParent || canDeactivateInherited) && ( - - {({ onClick }) => ( - - {translate('coding_rules.deactivate')} - - )} - - )} - - )} + ); }; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx index 8351a52a534..71e3a55a526 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx @@ -31,25 +31,27 @@ import { } from 'design-system'; import * as React from 'react'; import DocHelpTooltip from '~sonar-aligned/components/controls/DocHelpTooltip'; -import { Profile, deactivateRule } from '../../../api/quality-profiles'; -import ConfirmButton from '../../../components/controls/ConfirmButton'; +import { Profile } from '../../../api/quality-profiles'; import Tooltip from '../../../components/controls/Tooltip'; import { CleanCodeAttributePill } from '../../../components/shared/CleanCodeAttributePill'; import SoftwareImpactPillList from '../../../components/shared/SoftwareImpactPillList'; import TypeHelper from '../../../components/shared/TypeHelper'; import TagsList from '../../../components/tags/TagsList'; -import { DocLink } from '../../../helpers/doc-links'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { getRuleUrl } from '../../../helpers/urls'; -import { Rule } from '../../../types/types'; -import { Activation } from '../query'; +import { + useActivateRuleMutation, + useDeactivateRuleMutation, +} from '../../../queries/quality-profiles'; +import { Rule, RuleActivation } from '../../../types/types'; +import ActivatedRuleActions from './ActivatedRuleActions'; import ActivationButton from './ActivationButton'; interface Props { - activation?: Activation; + activation?: RuleActivation; isLoggedIn: boolean; canDeactivateInherited?: boolean; - onActivate: (profile: string, rule: string, activation: Activation) => void; + onActivate: (profile: string, rule: string, activation: RuleActivation) => void; onDeactivate: (profile: string, rule: string) => void; onOpen: (ruleKey: string) => void; rule: Rule; @@ -58,31 +60,66 @@ interface Props { selectedProfile?: Profile; } -export default class RuleListItem extends React.PureComponent { - handleDeactivate = () => { - if (this.props.selectedProfile) { +export default function RuleListItem(props: Readonly) { + const { + activation, + rule, + selectedProfile, + isLoggedIn, + selected, + selectRule, + canDeactivateInherited, + onDeactivate, + onActivate, + onOpen, + } = props; + const { mutate: activateRule } = useActivateRuleMutation((data) => { + if (data.reset && activation) { + onActivate(data.key, data.rule, { + ...activation, + // Actually the severity should be taken from the inherited qprofile, but we don't have this information + severity: rule.severity, + inherit: 'INHERITED', + }); + } + }); + const { mutate: deactivateRule } = useDeactivateRuleMutation((data) => + onDeactivate(data.key, data.rule), + ); + const handleDeactivate = () => { + if (selectedProfile) { const data = { - key: this.props.selectedProfile.key, - rule: this.props.rule.key, + key: selectedProfile.key, + rule: rule.key, }; - deactivateRule(data).then( - () => this.props.onDeactivate(data.key, data.rule), - () => {}, - ); + deactivateRule(data); } }; - handleActivate = (severity: string) => { - if (this.props.selectedProfile) { - this.props.onActivate(this.props.selectedProfile.key, this.props.rule.key, { + const handleActivate = (severity: string) => { + if (selectedProfile) { + onActivate(selectedProfile.key, rule.key, { + createdAt: new Date().toISOString(), severity, - inherit: 'NONE', + params: [], + qProfile: selectedProfile.key, + inherit: activation ? 'OVERRIDES' : 'NONE', }); } return Promise.resolve(); }; - handleNameClick = (event: React.MouseEvent) => { + const handleRevert = (key?: string) => { + if (key !== undefined) { + activateRule({ + key, + rule: rule.key, + reset: true, + }); + } + }; + + const handleNameClick = (event: React.MouseEvent) => { // cmd(ctrl) + click should open a rule permalink in a new tab const isLeftClickEvent = event.button === 0; const isModifiedEvent = !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); @@ -92,12 +129,11 @@ export default class RuleListItem extends React.PureComponent { event.preventDefault(); event.stopPropagation(); - this.props.onOpen(this.props.rule.key); + onOpen(rule.key); }; - renderActivation = () => { - const { activation, selectedProfile } = this.props; - if (!activation || !selectedProfile?.parentName) { + const renderActivation = () => { + if (!activation || selectedProfile?.parentName === undefined) { return null; } @@ -133,9 +169,7 @@ export default class RuleListItem extends React.PureComponent { ); }; - renderActions = () => { - const { activation, isLoggedIn, canDeactivateInherited, rule, selectedProfile } = this.props; - + const renderActions = () => { if (!selectedProfile || !isLoggedIn) { return null; } @@ -160,28 +194,16 @@ export default class RuleListItem extends React.PureComponent { if (activation) { return ( -
- {activation.inherit === 'NONE' || canDeactivateInherited ? ( - - {({ onClick }) => ( - - {translate('coding_rules.deactivate')} - - )} - - ) : ( - - - {translate('coding_rules.deactivate')} - - - )} -
+ ); } @@ -191,7 +213,7 @@ export default class RuleListItem extends React.PureComponent { @@ -200,118 +222,115 @@ export default class RuleListItem extends React.PureComponent { ); }; - render() { - const { rule, selected } = this.props; - const allTags = [...(rule.tags ?? []), ...(rule.sysTags ?? [])]; - return ( - this.props.selectRule(rule.key)} - > -
-
-
- {this.renderActivation()} + const allTags = [...(rule.tags ?? []), ...(rule.sysTags ?? [])]; + return ( + selectRule(rule.key)} + > +
+
+
+ {renderActivation()} - - {rule.name} - -
+ + {rule.name} + +
-
- {rule.cleanCodeAttributeCategory !== undefined && ( - - )} -
+
+ {rule.cleanCodeAttributeCategory !== undefined && ( + + )}
+
-
-
- {rule.impacts.length > 0 && ( - - )} -
+
+
+ {rule.impacts.length > 0 && ( + + )} +
- -
  • {rule.langName}
  • + +
  • {rule.langName}
  • - -
  • - -

    {translate('coding_rules.type.deprecation.title')}

    -

    {translate('coding_rules.type.deprecation.filter_by')}

    -
  • - } - links={[ - { - href: DocLink.CleanCodeIntroduction, - label: translate('learn_more'), - }, - ]} - > - - - + +
  • + +

    {translate('coding_rules.type.deprecation.title')}

    +

    {translate('coding_rules.type.deprecation.filter_by')}

    +
  • + } + links={[ + { + href: '/user-guide/clean-code/introduction', + label: translate('learn_more'), + }, + ]} + > + + + - {rule.isTemplate && ( - <> - -
  • - - - {translate('coding_rules.rule_template')} - - -
  • - - )} + {rule.isTemplate && ( + <> + +
  • + + + {translate('coding_rules.rule_template')} + + +
  • + + )} - {rule.status !== 'READY' && ( - <> - -
  • - {translate('rules.status', rule.status)} -
  • - - )} + {rule.status !== 'READY' && ( + <> + +
  • + {translate('rules.status', rule.status)} +
  • + + )} - {allTags.length > 0 && ( - <> - -
  • - -
  • - - )} - + {allTags.length > 0 && ( + <> + +
  • + +
  • + + )} + -
    {this.renderActions()}
    -
    +
    {renderActions()}
    - - ); - } +
    +
    + ); } const ListItemStyled = styled.li<{ selected: boolean }>` diff --git a/server/sonar-web/src/main/js/apps/coding-rules/query.ts b/server/sonar-web/src/main/js/apps/coding-rules/query.ts index 5a4659072ff..4950906bd5a 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/query.ts +++ b/server/sonar-web/src/main/js/apps/coding-rules/query.ts @@ -36,7 +36,7 @@ import { SoftwareImpactSeverity, SoftwareQuality, } from '../../types/clean-code-taxonomy'; -import { Dict, RuleInheritance } from '../../types/types'; +import { Dict, RuleActivation, RuleInheritance } from '../../types/types'; export interface Query { activation: boolean | undefined; @@ -72,14 +72,9 @@ export type Facets = { [F in FacetKey]?: Facet }; export type OpenFacets = Dict; -export interface Activation { - inherit: RuleInheritance; - severity: string; -} - export interface Actives { [rule: string]: { - [profile: string]: Activation; + [profile: string]: RuleActivation; }; } diff --git a/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx b/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx index c81db6ff094..0e59dce516c 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx @@ -147,9 +147,10 @@ const selectors = { // Rule Quality Profiles qpLink: (name: string) => byRole('link', { name }), activateButton: byRole('button', { name: 'coding_rules.activate' }), - deactivateButton: byRole('button', { name: 'coding_rules.deactivate' }), + deactivateButton: byRole('button', { name: /coding_rules.deactivate_in_quality_profile/ }), oldSeveritySelect: byRole('combobox', { name: 'severity' }), qualityProfileSelect: byRole('combobox', { name: 'coding_rules.quality_profile' }), + selectValue: byText(/severity\./), activateQPDialog: byRole('dialog', { name: 'coding_rules.activate_in_quality_profile' }), changeButton: (profile: string) => byRole('button', { name: `coding_rules.change_details_x.${profile}` }),