children: ReactNode;
className?: string;
description?: string | ReactNode;
+ disabled?: boolean;
help?: ReactNode;
htmlFor?: string;
id?: string;
children,
className,
description,
+ disabled,
help,
id,
required,
return (
<FieldWrapper className={className} id={id}>
<Highlight className="sw-mb-2 sw-flex sw-items-center sw-gap-2">
- <StyledLabel aria-label={ariaLabel} htmlFor={htmlFor} title={title}>
+ <StyledLabel aria-label={ariaLabel} disabled={disabled} htmlFor={htmlFor} title={title}>
{label}
{required && (
<RequiredIcon aria-label={requiredAriaLabel ?? 'required'} className="sw-ml-1" />
// This is needed to prevent the target input/button from being focused
// when clicking/hovering on the label. More info https://stackoverflow.com/questions/9098581/why-is-hover-for-input-triggered-on-corresponding-label-in-css
-const StyledLabel = styled(Label)`
+const StyledLabel = styled(Label)<{ disabled?: boolean }>`
pointer-events: none;
+ color: ${({ disabled }) => (disabled ? 'var(--echoes-color-text-disabled)' : 'inherit')};
`;
const FieldWrapper = styled.div`
mockRuleActivation,
mockRuleRepository,
} from '../../helpers/testMocks';
+import { SoftwareImpactSeverity, SoftwareQuality } from '../../types/clean-code-taxonomy';
import { RuleRepository, SearchRulesResponse } from '../../types/coding-rules';
-import { RawIssuesResponse } from '../../types/issues';
+import { IssueSeverity, RawIssuesResponse } from '../../types/issues';
import { RuleStatus, SearchRulesQuery } from '../../types/rules';
import { SecurityStandard } from '../../types/security';
import {
tags: 'tags',
};
+const MQRtoStandardSeverityMap = {
+ [SoftwareImpactSeverity.Info]: IssueSeverity.Info,
+ [SoftwareImpactSeverity.Low]: IssueSeverity.Minor,
+ [SoftwareImpactSeverity.Medium]: IssueSeverity.Major,
+ [SoftwareImpactSeverity.High]: IssueSeverity.Critical,
+ [SoftwareImpactSeverity.Blocker]: IssueSeverity.Blocker,
+};
+
+const StandardtoMQRSeverityMap = {
+ [IssueSeverity.Info]: SoftwareImpactSeverity.Info,
+ [IssueSeverity.Minor]: SoftwareImpactSeverity.Low,
+ [IssueSeverity.Major]: SoftwareImpactSeverity.Medium,
+ [IssueSeverity.Critical]: SoftwareImpactSeverity.High,
+ [IssueSeverity.Blocker]: SoftwareImpactSeverity.Blocker,
+};
+
export const RULE_TAGS_MOCK = ['awesome', 'cute', 'nice'];
export default class CodingRulesServiceMock {
activation.inherit = 'INHERITED';
activation.prioritizedRule = parentActivation?.prioritizedRule ?? false;
activation.severity = parentActivation?.severity ?? 'MAJOR';
+ activation.impacts = parentActivation?.impacts ?? [
+ {
+ softwareQuality: SoftwareQuality.Maintainability,
+ severity: SoftwareImpactSeverity.Medium,
+ },
+ ];
activation.params = parentParams;
return this.reply(undefined);
currentActivation.params,
Object.entries(data.params ?? {}).map(([key, value]) => ({ key, value })),
) &&
- currentActivation.severity === data.severity &&
- currentActivation.prioritizedRule === data.prioritizedRule
+ (!data.severity || currentActivation.severity === data.severity) &&
+ currentActivation.prioritizedRule === data.prioritizedRule &&
+ (!data.softwareQualityImpact ||
+ isEqual(
+ currentActivation.impacts,
+ Object.entries(data.softwareQualityImpact).map(([softwareQuality, severity]) => ({
+ softwareQuality,
+ severity,
+ })),
+ ))
) {
return this.reply(undefined);
}
+ const ruleImpacts = this.rules.find((r) => r.key === data.rule)?.impacts ?? [];
+ const inheritedImpacts =
+ this.rulesActivations[data.rule]?.find(({ qProfile }) => qProfile === data.key)?.impacts ??
+ [];
+ const severity = data.softwareQualityImpact
+ ? MQRtoStandardSeverityMap[data.softwareQualityImpact[SoftwareQuality.Maintainability]]
+ : data.severity;
+ const impacts = data.severity
+ ? [
+ ...ruleImpacts.filter(
+ (impact) => !inheritedImpacts.some((i) => i.softwareQuality === impact.softwareQuality),
+ ),
+ ...inheritedImpacts.filter(
+ (impact) => impact.softwareQuality !== SoftwareQuality.Maintainability,
+ ),
+ {
+ softwareQuality: SoftwareQuality.Maintainability,
+ severity:
+ StandardtoMQRSeverityMap[data.severity as keyof typeof StandardtoMQRSeverityMap],
+ },
+ ]
+ : Object.entries(data.softwareQualityImpact ?? {}).map(
+ ([softwareQuality, severity]: [SoftwareQuality, SoftwareImpactSeverity]) => ({
+ softwareQuality,
+ severity,
+ }),
+ );
+
const nextActivations = [
mockRuleActivation({
qProfile: data.key,
- severity: data.severity,
+ severity,
+ impacts,
prioritizedRule: data.prioritizedRule,
params: Object.entries(data.params ?? {}).map(([key, value]) => ({ key, value })),
}),
...inheritingProfiles.map((profile) =>
mockRuleActivation({
qProfile: profile.key,
- severity: data.severity,
+ severity,
+ impacts,
prioritizedRule: data.prioritizedRule,
inherit: 'INHERITED',
params: Object.entries(data.params ?? {}).map(([key, value]) => ({ key, value })),
{ key: '1', type: 'TEXT', htmlDesc: 'html description for key 1' },
{ key: '2', type: 'NUMBER', defaultValue: 'default value for key 2' },
],
+ impacts: [
+ {
+ softwareQuality: SoftwareQuality.Maintainability,
+ severity: SoftwareImpactSeverity.Medium,
+ },
+ ],
}),
mockRuleDetails({
key: RULE_2,
content: resourceContent,
},
],
+ impacts: [
+ {
+ softwareQuality: SoftwareQuality.Maintainability,
+ severity: SoftwareImpactSeverity.Medium,
+ },
+ ],
}),
mockRuleDetails({
key: RULE_6,
name: 'Bad Python rule',
isExternal: true,
descriptionSections: undefined,
+ impacts: [
+ {
+ softwareQuality: SoftwareQuality.Maintainability,
+ severity: SoftwareImpactSeverity.Medium,
+ },
+ {
+ softwareQuality: SoftwareQuality.Security,
+ severity: SoftwareImpactSeverity.Low,
+ },
+ ],
}),
mockRuleDetails({
key: RULE_7,
content: resourceContent,
},
],
+ impacts: [
+ {
+ softwareQuality: SoftwareQuality.Maintainability,
+ severity: SoftwareImpactSeverity.Low,
+ },
+ ],
}),
mockRuleDetails({
key: RULE_8,
key: RULE_9,
type: 'BUG',
severity: 'MINOR',
+ impacts: [
+ { softwareQuality: SoftwareQuality.Reliability, severity: SoftwareImpactSeverity.Low },
+ ],
lang: 'py',
langName: 'Python',
tags: ['awesome', 'cute'],
content: resourceContent,
},
],
+ impacts: [
+ {
+ softwareQuality: SoftwareQuality.Maintainability,
+ severity: SoftwareImpactSeverity.Low,
+ },
+ {
+ softwareQuality: SoftwareQuality.Reliability,
+ severity: SoftwareImpactSeverity.High,
+ },
+ ],
educationPrinciples: ['defense_in_depth', 'never_trust_user_input'],
}),
mockRuleDetails({
export function mockRulesActivationsInQP() {
return {
[RULE_1]: [mockRuleActivation({ qProfile: QP_1 }), mockRuleActivation({ qProfile: QP_6 })],
- [RULE_7]: [mockRuleActivation({ qProfile: QP_2 })],
+ [RULE_7]: [
+ mockRuleActivation({
+ qProfile: QP_2,
+ impacts: [
+ {
+ softwareQuality: SoftwareQuality.Maintainability,
+ severity: SoftwareImpactSeverity.Medium,
+ },
+ { softwareQuality: SoftwareQuality.Reliability, severity: SoftwareImpactSeverity.High },
+ { softwareQuality: SoftwareQuality.Security, severity: SoftwareImpactSeverity.High },
+ ],
+ }),
+ ],
[RULE_8]: [mockRuleActivation({ qProfile: QP_2 })],
[RULE_9]: [
mockRuleActivation({
{ key: '2', value: 'default value for key 2' },
],
inherit: 'INHERITED',
+ impacts: [
+ { softwareQuality: SoftwareQuality.Reliability, severity: SoftwareImpactSeverity.Medium },
+ ],
}),
],
[RULE_10]: [
- mockRuleActivation({ qProfile: QP_2, inherit: 'OVERRIDES', prioritizedRule: true }),
- mockRuleActivation({ qProfile: QP_2_Parent, severity: 'MINOR' }),
+ mockRuleActivation({
+ qProfile: QP_2,
+ inherit: 'OVERRIDES',
+ impacts: [
+ {
+ softwareQuality: SoftwareQuality.Maintainability,
+ severity: SoftwareImpactSeverity.Medium,
+ },
+ {
+ softwareQuality: SoftwareQuality.Reliability,
+ severity: SoftwareImpactSeverity.Info,
+ },
+ ],
+ prioritizedRule: true,
+ }),
+ mockRuleActivation({
+ qProfile: QP_2_Parent,
+ severity: 'MINOR',
+ impacts: [
+ {
+ softwareQuality: SoftwareQuality.Maintainability,
+ severity: SoftwareImpactSeverity.Low,
+ },
+ {
+ softwareQuality: SoftwareQuality.Reliability,
+ severity: SoftwareImpactSeverity.Blocker,
+ },
+ ],
+ }),
],
[RULE_11]: [
mockRuleActivation({ qProfile: QP_4 }),
import { Exporter, ProfileChangelogEvent } from '../apps/quality-profiles/types';
import { csvEscape } from '../helpers/csv';
import { RequestData, post, postJSON } from '../helpers/request';
-import { CleanCodeAttributeCategory, SoftwareImpact } from '../types/clean-code-taxonomy';
+import {
+ CleanCodeAttributeCategory,
+ SoftwareImpact,
+ SoftwareImpactSeverity,
+ SoftwareQuality,
+} from '../types/clean-code-taxonomy';
import { Dict, Paging, ProfileInheritanceDetails, UserSelected } from '../types/types';
export interface ProfileActions {
export interface ActivateRuleParameters {
key: string;
- params?: Dict<string>;
+ params?: Record<string, string>;
prioritizedRule?: boolean;
reset?: boolean;
rule: string;
severity?: string;
+ softwareQualityImpact?: Record<SoftwareQuality, SoftwareImpactSeverity>;
}
export function activateRule(data: ActivateRuleParameters) {
const params =
data.params && map(data.params, (value, key) => `${key}=${csvEscape(value)}`).join(';');
- return post('/api/qualityprofiles/activate_rule', { ...data, params }).catch(throwGlobalError);
+ const softwareQualityImpact =
+ data.softwareQualityImpact &&
+ map(data.softwareQualityImpact, (value, key) => `${key}=${value}`).join(';');
+ return post('/api/qualityprofiles/activate_rule', {
+ ...data,
+ params,
+ softwareQualityImpact,
+ }).catch(throwGlobalError);
}
export interface DeactivateRuleParameters {
import { fireEvent, screen, within } from '@testing-library/react';
import CodingRulesServiceMock, { RULE_TAGS_MOCK } from '../../../api/mocks/CodingRulesServiceMock';
import SettingsServiceMock from '../../../api/mocks/SettingsServiceMock';
-import { QP_2, RULE_1, RULE_10, RULE_9 } from '../../../api/mocks/data/ids';
+import { QP_2, RULE_1, RULE_10, RULE_7, RULE_9 } from '../../../api/mocks/data/ids';
import {
IMPACT_SEVERITIES,
ISSUE_TYPES,
// Filter by software quality
await user.click(ui.facetItem('software_quality.MAINTAINABILITY').get());
- expect(ui.getAllRuleListItems()).toHaveLength(9);
+ expect(ui.getAllRuleListItems()).toHaveLength(8);
// Filter by severity
await user.click(ui.facetItem(/severity_impact.HIGH/).get());
- expect(ui.getAllRuleListItems()).toHaveLength(8);
+ expect(ui.getAllRuleListItems()).toHaveLength(4);
});
it('filter by type and severity in standard mode', async () => {
});
});
- it('can activate/change/deactivate specific rule for quality profile', async () => {
- const { ui, user } = getPageObjects();
- rulesHandler.setIsAdmin();
- renderCodingRulesApp(mockLoggedInUser(), undefined, [Feature.PrioritizedRules]);
- await ui.appLoaded();
+ describe('old severity', () => {
+ it('can activate/change/deactivate specific rule for quality profile', async () => {
+ const { ui, user } = getPageObjects();
+ rulesHandler.setIsAdmin();
+ renderCodingRulesApp(mockLoggedInUser(), undefined, [Feature.PrioritizedRules]);
+ await ui.appLoaded();
- await user.click(ui.qpFacet.get());
- await user.click(ui.facetItem('QP Bar Python').get());
+ 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);
- // Only 4 rules are activated in selected QP
- expect(ui.getAllRuleListItems()).toHaveLength(4);
+ // Switch to inactive rules
+ 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();
- // Switch to inactive rules
- 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.oldSeveritySelect.get(ui.activateQPDialog.get())).toHaveValue(
+ 'coding_rules.custom_severity.severity_with_recommended.severity.MAJOR',
+ );
+ expect(ui.prioritizedSwitch.get(ui.activateQPDialog.get())).not.toBeChecked();
+ await user.click(ui.oldSeveritySelect.get());
+ await user.click(byRole('option', { name: 'severity.MINOR' }).get());
+ expect(ui.notRecommendedSeverity.get()).toBeInTheDocument();
+ expect(ui.notRecommendedSeverity.get()).toHaveTextContent('severity.MAJOR');
+
+ await user.click(ui.prioritizedSwitch.get(ui.activateQPDialog.get()));
+ 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.oldSeveritySelect.get(ui.changeQPDialog.get())).toHaveValue('severity.MINOR');
+ expect(ui.notRecommendedSeverity.get()).toBeInTheDocument();
+ expect(ui.notRecommendedSeverity.get()).toHaveTextContent('severity.MAJOR');
+ expect(ui.prioritizedSwitch.get(ui.changeQPDialog.get())).toBeChecked();
+ await user.click(ui.oldSeveritySelect.get());
+ await user.click(byRole('option', { name: 'severity.BLOCKER' }).get());
+ await user.click(ui.prioritizedSwitch.get(ui.changeQPDialog.get()));
+ expect(ui.notRecommendedSeverity.get()).toBeInTheDocument();
+ expect(ui.notRecommendedSeverity.get()).toHaveTextContent('severity.MAJOR');
+ 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.oldSeveritySelect.get(ui.changeQPDialog.get())).toHaveValue('severity.BLOCKER');
+ expect(ui.prioritizedSwitch.get(ui.changeQPDialog.get())).not.toBeChecked();
+ expect(ui.notRecommendedSeverity.get()).toBeInTheDocument();
+ expect(ui.notRecommendedSeverity.get()).toHaveTextContent('severity.MAJOR');
+ await user.click(ui.cancelButton.get(ui.changeQPDialog.get()));
+
+ // Deactivate activated rule
+ await user.click(ui.deactivateButton.get());
+ await user.click(ui.yesButton.get());
+ expect(ui.deactivateButton.query()).not.toBeInTheDocument();
+ expect(ui.activateButton.getAll()).toHaveLength(2);
+ });
- // Activate Rule for qp
- await user.click(ui.activateButton.getAll()[0]);
- expect(ui.selectValue.get(ui.activateQPDialog.get())).toHaveTextContent('severity.MAJOR');
- expect(ui.prioritizedSwitch.get(ui.activateQPDialog.get())).not.toBeChecked();
- await user.click(ui.oldSeveritySelect.get());
- await user.click(byRole('option', { name: 'severity.MINOR' }).get());
+ 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(), undefined, [Feature.PrioritizedRules]);
+ await ui.appLoaded();
- await user.click(ui.prioritizedSwitch.get(ui.activateQPDialog.get()));
- await user.click(ui.activateButton.get(ui.activateQPDialog.get()));
+ await user.click(ui.qpFacet.get());
+ await user.click(ui.facetItem('QP Bar Python').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');
- expect(ui.prioritizedSwitch.get(ui.changeQPDialog.get())).toBeChecked();
- await user.click(ui.oldSeveritySelect.get());
- await user.click(byRole('option', { name: 'severity.BLOCKER' }).get());
- await user.click(ui.prioritizedSwitch.get(ui.changeQPDialog.get()));
- await user.click(ui.saveButton.get(ui.changeQPDialog.get()));
+ // Only 4 rules are activated in selected QP
+ expect(ui.getAllRuleListItems()).toHaveLength(4);
- // Check that new severity is saved
- await user.click(ui.changeButton('QP Bar').get());
- expect(ui.selectValue.get(ui.changeQPDialog.get())).toHaveTextContent('severity.BLOCKER');
- expect(ui.prioritizedSwitch.get(ui.changeQPDialog.get())).not.toBeChecked();
- await user.click(ui.cancelButton.get(ui.changeQPDialog.get()));
+ // 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();
- // Deactivate activated rule
- await user.click(ui.deactivateButton.get());
- await user.click(ui.yesButton.get());
- expect(ui.deactivateButton.query()).not.toBeInTheDocument();
- expect(ui.activateButton.getAll()).toHaveLength(2);
+ 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.oldSeveritySelect.get(ui.changeQPDialog.get())).toHaveValue('severity.MAJOR');
+ expect(ui.notRecommendedSeverity.get()).toBeInTheDocument();
+ expect(ui.notRecommendedSeverity.get()).toHaveTextContent('severity.MINOR');
+ expect(ui.prioritizedSwitch.get(ui.changeQPDialog.get())).toBeChecked();
+ 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.oldSeveritySelect.get(ui.changeQPDialog.get())).toHaveValue(
+ 'coding_rules.custom_severity.severity_with_recommended.severity.MINOR',
+ );
+ expect(ui.notRecommendedSeverity.query()).not.toBeInTheDocument();
+ expect(ui.prioritizedSwitch.get(ui.changeQPDialog.get())).not.toBeChecked();
+ await user.click(ui.cancelButton.get(ui.changeQPDialog.get()));
+ });
+
+ it('should not make rule overriden if no changes were done', async () => {
+ const { ui, user } = getPageObjects();
+ settingsHandler.set(SettingsKey.QPAdminCanDisableInheritedRules, 'false');
+ rulesHandler.setIsAdmin();
+ renderCodingRulesApp(mockLoggedInUser(), undefined, [Feature.PrioritizedRules]);
+ await ui.appLoaded();
+
+ await user.click(ui.qpFacet.get());
+ await user.click(ui.facetItem('QP Bar Python').get());
+
+ // filter out everything except INHERITED rule
+ await user.type(ui.searchInput.get(), RULE_9);
+
+ // Only 1 rule left after search
+ expect(ui.getAllRuleListItems()).toHaveLength(1);
+ 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.oldSeveritySelect.get(ui.changeQPDialog.get())).toHaveValue('severity.MAJOR');
+ expect(ui.notRecommendedSeverity.get()).toBeInTheDocument();
+ expect(ui.notRecommendedSeverity.get()).toHaveTextContent('severity.MINOR');
+ expect(ui.prioritizedSwitch.get(ui.changeQPDialog.get())).not.toBeChecked();
+ await user.click(ui.saveButton.get(ui.changeQPDialog.get()));
+
+ expect(ui.revertToParentDefinitionButton.query()).not.toBeInTheDocument();
+ });
});
- 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(), undefined, [Feature.PrioritizedRules]);
- await ui.appLoaded();
+ describe('new severity', () => {
+ it('can activate/change specific rule with multiple impacts for quality profile', async () => {
+ const { ui, user } = getPageObjects();
+ rulesHandler.setIsAdmin();
+ renderCodingRulesApp(mockLoggedInUser(), undefined, []);
+ await ui.appLoaded();
- await user.click(ui.qpFacet.get());
- await user.click(ui.facetItem('QP Bar Python').get());
+ await user.click(ui.qpFacet.get());
+ await user.click(ui.facetItem('QP Bar Python').get());
+ await user.click(ui.qpInactiveRadio.get(ui.facetItem('QP Bar Python').get()));
+
+ // Activate Rule for qp
+ await user.click(ui.activateButton.getAll()[1]);
- // Only 4 rules are activated in selected QP
- expect(ui.getAllRuleListItems()).toHaveLength(4);
+ await user.click(ui.mqrSwitch.get(ui.activateQPDialog.get()));
- // 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();
+ expect(ui.newSeveritySelect(SoftwareQuality.Maintainability).get()).toHaveValue(
+ 'coding_rules.custom_severity.severity_with_recommended.severity_impact.MEDIUM',
+ );
+ expect(ui.newSeveritySelect(SoftwareQuality.Security).get()).toHaveValue(
+ 'coding_rules.custom_severity.severity_with_recommended.severity_impact.LOW',
+ );
+ expect(ui.newSeveritySelect(SoftwareQuality.Reliability).get()).toBeDisabled();
+ await user.click(ui.newSeveritySelect(SoftwareQuality.Maintainability).get());
+ await user.click(byRole('option', { name: 'severity_impact.LOW' }).get());
+ await user.click(ui.newSeveritySelect(SoftwareQuality.Security).get());
+ await user.click(byRole('option', { name: 'severity_impact.MEDIUM' }).get());
+ expect(ui.notRecommendedSeverity.getAll()).toHaveLength(2);
+ expect(ui.notRecommendedSeverity.getAt(0)).toHaveTextContent('severity_impact.LOW');
+ expect(ui.notRecommendedSeverity.getAt(1)).toHaveTextContent('severity_impact.MEDIUM');
+
+ 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);
+
+ await user.click(ui.changeButton('QP Bar').get());
+ expect(ui.oldSeveritySelect.get(ui.changeQPDialog.get())).toHaveValue('severity.MINOR');
+ expect(ui.notRecommendedSeverity.get()).toBeInTheDocument();
+ expect(ui.notRecommendedSeverity.get()).toHaveTextContent('severity.MAJOR');
+ await user.click(ui.mqrSwitch.get());
+
+ expect(ui.newSeveritySelect(SoftwareQuality.Maintainability).get()).toHaveValue(
+ 'severity_impact.LOW',
+ );
+ expect(ui.newSeveritySelect(SoftwareQuality.Security).get()).toHaveValue(
+ 'severity_impact.MEDIUM',
+ );
+ expect(ui.newSeveritySelect(SoftwareQuality.Reliability).get()).toBeDisabled();
+ expect(ui.notRecommendedSeverity.getAll()).toHaveLength(2);
+ expect(ui.notRecommendedSeverity.getAt(0)).toHaveTextContent('severity_impact.LOW');
+ expect(ui.notRecommendedSeverity.getAt(1)).toHaveTextContent('severity_impact.MEDIUM');
+ await user.click(ui.newSeveritySelect(SoftwareQuality.Security).get());
+ await user.click(byRole('option', { name: 'severity_impact.BLOCKER' }).get());
+ expect(ui.notRecommendedSeverity.getAll()).toHaveLength(2);
+ expect(ui.notRecommendedSeverity.getAt(0)).toHaveTextContent('severity_impact.LOW');
+ expect(ui.notRecommendedSeverity.getAt(1)).toHaveTextContent('severity_impact.MEDIUM');
+ await user.click(ui.saveButton.get(ui.changeQPDialog.get()));
+
+ // Check that new severity is saved
+ await user.click(ui.changeButton('QP Bar').get());
+ await user.click(ui.mqrSwitch.get());
+ expect(ui.oldSeveritySelect.get(ui.changeQPDialog.get())).toHaveValue('severity.MINOR');
+ expect(ui.notRecommendedSeverity.get()).toBeInTheDocument();
+ expect(ui.notRecommendedSeverity.get()).toHaveTextContent('severity.MAJOR');
+ await user.click(ui.mqrSwitch.get());
+
+ expect(ui.newSeveritySelect(SoftwareQuality.Maintainability).get()).toHaveValue(
+ 'severity_impact.LOW',
+ );
+ expect(ui.newSeveritySelect(SoftwareQuality.Security).get()).toHaveValue(
+ 'severity_impact.BLOCKER',
+ );
+ expect(ui.newSeveritySelect(SoftwareQuality.Reliability).get()).toBeDisabled();
- await user.type(ui.searchInput.get(), RULE_10);
+ await user.click(ui.cancelButton.get(ui.changeQPDialog.get()));
+ });
- // Only 1 rule left after search
- expect(ui.getAllRuleListItems()).toHaveLength(1);
- expect(ui.revertToParentDefinitionButton.get()).toBeInTheDocument();
- expect(ui.changeButton('QP Bar').get()).toBeInTheDocument();
+ 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(), undefined, [Feature.PrioritizedRules]);
+ await ui.appLoaded();
- // Check that severity is reflected correctly
- await user.click(ui.changeButton('QP Bar').get());
- expect(ui.selectValue.get(ui.changeQPDialog.get())).toHaveTextContent('severity.MAJOR');
- expect(ui.prioritizedSwitch.get(ui.changeQPDialog.get())).toBeChecked();
- await user.click(ui.cancelButton.get(ui.changeQPDialog.get()));
+ await user.click(ui.qpFacet.get());
+ await user.click(ui.facetItem('QP Bar Python').get());
- await user.click(ui.revertToParentDefinitionButton.get());
- await user.click(ui.yesButton.get());
+ // Only 4 rules are activated in selected QP
+ expect(ui.getAllRuleListItems()).toHaveLength(4);
- 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();
+ // 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();
- // Check that severity is reflected correctly
- await user.click(ui.changeButton('QP Bar').get());
- expect(ui.selectValue.get(ui.changeQPDialog.get())).toHaveTextContent('severity.MINOR');
- expect(ui.prioritizedSwitch.get(ui.changeQPDialog.get())).not.toBeChecked();
- await user.click(ui.cancelButton.get(ui.changeQPDialog.get()));
- });
+ await user.type(ui.searchInput.get(), RULE_10);
- it('should not make rule overriden if no changes were done', async () => {
- const { ui, user } = getPageObjects();
- settingsHandler.set(SettingsKey.QPAdminCanDisableInheritedRules, 'false');
- rulesHandler.setIsAdmin();
- renderCodingRulesApp(mockLoggedInUser(), undefined, [Feature.PrioritizedRules]);
- await ui.appLoaded();
+ // 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());
+ await user.click(ui.mqrSwitch.get(ui.changeQPDialog.get()));
+ expect(ui.newSeveritySelect(SoftwareQuality.Maintainability).get()).toHaveValue(
+ 'severity_impact.MEDIUM',
+ );
+ expect(ui.newSeveritySelect(SoftwareQuality.Reliability).get()).toHaveValue(
+ 'severity_impact.INFO',
+ );
+ expect(ui.newSeveritySelect(SoftwareQuality.Security).get()).toBeDisabled();
+ expect(ui.notRecommendedSeverity.getAll()).toHaveLength(2);
+ expect(ui.notRecommendedSeverity.getAt(0)).toHaveTextContent('severity_impact.HIGH');
+ expect(ui.notRecommendedSeverity.getAt(1)).toHaveTextContent('severity_impact.LOW');
+ expect(ui.prioritizedSwitch.get(ui.changeQPDialog.get())).toBeChecked();
+ await user.click(ui.cancelButton.get(ui.changeQPDialog.get()));
- await user.click(ui.qpFacet.get());
- await user.click(ui.facetItem('QP Bar Python').get());
+ await user.click(ui.revertToParentDefinitionButton.get());
+ await user.click(ui.yesButton.get());
- // filter out everything except INHERITED rule
- await user.type(ui.searchInput.get(), RULE_9);
+ 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.newSeveritySelect(SoftwareQuality.Maintainability).get()).toHaveValue(
+ 'coding_rules.custom_severity.severity_with_recommended.severity_impact.LOW',
+ );
+ expect(ui.newSeveritySelect(SoftwareQuality.Reliability).get()).toHaveValue(
+ 'severity_impact.BLOCKER',
+ );
+ expect(ui.newSeveritySelect(SoftwareQuality.Security).get()).toBeDisabled();
+ expect(ui.notRecommendedSeverity.getAll()).toHaveLength(1);
+ expect(ui.notRecommendedSeverity.getAt(0)).toHaveTextContent('severity_impact.HIGH');
+ expect(ui.prioritizedSwitch.get(ui.changeQPDialog.get())).not.toBeChecked();
+ await user.click(ui.cancelButton.get(ui.changeQPDialog.get()));
+ });
- // Only 1 rule left after search
- expect(ui.getAllRuleListItems()).toHaveLength(1);
- expect(ui.deactivateButton.get()).toBeInTheDocument();
- expect(ui.deactivateButton.get()).toBeDisabled();
- expect(ui.changeButton('QP Bar').get()).toBeInTheDocument();
+ it('should not make rule overriden if no changes were done', async () => {
+ const { ui, user } = getPageObjects();
+ settingsHandler.set(SettingsKey.QPAdminCanDisableInheritedRules, 'false');
+ rulesHandler.setIsAdmin();
+ renderCodingRulesApp(mockLoggedInUser(), undefined, []);
+ await ui.appLoaded();
- // Check that severity is reflected correctly
- await user.click(ui.changeButton('QP Bar').get());
- expect(ui.selectValue.get(ui.changeQPDialog.get())).toHaveTextContent('severity.MAJOR');
- expect(ui.prioritizedSwitch.get(ui.changeQPDialog.get())).not.toBeChecked();
- await user.click(ui.saveButton.get(ui.changeQPDialog.get()));
+ await user.click(ui.qpFacet.get());
+ await user.click(ui.facetItem('QP Bar Python').get());
+
+ // filter out everything except INHERITED rule
+ await user.type(ui.searchInput.get(), RULE_9);
+ expect(ui.changeButton('QP Bar').get()).toBeInTheDocument();
+
+ // Check that severity is reflected correctly
+ await user.click(ui.changeButton('QP Bar').get());
+ await user.click(ui.mqrSwitch.get(ui.changeQPDialog.get()));
+ expect(ui.newSeveritySelect(SoftwareQuality.Reliability).get()).toHaveValue(
+ 'severity_impact.MEDIUM',
+ );
+ expect(ui.newSeveritySelect(SoftwareQuality.Security).get()).toBeDisabled();
+ expect(ui.newSeveritySelect(SoftwareQuality.Maintainability).get()).toBeDisabled();
+ expect(ui.notRecommendedSeverity.get()).toBeInTheDocument();
+ expect(ui.notRecommendedSeverity.get()).toHaveTextContent('severity_impact.LOW');
+ await user.click(ui.saveButton.get(ui.changeQPDialog.get()));
+
+ expect(ui.revertToParentDefinitionButton.query()).not.toBeInTheDocument();
+ });
- expect(ui.revertToParentDefinitionButton.query()).not.toBeInTheDocument();
+ it('should ignore excessive activation impacts', async () => {
+ const { ui, user } = getPageObjects();
+ settingsHandler.set(SettingsKey.QPAdminCanDisableInheritedRules, 'false');
+ rulesHandler.setIsAdmin();
+ renderCodingRulesApp(mockLoggedInUser(), undefined, []);
+ await ui.appLoaded();
+
+ await user.click(ui.qpFacet.get());
+ await user.click(ui.facetItem('QP Bar Python').get());
+
+ await user.type(ui.searchInput.get(), RULE_7);
+ expect(ui.changeButton('QP Bar').get()).toBeInTheDocument();
+
+ await user.click(ui.changeButton('QP Bar').get());
+ await user.click(ui.mqrSwitch.get(ui.changeQPDialog.get()));
+ expect(ui.newSeveritySelect(SoftwareQuality.Maintainability).get()).toHaveValue(
+ 'severity_impact.MEDIUM',
+ );
+ expect(ui.newSeveritySelect(SoftwareQuality.Security).get()).toBeDisabled();
+ expect(ui.newSeveritySelect(SoftwareQuality.Reliability).get()).toBeDisabled();
+ expect(ui.notRecommendedSeverity.get()).toBeInTheDocument();
+ expect(ui.notRecommendedSeverity.get()).toHaveTextContent('severity_impact.LOW');
+ await user.click(ui.newSeveritySelect(SoftwareQuality.Maintainability).get());
+ await user.click(
+ byRole('option', {
+ name: 'coding_rules.custom_severity.severity_with_recommended.severity_impact.LOW',
+ }).get(),
+ );
+ await user.click(ui.saveButton.get(ui.changeQPDialog.get()));
+
+ await user.click(ui.changeButton('QP Bar').get());
+ expect(ui.newSeveritySelect(SoftwareQuality.Maintainability).get()).toHaveValue(
+ 'coding_rules.custom_severity.severity_with_recommended.severity_impact.LOW',
+ );
+ expect(ui.newSeveritySelect(SoftwareQuality.Security).get()).toBeDisabled();
+ expect(ui.newSeveritySelect(SoftwareQuality.Reliability).get()).toBeDisabled();
+ expect(ui.notRecommendedSeverity.query()).not.toBeInTheDocument();
+ });
});
it('should not show prioritized rule switcher if feature is not enabled', async () => {
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { Button, ButtonVariety, Modal } from '@sonarsource/echoes-react';
+import { Button, ButtonVariety, Checkbox, Modal, Text } from '@sonarsource/echoes-react';
import {
FlagMessage,
FormField,
Switch,
} from 'design-system';
import * as React from 'react';
-import { useIntl } from 'react-intl';
+import { FormattedMessage, useIntl } from 'react-intl';
import { Profile } from '../../../api/quality-profiles';
import { useAvailableFeatures } from '../../../app/components/available-features/withAvailableFeatures';
import DocumentationLink from '../../../components/common/DocumentationLink';
import { DocLink } from '../../../helpers/doc-links';
import { useActivateRuleMutation } from '../../../queries/quality-profiles';
+import { SoftwareImpactSeverity, SoftwareQuality } from '../../../types/clean-code-taxonomy';
import { Feature } from '../../../types/features';
import { IssueSeverity } from '../../../types/issues';
-import { Dict, Rule, RuleActivation, RuleDetails } from '../../../types/types';
+import { Rule, RuleActivation, RuleDetails } from '../../../types/types';
import { sortProfiles } from '../../quality-profiles/utils';
import { SeveritySelect } from './SeveritySelect';
const [changedSeverity, setChangedSeverity] = React.useState<IssueSeverity | undefined>(
undefined,
);
+ const [changedImpactSeveritiesMap, setChangedImpactSeverities] = React.useState<
+ Map<SoftwareQuality, SoftwareImpactSeverity>
+ >(new Map());
+ const [isMQRMode, setIsMQRMode] = React.useState<boolean>(false);
const profilesWithDepth = React.useMemo(() => {
return getQualityProfilesWithDepth(profiles, rule.lang);
const params = changedParams ?? getRuleParams({ activation, rule });
const severity =
changedSeverity ?? ((activation ? activation.severity : rule.severity) as IssueSeverity);
+ const impacts = new Map<SoftwareQuality, SoftwareImpactSeverity>([
+ ...rule.impacts.map((impact) => [impact.softwareQuality, impact.severity] as const),
+ ...(activation?.impacts
+ ?.filter((impact) => rule.impacts.some((i) => i.softwareQuality === impact.softwareQuality))
+ .map((impact) => [impact.softwareQuality, impact.severity] as const) ?? []),
+ ...changedImpactSeveritiesMap,
+ ]);
const profileOptions = profilesWithDepth.map((p) => ({ label: p.name, value: p }));
const isCustomRule = !!(rule as RuleDetails).templateKey;
const activeInAllProfiles = profilesWithDepth.length <= 0;
setChangedProfile(undefined);
setChangedParams(undefined);
setChangedSeverity(undefined);
+ setChangedImpactSeverities(new Map());
}
}, [isOpen]);
key: profile?.key ?? '',
params,
rule: rule.key,
- severity,
+ severity: !isMQRMode ? severity : undefined,
prioritizedRule,
+ softwareQualityImpact: isMQRMode
+ ? (Object.fromEntries(impacts) as Record<SoftwareQuality, SoftwareImpactSeverity>)
+ : undefined,
};
activateRule(data);
};
</div>
}
>
- <label
+ <Checkbox
+ onCheck={(checked) => setChangedPrioritizedRule(!!checked)}
+ label={intl.formatMessage({ id: 'coding_rules.prioritized_rule.switch_label' })}
id="coding-rules-prioritized-rule"
- className="sw-flex sw-items-center sw-gap-2"
- >
- <Switch
- onChange={setChangedPrioritizedRule}
- name={intl.formatMessage({ id: 'coding_rules.prioritized_rule.title' })}
- value={prioritizedRule}
- />
- <span className="sw-text-xs">
- {intl.formatMessage({ id: 'coding_rules.prioritized_rule.switch_label' })}
- </span>
- </label>
+ checked={prioritizedRule}
+ />
</FormField>
)}
- <FormField
- ariaLabel={intl.formatMessage({ id: 'severity_deprecated' })}
- label={intl.formatMessage({ id: 'severity_deprecated' })}
- htmlFor="coding-rules-severity-select"
- >
- <SeveritySelect
- isDisabled={submitting}
- onChange={({ value }: LabelValueSelectOption<IssueSeverity>) => {
- setChangedSeverity(value);
- }}
- severity={severity}
- />
- <FlagMessage className="sw-mb-4 sw-mt-2" variant="info">
- <div>
- {intl.formatMessage({ id: 'coding_rules.severity_deprecated' })}
- <DocumentationLink
- className="sw-ml-2 sw-whitespace-nowrap"
- to={DocLink.CleanCodeIntroduction}
- >
- {intl.formatMessage({ id: 'learn_more' })}
- </DocumentationLink>
- </div>
- </FlagMessage>
+ <FormField label="MQR Mode">
+ <Switch value={isMQRMode} onChange={setIsMQRMode} />
</FormField>
+ {!isMQRMode && (
+ <>
+ <FormField label={intl.formatMessage({ id: 'coding_rules.custom_severity.title' })}>
+ <Text>
+ <FormattedMessage
+ id="coding_rules.custom_severity.description.standard"
+ values={{
+ link: (
+ <DocumentationLink to={DocLink.RuleSeverity}>
+ {intl.formatMessage({
+ id: 'coding_rules.custom_severity.description.standard.link',
+ })}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ </Text>
+ </FormField>
+
+ <FormField
+ ariaLabel={intl.formatMessage({
+ id: 'coding_rules.custom_severity.choose_severity',
+ })}
+ label={intl.formatMessage({ id: 'coding_rules.custom_severity.choose_severity' })}
+ htmlFor="coding-rules-custom-severity-select"
+ >
+ <SeveritySelect
+ id="coding-rules-custom-severity-select"
+ isDisabled={submitting}
+ recommendedSeverity={rule.severity}
+ onChange={(value: string) => {
+ setChangedSeverity(value as IssueSeverity);
+ }}
+ severity={severity}
+ />
+ </FormField>
+ </>
+ )}
+
+ {isMQRMode && (
+ <>
+ <FormField label={intl.formatMessage({ id: 'coding_rules.custom_severity.title' })}>
+ <Text>
+ <FormattedMessage
+ id="coding_rules.custom_severity.description.mqr"
+ values={{
+ link: (
+ <DocumentationLink to={DocLink.RuleSeverity}>
+ {intl.formatMessage({
+ id: 'coding_rules.custom_severity.description.mqr.link',
+ })}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ </Text>
+ </FormField>
+
+ {Object.values(SoftwareQuality).map((quality) => {
+ const impact = rule.impacts.find((impact) => impact.softwareQuality === quality);
+ const id = `coding-rules-custom-severity-${quality}-select`;
+ return (
+ <FormField
+ htmlFor={id}
+ key={quality}
+ disabled={!impact}
+ ariaLabel={intl.formatMessage({ id: `software_quality.${quality}` })}
+ label={intl.formatMessage({ id: `software_quality.${quality}` })}
+ >
+ <SeveritySelect
+ id={id}
+ impactSeverity
+ isDisabled={submitting || !impact}
+ recommendedSeverity={impact?.severity ?? ''}
+ onChange={(value: string) => {
+ setChangedImpactSeverities(
+ new Map(changedImpactSeveritiesMap).set(
+ quality,
+ value as SoftwareImpactSeverity,
+ ),
+ );
+ }}
+ severity={impacts.get(quality) ?? ''}
+ />
+ </FormField>
+ );
+ })}
+ </>
+ )}
+
{isCustomRule ? (
<Note as="p" className="sw-my-4">
{intl.formatMessage({ id: 'coding_rules.custom_rule.activation_notice' })}
activation?: RuleActivation;
rule: RuleDetails | Rule;
}) {
- const params: Dict<string> = {};
+ const params: Record<string, string> = {};
if (rule?.params) {
for (const param of rule.params) {
params[param.key] = param.defaultValue ?? '';
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { InputSelect, LabelValueSelectOption } from 'design-system';
+import { HelperText, Select } from '@sonarsource/echoes-react';
+import { isEmpty } from 'lodash';
import * as React from 'react';
-import { OptionProps, SingleValueProps, components } from 'react-select';
-import SeverityHelper from '../../../components/shared/SeverityHelper';
+import { FormattedMessage, useIntl } from 'react-intl';
+import SeverityIcon from '../../../components/icon-mappers/SeverityIcon';
+import SoftwareImpactSeverityIcon from '../../../components/icon-mappers/SoftwareImpactSeverityIcon';
import { SEVERITIES } from '../../../helpers/constants';
-import { translate } from '../../../helpers/l10n';
-import { IssueSeverity } from '../../../types/issues';
+import { SoftwareImpactSeverity } from '../../../types/clean-code-taxonomy';
export interface SeveritySelectProps {
+ id: string;
+ impactSeverity?: boolean;
isDisabled: boolean;
- onChange: (value: LabelValueSelectOption<IssueSeverity>) => void;
+ onChange: (value: string) => void;
+ recommendedSeverity: string;
severity: string;
}
-function Option(props: Readonly<OptionProps<LabelValueSelectOption<IssueSeverity>, false>>) {
- // For tests and a11y
- props.innerProps.role = 'option';
- props.innerProps['aria-selected'] = props.isSelected;
-
- return (
- <components.Option {...props}>
- <SeverityHelper className="sw-flex sw-items-center" severity={props.data.value} />
- </components.Option>
- );
-}
-
-function SingleValue(
- props: Readonly<SingleValueProps<LabelValueSelectOption<IssueSeverity>, false>>,
-) {
- return (
- <components.SingleValue {...props}>
- <SeverityHelper className="sw-flex sw-items-center" severity={props.data.value} />
- </components.SingleValue>
- );
-}
-
export function SeveritySelect(props: SeveritySelectProps) {
- const { isDisabled, severity } = props;
- const serverityOption = SEVERITIES.map((severity) => ({
- label: translate('severity', severity),
- value: severity,
- }));
+ const { isDisabled, severity, recommendedSeverity, impactSeverity, id } = props;
+ const intl = useIntl();
+ const Icon = impactSeverity ? SoftwareImpactSeverityIcon : SeverityIcon;
+ const getSeverityTranslation = (severity: string) =>
+ impactSeverity
+ ? intl.formatMessage({ id: `severity_impact.${severity}` })
+ : intl.formatMessage({ id: `severity.${severity}` });
+ const serverityOption = (impactSeverity ? Object.values(SoftwareImpactSeverity) : SEVERITIES).map(
+ (severity) => ({
+ label:
+ severity === recommendedSeverity
+ ? intl.formatMessage(
+ { id: 'coding_rules.custom_severity.severity_with_recommended' },
+ { severity: getSeverityTranslation(severity) },
+ )
+ : getSeverityTranslation(severity),
+ value: severity,
+ prefix: <Icon severity={severity} aria-hidden />,
+ }),
+ );
return (
- <InputSelect
- aria-label={translate('severity')}
- inputId="coding-rules-severity-select"
- isDisabled={isDisabled}
- onChange={props.onChange}
- components={{ Option, SingleValue }}
- options={serverityOption}
- isSearchable={false}
- value={serverityOption.find((s) => s.value === severity)}
- />
+ <>
+ <Select
+ id={id}
+ isDisabled={isDisabled}
+ onChange={props.onChange}
+ data={serverityOption}
+ isSearchable={false}
+ isNotClearable
+ placeholder={
+ isDisabled && !isEmpty(severity) ? intl.formatMessage({ id: 'not_impacted' }) : undefined
+ }
+ value={severity}
+ valueIcon={<Icon severity={severity} aria-hidden />}
+ />
+ {severity !== recommendedSeverity && (
+ <HelperText className="sw-mt-2">
+ <FormattedMessage
+ id="coding_rules.custom_severity.not_recommended"
+ values={{
+ recommended: (
+ <b className="sw-lowercase">{getSeverityTranslation(recommendedSeverity)}</b>
+ ),
+ }}
+ />
+ </HelperText>
+ )}
+ </>
);
}
name: /coding_rules.deactivate_in_quality_profile/,
hidden: true,
}),
- oldSeveritySelect: byLabelText('severity'),
qualityProfileSelect: byLabelText('coding_rules.quality_profile'),
- prioritizedSwitch: byRole('switch', { hidden: true }),
- selectValue: byText(/severity\./),
+ oldSeveritySelect: byRole('combobox', { name: 'coding_rules.custom_severity.choose_severity' }),
+ mqrSwitch: byRole('switch'),
+ newSeveritySelect: (quality: SoftwareQuality) =>
+ byRole('combobox', { name: `software_quality.${quality}` }),
+ notRecommendedSeverity: byText('coding_rules.custom_severity.not_recommended'),
+ prioritizedSwitch: byRole('checkbox', { name: 'coding_rules.prioritized_rule.switch_label' }),
activateQPDialog: byRole('dialog', { name: 'coding_rules.activate_in_quality_profile' }),
changeButton: (profile: string) =>
byRole('button', { name: `coding_rules.change_details_x.${profile}` }),
*/
import * as React from 'react';
import { useIntl } from 'react-intl';
+import SoftwareImpactSeverityIcon from '../../../components/icon-mappers/SoftwareImpactSeverityIcon';
import { ProfileChangelogEventImpactChange } from '../types';
interface Props {
const intl = useIntl();
const labels = {
- oldSeverity: intl.formatMessage({ id: `severity.${oldSeverity}` }),
+ oldSeverity: (
+ <>
+ <SoftwareImpactSeverityIcon severity={oldSeverity} />{' '}
+ {intl.formatMessage({ id: `severity_impact.${oldSeverity}` })}
+ </>
+ ),
oldSoftwareQuality: intl.formatMessage({ id: `software_quality.${oldSoftwareQuality}` }),
- newSeverity: intl.formatMessage({ id: `severity.${newSeverity}` }),
+ newSeverity: (
+ <>
+ <SoftwareImpactSeverityIcon severity={newSeverity} />{' '}
+ {intl.formatMessage({ id: `severity_impact.${newSeverity}` })}
+ </>
+ ),
newSoftwareQuality: intl.formatMessage({ id: `software_quality.${newSoftwareQuality}` }),
};
ui.checkRow(3, '', '', '', 'Rule 1', [
/quality_profiles.deprecated_severity_set_to severity.CRITICAL/,
/quality_profiles.changelog.cca_and_category_changed.*COMPLETE.*INTENTIONAL.*LAWFUL.*RESPONSIBLE/,
- /quality_profiles.changelog.impact_added.severity.*MEDIUM.*RELIABILITY/,
- /quality_profiles.changelog.impact_removed.severity.HIGH.*MAINTAINABILITY/,
+ /quality_profiles.changelog.impact_added.severity_impact.*MEDIUM.*RELIABILITY/,
+ /quality_profiles.changelog.impact_removed.severity_impact.HIGH.*MAINTAINABILITY/,
]);
await user.click(ui.link.get(rows[1]));
expect(screen.getByText('/coding_rules?rule_key=c%3Arule0')).toBeInTheDocument();
PullRequestAnalysis = '/analyzing-source-code/pull-request-analysis/introduction/',
QualityGates = '/instance-administration/analysis-functions/quality-gates/',
Root = '/',
+ RuleSeverity = '/instance-administration/analysis-functions/quality-profiles/#rule-severity',
RulesOverview = '/user-guide/rules/overview',
SecurityHotspots = '/user-guide/security-hotspots/',
SecurityReports = '/user-guide/viewing-reports/security-reports/',
qProfile: 'baz',
severity: 'MAJOR',
prioritizedRule: false,
+ impacts: [
+ { softwareQuality: SoftwareQuality.Maintainability, severity: SoftwareImpactSeverity.Medium },
+ ],
...overrides,
};
}
CleanCodeAttribute,
CleanCodeAttributeCategory,
SoftwareImpact,
+ SoftwareImpactSeverity,
+ SoftwareQuality,
} from './clean-code-taxonomy';
import { MessageFormatting, RawIssue } from './issues';
import { NewCodeDefinitionType } from './new-code-definition';
export interface RuleActivation {
createdAt: string;
+ impacts: { severity: SoftwareImpactSeverity; softwareQuality: SoftwareQuality }[];
inherit: RuleInheritance;
params: { key: string; value: string }[];
prioritizedRule: boolean;
no_file_selected=No file selected
no_tags=No tags
not_now=Not now
+not_impacted=Not impacted
off=off
on=on
or=Or
#------------------------------------------------------------------------------
coding_rules.active_in_all_profiles=The rule is already activated on all available Quality Profiles.
coding_rules.severity_deprecated=Changing rule severities is deprecated and will not be possible in the future.
+coding_rules.custom_severity.title=Rule severity in this quality profile
+coding_rules.custom_severity.description.standard=Changing rule severity in a quality profile {link}
+coding_rules.custom_severity.description.standard.link=does not change its recommended severity
+coding_rules.custom_severity.description.mqr=In the multi-quality mode, severities are directly tied to the software quality impacted. This means that {link}
+coding_rules.custom_severity.description.mqr.link=each software quality has its own severity per rule.
+coding_rules.custom_severity.not_recommended=This is a custom rule severity, the recommended one is {recommended}
+coding_rules.custom_severity.choose_severity=Choose severity
+coding_rules.custom_severity.severity_with_recommended={severity} (recommended)
coding_rules.activate=Activate
coding_rules.activate_in=Activate In
coding_rules.activate_in_quality_profile=Activate In Quality Profile
coding_rules.severity.deprecation.title=Severities are now directly tied to the software quality impacted. This old severity is deprecated and it will no longer be possible to change it in the future.
coding_rules.severity.deprecation.filter_by=You can now filter rules by Software Quality and new Severity.
coding_rules.prioritized_rule.title=Prioritized rule
-coding_rules.prioritized_rule.switch_label=Indicates that all corresponding issues in Overall Code should be fixed
+coding_rules.prioritized_rule.switch_label=All corresponding issues in the overall code should be fixed
coding_rules.prioritized_rule.note=For your Quality Gate to fail when corresponding issues exist in the overall code, you must add a condition that checks whether any issues have been raised from prioritized rules.
coding_rules.update_custom_rule=Update Custom Rule