Browse Source

SONAR-20355 Enable Desactivate button for inherited rules if allowed by settings

Co-authored-by: Benjamin Raymond <31401273+7PH@users.noreply.github.com>
tags/10.3.0.82913
Wouter Admiraal 8 months ago
parent
commit
a64c351b61

+ 1
- 0
server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts View File

@@ -464,6 +464,7 @@ export default class CodingRulesServiceMock {
}
const responseRules = filteredRules.slice((currentP - 1) * currentPs, currentP * currentPs);
return this.reply({
actives: qprofile ? this.rulesActivations : undefined,
rules: responseRules,
facets: facetCounts,
paging: mockPaging({

+ 4
- 0
server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts View File

@@ -132,6 +132,10 @@ export default class SettingsServiceMock {
key: 'sonar.javascript.globals',
values: ['angular', 'google', 'd3'],
},
{
key: SettingsKey.QPAdminCanDisableInheritedRules,
value: 'true',
},
];

#settingValues: SettingValue[] = cloneDeep(this.#defaultValues);

+ 1
- 1
server/sonar-web/src/main/js/api/mocks/data/qualityProfiles.ts View File

@@ -29,7 +29,7 @@ export function mockQualityProfilesList() {
languageName: 'Java',
actions: { edit: true },
}),
mockQualityProfile({ key: QP_2, name: 'QP Bar', language: 'js' }),
mockQualityProfile({ key: QP_2, name: 'QP Bar', language: 'py', languageName: 'Python' }),
mockQualityProfile({ key: QP_3, name: 'QP FooBar', language: 'java', languageName: 'Java' }),
mockQualityProfile({
key: QP_4,

+ 7
- 1
server/sonar-web/src/main/js/api/mocks/data/rules.ts View File

@@ -27,6 +27,8 @@ import {
} from '../../../types/clean-code-taxonomy';
import {
ADVANCED_RULE,
QP_1,
QP_2,
RULE_1,
RULE_10,
RULE_11,
@@ -243,6 +245,10 @@ export function mockRuleDetailsList() {

export function mockRulesActivationsInQP() {
return {
[RULE_1]: [mockRuleActivation({ qProfile: 'p1' })],
[RULE_1]: [mockRuleActivation({ qProfile: QP_1 })],
[RULE_7]: [mockRuleActivation({ qProfile: QP_2 })],
[RULE_8]: [mockRuleActivation({ qProfile: QP_2 })],
[RULE_9]: [mockRuleActivation({ qProfile: QP_2, inherit: 'INHERITED' })],
[RULE_10]: [mockRuleActivation({ qProfile: QP_2, inherit: 'OVERRIDES' })],
};
}

+ 51
- 27
server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts View File

@@ -20,6 +20,8 @@
import { act, fireEvent, screen } 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 } from '../../../api/mocks/data/ids';
import { CLEAN_CODE_CATEGORIES, SOFTWARE_QUALITIES } from '../../../helpers/constants';
import { parseDate } from '../../../helpers/dates';
import { mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks';
@@ -29,13 +31,18 @@ import {
CleanCodeAttributeCategory,
SoftwareQuality,
} from '../../../types/clean-code-taxonomy';
import { SettingsKey } from '../../../types/settings';
import { CurrentUser } from '../../../types/users';
import routes from '../routes';
import { getPageObjects } from '../utils-tests';

const handler: CodingRulesServiceMock = new CodingRulesServiceMock();
const rulesHandler = new CodingRulesServiceMock();
const settingsHandler = new SettingsServiceMock();

afterEach(() => handler.reset());
afterEach(() => {
rulesHandler.reset();
settingsHandler.reset();
});

describe('Rules app list', () => {
it('renders correctly', async () => {
@@ -45,7 +52,7 @@ describe('Rules app list', () => {
await ui.appLoaded();

// Renders list
handler
rulesHandler
.allRulesName()
.forEach((name) => expect(ui.ruleListItemLink(name).get()).toBeInTheDocument());

@@ -257,7 +264,7 @@ describe('Rules app list', () => {
describe('bulk change', () => {
it('no quality profile for bulk change based on language search', async () => {
const { ui, user } = getPageObjects();
handler.setIsAdmin();
rulesHandler.setIsAdmin();
renderCodingRulesApp(mockLoggedInUser());
await ui.appLoaded();

@@ -276,13 +283,13 @@ describe('Rules app list', () => {

it('should be able to bulk activate quality profile', async () => {
const { ui, user } = getPageObjects();
handler.setIsAdmin();
rulesHandler.setIsAdmin();
renderCodingRulesApp(mockLoggedInUser());
await ui.appLoaded();

const [selectQPSuccess, selectQPWarning] = handler.allQualityProfile('java');
const [selectQPSuccess, selectQPWarning] = rulesHandler.allQualityProfile('java');

const rulesCount = handler.allRulesCount();
const rulesCount = rulesHandler.allRulesCount();

await ui.bulkActivate(rulesCount, selectQPSuccess);

@@ -293,7 +300,7 @@ describe('Rules app list', () => {
await user.click(ui.bulkClose.get());

// Try bulk change when quality profile has warnning.
handler.activateWithWarning();
rulesHandler.activateWithWarning();

await ui.bulkActivate(rulesCount, selectQPWarning);
expect(
@@ -305,12 +312,12 @@ describe('Rules app list', () => {

it('should be able to bulk deactivate quality profile', async () => {
const { ui } = getPageObjects();
handler.setIsAdmin();
rulesHandler.setIsAdmin();
renderCodingRulesApp(mockLoggedInUser());
await ui.appLoaded();

const [selectQP] = handler.allQualityProfile('java');
const rulesCount = handler.allRulesCount();
const [selectQP] = rulesHandler.allQualityProfile('java');
const rulesCount = rulesHandler.allRulesCount();

await ui.bulkDeactivate(rulesCount, selectQP);

@@ -322,34 +329,51 @@ describe('Rules app list', () => {

it('can activate/deactivate specific rule for quality profile', async () => {
const { ui, user } = getPageObjects();
rulesHandler.setIsAdmin();
renderCodingRulesApp(mockLoggedInUser());
await ui.appLoaded();

await act(async () => {
await user.click(ui.qpFacet.get());
await user.click(ui.facetItem('QP Foo Java').get());
await user.click(ui.facetItem('QP Bar Python').get());
});

// Only one rule is activated in selected QP
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(1);
// Only 4 rules are activated in selected QP
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(4);

// Switch to inactive rules
await act(async () => {
await user.click(ui.qpInactiveRadio.get(ui.facetItem('QP Foo Java').get()));
await user.click(ui.qpInactiveRadio.get(ui.facetItem('QP Bar Python').get()));
});
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(1);
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(2);
expect(ui.activateButton.getAll()).toHaveLength(2);

// Activate Rule for qp
await user.click(ui.activateButton.get());
await user.click(ui.activateButton.getAll()[0]);
await user.click(ui.activateButton.get(ui.activateQPDialog.get()));
expect(ui.activateButton.query()).not.toBeInTheDocument();
expect(ui.deactivateButton.get()).toBeInTheDocument();
expect(ui.activateButton.getAll()).toHaveLength(1);
expect(ui.deactivateButton.getAll()).toHaveLength(1);

// Deactivate activated rule
await user.click(ui.deactivateButton.get());
await user.click(ui.yesButton.get());
expect(ui.deactivateButton.query()).not.toBeInTheDocument();
expect(ui.activateButton.get()).toBeInTheDocument();
expect(ui.activateButton.getAll()).toHaveLength(2);
});

it('can not deactivate rules for quality profile if setting is false', async () => {
const { ui } = getPageObjects();
rulesHandler.setIsAdmin();
settingsHandler.set(SettingsKey.QPAdminCanDisableInheritedRules, 'false');
renderCodingRulesApp(
mockLoggedInUser(),
'coding_rules?activation=true&tags=cute&qprofile=' + QP_2,
);
await ui.appLoaded();

// Only rule 9 is shown (inherited, activated)
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(1);
expect(ui.deactivateButton.get()).toBeDisabled();
});

it('navigates by keyboard', async () => {
@@ -508,7 +532,7 @@ describe('Rule app details', () => {

it('can activate/change/deactivate rule in quality profile', async () => {
const { ui, user } = getPageObjects();
handler.setIsAdmin();
rulesHandler.setIsAdmin();
renderCodingRulesApp(mockLoggedInUser(), 'coding_rules?open=rule1');
await ui.appLoaded();
expect(ui.qpLink('QP Foo').get()).toBeInTheDocument();
@@ -552,7 +576,7 @@ describe('Rule app details', () => {

it('can extend the rule description', async () => {
const { ui, user } = getPageObjects();
handler.setIsAdmin();
rulesHandler.setIsAdmin();
renderCodingRulesApp(undefined, 'coding_rules?open=rule5');
await ui.appLoaded();
expect(ui.ruleTitle('Awsome Python rule').get()).toBeInTheDocument();
@@ -585,7 +609,7 @@ describe('Rule app details', () => {

it('can set tags', async () => {
const { ui, user } = getPageObjects();
handler.setIsAdmin();
rulesHandler.setIsAdmin();
renderCodingRulesApp(undefined, 'coding_rules?open=rule10');
await ui.appLoaded();

@@ -613,7 +637,7 @@ describe('Rule app details', () => {
describe('custom rule', () => {
it('can create custom rule', async () => {
const { ui, user } = getPageObjects();
handler.setIsAdmin();
rulesHandler.setIsAdmin();
renderCodingRulesApp(mockLoggedInUser());
await ui.appLoaded();

@@ -655,7 +679,7 @@ describe('Rule app details', () => {

it('can edit custom rule', async () => {
const { ui, user } = getPageObjects();
handler.setIsAdmin();
rulesHandler.setIsAdmin();
renderCodingRulesApp(mockLoggedInUser(), 'coding_rules?open=rule9');
await ui.appLoaded();

@@ -675,7 +699,7 @@ describe('Rule app details', () => {

it('can delete custom rule', async () => {
const { ui, user } = getPageObjects();
handler.setIsAdmin();
rulesHandler.setIsAdmin();
renderCodingRulesApp(mockLoggedInUser(), 'coding_rules?open=rule9');
await ui.appLoaded();

@@ -690,7 +714,7 @@ describe('Rule app details', () => {

it('can delete custom rule from template page', async () => {
const { ui, user } = getPageObjects();
handler.setIsAdmin();
rulesHandler.setIsAdmin();
renderCodingRulesApp(mockLoggedInUser(), 'coding_rules?open=rule8');
await ui.appLoaded();


+ 18
- 11
server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx View File

@@ -22,6 +22,7 @@ import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { Profile, searchQualityProfiles } from '../../../api/quality-profiles';
import { getRulesApp, searchRules } from '../../../api/rules';
import { getValue } from '../../../api/settings';
import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
import FiltersHeader from '../../../components/common/FiltersHeader';
@@ -42,6 +43,7 @@ import {
removeWhitePageClass,
} from '../../../helpers/pages';
import { SecurityStandard } from '../../../types/security';
import { SettingsKey } from '../../../types/settings';
import { Dict, Paging, RawQuery, Rule, RuleActivation } from '../../../types/types';
import { CurrentUser, isLoggedIn } from '../../../types/users';
import {
@@ -85,6 +87,7 @@ interface Props {
interface State {
actives?: Actives;
canWrite?: boolean;
canDeactivateInherited?: boolean;
facets?: Facets;
loading: boolean;
openFacets: OpenFacets;
@@ -236,17 +239,20 @@ export class CodingRulesApp extends React.PureComponent<Props, State> {

fetchInitialData = () => {
this.setState({ loading: true });
Promise.all([getRulesApp(), searchQualityProfiles()]).then(
([{ canWrite, repositories }, { profiles }]) => {
this.setState({
canWrite,
referencedProfiles: keyBy(profiles, 'key'),
referencedRepositories: keyBy(repositories, 'key'),
});
this.fetchFirstRules();
},
this.stopLoading,
);

Promise.all([
getRulesApp(),
searchQualityProfiles(),
getValue({ key: SettingsKey.QPAdminCanDisableInheritedRules }),
]).then(([{ canWrite, repositories }, { profiles }, setting]) => {
this.setState({
canWrite,
canDeactivateInherited: setting?.value === 'true',
referencedProfiles: keyBy(profiles, 'key'),
referencedRepositories: keyBy(repositories, 'key'),
});
this.fetchFirstRules();
}, this.stopLoading);
};

makeFetchRequest = (query?: RawQuery) =>
@@ -664,6 +670,7 @@ export class CodingRulesApp extends React.PureComponent<Props, State> {
<RuleListItem
activation={this.getRuleActivation(rule.key)}
isLoggedIn={isLoggedIn(this.props.currentUser)}
canDeactivateInherited={this.state.canDeactivateInherited}
key={rule.key}
onActivate={this.handleRuleActivate}
onDeactivate={this.handleRuleDeactivate}

+ 7
- 3
server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx View File

@@ -38,6 +38,7 @@ import RuleInheritanceIcon from './RuleInheritanceIcon';
interface Props {
activation?: Activation;
isLoggedIn: boolean;
canDeactivateInherited?: boolean;
onActivate: (profile: string, rule: string, activation: Activation) => void;
onDeactivate: (profile: string, rule: string) => void;
onOpen: (ruleKey: string) => void;
@@ -128,7 +129,7 @@ export default class RuleListItem extends React.PureComponent<Props> {
};

renderActions = () => {
const { activation, isLoggedIn, rule, selectedProfile } = this.props;
const { activation, isLoggedIn, canDeactivateInherited, rule, selectedProfile } = this.props;

if (!selectedProfile || !isLoggedIn) {
return null;
@@ -155,7 +156,7 @@ export default class RuleListItem extends React.PureComponent<Props> {
if (activation) {
return (
<td className="coding-rule-table-meta-cell coding-rule-activation-actions">
{activation.inherit === 'NONE' ? (
{activation.inherit === 'NONE' || canDeactivateInherited ? (
<ConfirmButton
confirmButtonText={translate('yes')}
modalBody={translate('coding_rules.deactivate.confirm')}
@@ -173,7 +174,10 @@ export default class RuleListItem extends React.PureComponent<Props> {
</ConfirmButton>
) : (
<Tooltip overlay={translate('coding_rules.can_not_deactivate')}>
<Button className="coding-rules-detail-quality-profile-deactivate button-red disabled">
<Button
className="coding-rules-detail-quality-profile-deactivate button-red"
disabled
>
{translate('coding_rules.deactivate')}
</Button>
</Tooltip>

+ 1
- 0
server/sonar-web/src/main/js/types/settings.ts View File

@@ -27,6 +27,7 @@ export const enum SettingsKey {
PluginRiskConsent = 'sonar.plugins.risk.consent',
LicenceRemainingLocNotificationThreshold = 'sonar.license.notifications.remainingLocThreshold',
TokenMaxAllowedLifetime = 'sonar.auth.token.max.allowed.lifetime',
QPAdminCanDisableInheritedRules = 'sonar.allowQualityProfileAdminsDisableInheritedRules',
}

export enum GlobalSettingKeys {

Loading…
Cancel
Save