--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { cloneDeep, countBy } from 'lodash';
+import { mockQualityProfile, mockRule, mockRuleRepository } from '../../helpers/testMocks';
+import { RuleRepository } from '../../types/coding-rules';
+import { SearchRulesQuery } from '../../types/rules';
+import { Rule } from '../../types/types';
+import {
+ bulkActivateRules,
+ bulkDeactivateRules,
+ Profile,
+ searchQualityProfiles,
+ SearchQualityProfilesParameters,
+ SearchQualityProfilesResponse
+} from '../quality-profiles';
+import { getRulesApp, searchRules } from '../rules';
+
+interface FacetFilter {
+ languages?: string;
+}
+
+const FACET_RULE_MAP: { [key: string]: keyof Rule } = {
+ languages: 'lang',
+ types: 'type'
+};
+export default class CodingRulesMock {
+ defaultRules: Rule[] = [];
+ rules: Rule[] = [];
+ qualityProfile: Profile[] = [];
+ repositories: RuleRepository[] = [];
+ isAdmin = false;
+ applyWithWarning = false;
+
+ constructor() {
+ this.repositories = [
+ mockRuleRepository({ key: 'repo1' }),
+ mockRuleRepository({ key: 'repo2' })
+ ];
+ this.qualityProfile = [
+ mockQualityProfile({ key: 'p1', name: 'QP Foo', language: 'java', languageName: 'Java' }),
+ mockQualityProfile({ key: 'p2', name: 'QP Bar', language: 'js' }),
+ mockQualityProfile({ key: 'p3', name: 'QP FooBar', language: 'java', languageName: 'Java' })
+ ];
+
+ this.defaultRules = [
+ mockRule({
+ key: 'rule1',
+ type: 'BUG',
+ lang: 'java',
+ langName: 'Java',
+ name: 'Awsome java rule'
+ }),
+ mockRule({ key: 'rule2', name: 'Hot hotspot', type: 'SECURITY_HOTSPOT' }),
+ mockRule({ key: 'rule3', name: 'Unknown rule' }),
+ mockRule({ key: 'rule4', type: 'BUG', lang: 'c', langName: 'C', name: 'Awsome C rule' })
+ ];
+
+ (searchRules as jest.Mock).mockImplementation(this.handleSearchRules);
+ (searchQualityProfiles as jest.Mock).mockImplementation(this.handleSearchQualityProfiles);
+ (getRulesApp as jest.Mock).mockImplementation(this.handleGetRulesApp);
+ (bulkActivateRules as jest.Mock).mockImplementation(this.handleBulkActivateRules);
+ (bulkDeactivateRules as jest.Mock).mockImplementation(this.handleBulkDeactivateRules);
+
+ this.rules = cloneDeep(this.defaultRules);
+ }
+
+ filterFacet({ languages }: FacetFilter) {
+ let filteredRules = this.rules;
+ if (languages) {
+ filteredRules = filteredRules.filter(r => r.lang && languages.includes(r.lang));
+ }
+ return filteredRules;
+ }
+
+ setIsAdmin() {
+ this.isAdmin = true;
+ }
+
+ activateWithWarning() {
+ this.applyWithWarning = true;
+ }
+
+ reset() {
+ this.isAdmin = false;
+ this.applyWithWarning = false;
+ this.rules = cloneDeep(this.defaultRules);
+ }
+
+ allRulesName() {
+ return this.rules.map(r => r.name);
+ }
+
+ allQualityProfile(language: string) {
+ return this.qualityProfile.filter(qp => qp.language === language);
+ }
+
+ handleSearchRules = ({ facets, languages, p, ps }: SearchRulesQuery) => {
+ const countFacet = (facets || '').split(',').map((facet: keyof Rule) => {
+ const facetCount = countBy(this.rules.map(r => r[FACET_RULE_MAP[facet] || facet] as string));
+ return {
+ property: facet,
+ values: Object.keys(facetCount).map(val => ({ val, count: facetCount[val] }))
+ };
+ });
+ const currentPs = ps || 10;
+ const currentP = p || 1;
+ const filteredRules = this.filterFacet({ languages });
+ const responseRules = filteredRules.slice((currentP - 1) * currentPs, currentP * currentPs);
+ return this.reply({
+ total: filteredRules.length,
+ p: currentP,
+ ps: currentPs,
+ rules: responseRules,
+ facets: countFacet
+ });
+ };
+
+ handleBulkActivateRules = () => {
+ if (this.applyWithWarning) {
+ return this.reply({
+ succeeded: this.rules.length - 1,
+ failed: 1,
+ errors: [{ msg: 'c rule c:S6069 cannot be activated on cpp profile SonarSource' }]
+ });
+ }
+ return this.reply({
+ succeeded: this.rules.length,
+ failed: 0,
+ errors: []
+ });
+ };
+
+ handleBulkDeactivateRules = () => {
+ return this.reply({
+ succeeded: this.rules.length,
+ failed: 0
+ });
+ };
+
+ handleSearchQualityProfiles = ({ language }: SearchQualityProfilesParameters = {}): Promise<
+ SearchQualityProfilesResponse
+ > => {
+ let profiles: Profile[] = this.isAdmin
+ ? this.qualityProfile.map(p => ({ ...p, actions: { edit: true } }))
+ : this.qualityProfile;
+ if (language) {
+ profiles = profiles.filter(p => p.language === language);
+ }
+ return this.reply({ profiles });
+ };
+
+ handleGetRulesApp = () => {
+ return this.reply({ canWrite: this.isAdmin, repositories: this.repositories });
+ };
+
+ reply<T>(response: T): Promise<T> {
+ return Promise.resolve(cloneDeep(response));
+ }
+}
import throwGlobalError from '../app/utils/throwGlobalError';
import { getJSON, post, postJSON } from '../helpers/request';
import { GetRulesAppResponse, SearchRulesResponse } from '../types/coding-rules';
+import { SearchRulesQuery } from '../types/rules';
import { RuleActivation, RuleDetails } from '../types/types';
export function getRulesApp(): Promise<GetRulesAppResponse> {
return getJSON('/api/rules/app').catch(throwGlobalError);
}
-export function searchRules(data: {
- activation?: boolean | string;
- active_severities?: string;
- asc?: boolean | string;
- available_since?: string;
- cwe?: string;
- f?: string;
- facets?: string;
- include_external?: boolean | string;
- inheritance?: string;
- is_template?: boolean | string;
- languages?: string;
- owaspTop10?: string;
- p?: number;
- ps?: number;
- q?: string;
- qprofile?: string;
- repositories?: string;
- rule_key?: string;
- s?: string;
- sansTop25?: string;
- severities?: string;
- sonarsourceSecurity?: string;
- statuses?: string;
- tags?: string;
- template_key?: string;
- types?: string;
-}): Promise<SearchRulesResponse> {
+export function searchRules(data: SearchRulesQuery): Promise<SearchRulesResponse> {
return getJSON('/api/rules/search', data).catch(throwGlobalError);
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { screen, waitFor, within } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import CodingRulesMock from '../../../api/mocks/CodingRulesMock';
+import { mockLoggedInUser } from '../../../helpers/testMocks';
+import { renderApp } from '../../../helpers/testReactTestingUtils';
+import { CurrentUser } from '../../../types/users';
+import routes from '../routes';
+
+jest.mock('../../../api/rules');
+jest.mock('../../../api/quality-profiles');
+
+let handler: CodingRulesMock;
+
+beforeAll(() => {
+ window.scrollTo = jest.fn();
+ handler = new CodingRulesMock();
+});
+
+afterEach(() => handler.reset());
+
+jest.setTimeout(10_000);
+
+it('should list all rules', async () => {
+ renderCodingRulesApp();
+
+ await waitFor(() => {
+ handler
+ .allRulesName()
+ .forEach(name => expect(screen.getByRole('link', { name })).toBeInTheDocument());
+ });
+});
+
+it('should have all type facet', async () => {
+ renderCodingRulesApp();
+
+ await waitFor(() => {
+ [
+ 'issue.type.BUG',
+ 'issue.type.VULNERABILITY',
+ 'issue.type.CODE_SMELL',
+ 'issue.type.SECURITY_HOTSPOT'
+ ].forEach(name => expect(screen.getByRole('link', { name })).toBeInTheDocument());
+ });
+});
+
+it('select the correct quality profile for bulk change base on language search', async () => {
+ const user = userEvent.setup();
+ handler.setIsAdmin();
+ renderCodingRulesApp(mockLoggedInUser());
+ const selectQP = handler.allQualityProfile('js')[0];
+
+ await user.click(await screen.findByRole('link', { name: 'JavaScript' }));
+ await user.click(await screen.findByRole('button', { name: 'bulk_change' }));
+ await user.click(await screen.findByRole('link', { name: 'coding_rules.activate_in…' }));
+ const dialog = screen.getByRole('dialog', {
+ name: 'coding_rules.activate_in_quality_profile (2 coding_rules._rules)'
+ });
+
+ expect(dialog).toBeInTheDocument();
+ const dialogScreen = within(dialog);
+ expect(dialogScreen.getByText(`${selectQP.name} - ${selectQP.languageName}`)).toBeInTheDocument();
+});
+
+it('no quality profile for bulk cahnge base on language search', async () => {
+ const user = userEvent.setup();
+ handler.setIsAdmin();
+ renderCodingRulesApp(mockLoggedInUser());
+
+ await user.click(await screen.findByRole('link', { name: 'C' }));
+ await user.click(await screen.findByRole('button', { name: 'bulk_change' }));
+ await user.click(await screen.findByRole('link', { name: 'coding_rules.activate_in…' }));
+ const dialog = screen.getByRole('dialog', {
+ name: 'coding_rules.activate_in_quality_profile (1 coding_rules._rules)'
+ });
+
+ expect(dialog).toBeInTheDocument();
+ const dialogScreen = within(dialog);
+ await user.click(dialogScreen.getByRole('textbox', { name: 'coding_rules.activate_in' }));
+ expect(dialogScreen.getByText('coding_rules.bulk_change.no_quality_profile')).toBeInTheDocument();
+});
+
+it('should be able to bulk activate quality profile', async () => {
+ const user = userEvent.setup();
+ handler.setIsAdmin();
+ renderCodingRulesApp(mockLoggedInUser());
+
+ const selectQPSuccess = handler.allQualityProfile('java')[0];
+ const selectQPWarning = handler.allQualityProfile('java')[1];
+
+ await user.click(await screen.findByRole('button', { name: 'bulk_change' }));
+ await user.click(await screen.findByRole('link', { name: 'coding_rules.activate_in…' }));
+
+ const dialog = screen.getByRole('dialog', {
+ name: 'coding_rules.activate_in_quality_profile (4 coding_rules._rules)'
+ });
+ expect(dialog).toBeInTheDocument();
+
+ let dialogScreen = within(dialog);
+ await user.click(dialogScreen.getByRole('textbox', { name: 'coding_rules.activate_in' }));
+ await user.click(
+ dialogScreen.getByText(`${selectQPSuccess.name} - ${selectQPSuccess.languageName}`)
+ );
+ expect(
+ dialogScreen.getByText(`${selectQPSuccess.name} - ${selectQPSuccess.languageName}`)
+ ).toBeInTheDocument();
+
+ await user.click(dialogScreen.getByRole('button', { name: 'apply' }));
+ expect(
+ dialogScreen.getByText(
+ `coding_rules.bulk_change.success.${selectQPSuccess.name}.${selectQPSuccess.languageName}.${
+ handler.allRulesName().length
+ }`
+ )
+ ).toBeInTheDocument();
+
+ await user.click(dialogScreen.getByRole('button', { name: 'close' }));
+
+ // Try bulk change when quality profile has warnning.
+ handler.activateWithWarning();
+
+ await user.click(await screen.findByRole('button', { name: 'bulk_change' }));
+ await user.click(await screen.findByRole('link', { name: 'coding_rules.activate_in…' }));
+ dialogScreen = within(
+ screen.getByRole('dialog', {
+ name: 'coding_rules.activate_in_quality_profile (4 coding_rules._rules)'
+ })
+ );
+ await user.click(dialogScreen.getByRole('textbox', { name: 'coding_rules.activate_in' }));
+ await user.click(
+ dialogScreen.getByText(`${selectQPWarning.name} - ${selectQPWarning.languageName}`)
+ );
+ await user.click(dialogScreen.getByRole('button', { name: 'apply' }));
+ expect(
+ dialogScreen.getByText(
+ `coding_rules.bulk_change.warning.${selectQPWarning.name}.${
+ selectQPWarning.languageName
+ }.${handler.allRulesName().length - 1}.1`
+ )
+ ).toBeInTheDocument();
+});
+
+it('should be able to bulk deactivate quality profile', async () => {
+ const user = userEvent.setup();
+ handler.setIsAdmin();
+ renderCodingRulesApp(mockLoggedInUser());
+
+ const selectQP = handler.allQualityProfile('java')[0];
+
+ await user.click(await screen.findByRole('button', { name: 'bulk_change' }));
+ await user.click(await screen.findByRole('link', { name: 'coding_rules.deactivate_in…' }));
+ const dialogScreen = within(
+ screen.getByRole('dialog', {
+ name: 'coding_rules.deactivate_in_quality_profile (4 coding_rules._rules)'
+ })
+ );
+ await user.click(dialogScreen.getByRole('textbox', { name: 'coding_rules.deactivate_in' }));
+
+ await user.click(dialogScreen.getByText(`${selectQP.name} - ${selectQP.languageName}`));
+ await user.click(dialogScreen.getByRole('button', { name: 'apply' }));
+ expect(
+ dialogScreen.getByText(
+ `coding_rules.bulk_change.success.${selectQP.name}.${selectQP.languageName}.${
+ handler.allRulesName().length
+ }`
+ )
+ ).toBeInTheDocument();
+});
+
+function renderCodingRulesApp(currentUser?: CurrentUser) {
+ renderApp('coding_rules', routes, {
+ currentUser,
+ languages: {
+ js: { key: 'js', name: 'JavaScript' },
+ java: { key: 'java', name: 'Java' },
+ c: { key: 'c', name: 'C' }
+ }
+ });
+}
*/
import classNames from 'classnames';
import * as React from 'react';
+import { components, OptionProps, OptionTypeBase, SingleValueProps } from 'react-select';
import { activateRule, Profile } from '../../../api/quality-profiles';
import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
import Modal from '../../../components/controls/Modal';
-import SelectLegacy from '../../../components/controls/SelectLegacy';
+import Select from '../../../components/controls/Select';
import SeverityHelper from '../../../components/shared/SeverityHelper';
import { Alert } from '../../../components/ui/Alert';
import { SEVERITIES } from '../../../helpers/constants';
rule: Rule | RuleDetails;
}
+interface ProfileWithDeph extends Profile {
+ depth: number;
+}
+
interface State {
params: Dict<string>;
- profile: string;
+ profile?: ProfileWithDeph;
severity: string;
submitting: boolean;
}
const profilesWithDepth = this.getQualityProfilesWithDepth(props);
this.state = {
params: this.getParams(props),
- profile: profilesWithDepth.length > 0 ? profilesWithDepth[0].key : '',
+ profile: profilesWithDepth.length > 0 ? profilesWithDepth[0] : undefined,
severity: props.activation ? props.activation.severity : props.rule.severity,
submitting: false
};
event.preventDefault();
this.setState({ submitting: true });
const data = {
- key: this.state.profile,
+ key: this.state.profile?.key || '',
params: this.state.params,
rule: this.props.rule.key,
severity: this.state.severity
this.setState((state: State) => ({ params: { ...state.params, [name]: value } }));
};
- handleProfileChange = ({ value }: { value: string }) => {
- this.setState({ profile: value });
+ handleProfileChange = (profile: ProfileWithDeph) => {
+ this.setState({ profile });
};
- handleSeverityChange = ({ value }: { value: string }) => {
+ handleSeverityChange = ({ value }: OptionTypeBase) => {
this.setState({ severity: value });
};
- renderSeverityOption = ({ value }: { value: string }) => {
- return <SeverityHelper severity={value} />;
- };
-
render() {
const { activation, rule } = this.props;
const { profile, severity, submitting } = this.state;
const isCustomRule = !!(rule as RuleDetails).templateKey;
const activeInAllProfiles = profilesWithDepth.length <= 0;
const isUpdateMode = !!activation;
+ const serverityOption = SEVERITIES.map(severity => ({
+ label: translate('severity', severity),
+ value: severity
+ }));
+
+ function Option(props: OptionProps<OptionTypeBase, false>) {
+ return (
+ <components.Option {...props}>
+ <SeverityHelper severity={props.data.value} />
+ </components.Option>
+ );
+ }
+
+ function SingleValue(props: SingleValueProps<OptionTypeBase>) {
+ return (
+ <components.SingleValue {...props}>
+ <SeverityHelper className="coding-rules-severity-value" severity={props.data.value} />
+ </components.SingleValue>
+ );
+ }
return (
<Modal contentLabel={this.props.modalHeader} onRequestClose={this.props.onClose} size="small">
)}
<div className="modal-field">
- <label>{translate('coding_rules.quality_profile')}</label>
- <SelectLegacy
+ <label id="coding-rules-quality-profile-select">
+ {translate('coding_rules.quality_profile')}
+ </label>
+ <Select
className="js-profile"
- clearable={false}
- disabled={submitting || profilesWithDepth.length === 1}
+ aria-labelledby="coding-rules-quality-profile-select"
+ isClearable={false}
+ isDisabled={submitting || profilesWithDepth.length === 1}
onChange={this.handleProfileChange}
- options={profilesWithDepth.map(profile => ({
- label: ' '.repeat(profile.depth) + profile.name,
- value: profile.key
- }))}
+ getOptionLabel={p => ' '.repeat(p.depth) + p.name}
+ options={profilesWithDepth}
value={profile}
/>
</div>
<div className="modal-field">
- <label>{translate('severity')}</label>
- <SelectLegacy
+ <label id="coding-rules-severity-select">{translate('severity')}</label>
+ <Select
className="js-severity"
- clearable={false}
- disabled={submitting}
+ isClearable={false}
+ isDisabled={submitting}
+ aria-labelledby="coding-rules-severity-select"
onChange={this.handleSeverityChange}
- optionRenderer={this.renderSeverityOption}
- options={SEVERITIES.map(severity => ({
- label: translate('severity', severity),
- value: severity
- }))}
- searchable={false}
- value={severity}
- valueRenderer={this.renderSeverityOption}
+ components={{ Option, SingleValue }}
+ options={serverityOption}
+ isSearchable={false}
+ value={serverityOption.find(s => s.value === severity)}
/>
</div>
{isCustomRule ? (
renderResult = (result: ActivationResult) => {
const { profile: profileKey } = result;
const profile = this.props.referencedProfiles[profileKey];
- if (!profile) {
- return null;
- }
+
const { languages } = this.props;
const language = languages[profile.language]
? languages[profile.language].name
interface Props {
allowCustomRules?: boolean;
canWrite?: boolean;
- hideQualityProfiles?: boolean;
onActivate: (profile: string, rule: string, activation: Activation) => void;
onDeactivate: (profile: string, rule: string) => void;
onDelete: (rule: string) => void;
return <div className="coding-rule-details" />;
}
- const { allowCustomRules, canWrite, hideQualityProfiles, referencedProfiles } = this.props;
+ const { allowCustomRules, canWrite, referencedProfiles } = this.props;
const { params = [] } = ruleDetails;
const isCustom = !!ruleDetails.templateKey;
/>
)}
- {!ruleDetails.isTemplate && !hideQualityProfiles && (
+ {!ruleDetails.isTemplate && (
<RuleDetailsProfiles
activations={this.state.actives}
canWrite={canWrite}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { activateRule } from '../../../../api/quality-profiles';
import {
mockQualityProfile,
mockRule,
} from '../../../../helpers/testMocks';
import ActivationFormModal from '../ActivationFormModal';
+jest.mock('../../../../api/quality-profiles', () => ({
+ activateRule: jest.fn().mockResolvedValueOnce({})
+}));
+
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(
expect(wrapper).toMatchSnapshot('submitting');
});
+it('should activate rule on quality profile when submit', () => {
+ const wrapper = shallowRender();
+ wrapper.instance().handleFormSubmit(({
+ preventDefault: jest.fn()
+ } as any) as React.SyntheticEvent<HTMLFormElement>);
+ expect(activateRule).toHaveBeenCalledWith({
+ key: '',
+ params: {
+ '1': '1',
+ '2': '1'
+ },
+ rule: 'javascript:S1067',
+ severity: 'MAJOR'
+ });
+});
+
+it('should handle profile change correctly', () => {
+ const wrapper = shallowRender();
+ const qualityProfile = mockQualityProfile();
+ wrapper.instance().handleProfileChange(qualityProfile);
+ expect(wrapper.state().profile).toBe(qualityProfile);
+});
+
function shallowRender(props: Partial<ActivationFormModal['props']> = {}) {
return shallow<ActivationFormModal>(
<ActivationFormModal
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import { bulkActivateRules, bulkDeactivateRules } from '../../../../api/quality-profiles';
-import { mockLanguage, mockQualityProfile } from '../../../../helpers/testMocks';
-import { submit, waitAndUpdate } from '../../../../helpers/testUtils';
-import { Query } from '../../query';
-import { BulkChangeModal } from '../BulkChangeModal';
-
-jest.mock('../../../../api/quality-profiles', () => ({
- bulkActivateRules: jest.fn().mockResolvedValue({ failed: 0, succeeded: 2 }),
- bulkDeactivateRules: jest.fn().mockResolvedValue({ failed: 2, succeeded: 0 })
-}));
-
-beforeEach(jest.clearAllMocks);
-
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot('default');
- expect(shallowRender({ profile: undefined })).toMatchSnapshot('no profile pre-selected');
- expect(shallowRender({ action: 'deactivate' })).toMatchSnapshot('deactivate action');
- expect(
- shallowRender().setState({
- results: [
- { failed: 2, profile: 'foo', succeeded: 0 },
- { failed: 0, profile: 'bar', succeeded: 2 }
- ]
- })
- ).toMatchSnapshot('results');
- expect(shallowRender().setState({ submitting: true })).toMatchSnapshot('submitting');
- expect(shallowRender().setState({ finished: true })).toMatchSnapshot('finished');
-});
-
-it('should pre-select a profile if only 1 is available', () => {
- const profile = mockQualityProfile({
- actions: { edit: true },
- isBuiltIn: false,
- key: 'foo',
- language: 'js'
- });
- const wrapper = shallowRender({ profile: undefined, referencedProfiles: { foo: profile } });
- expect(wrapper.state().selectedProfiles).toEqual([profile]);
-});
-
-it('should handle profile selection', () => {
- const wrapper = shallowRender();
- const profiles = [mockQualityProfile({ name: 'foo' }), mockQualityProfile({ name: 'bar' })];
- wrapper.instance().handleProfileSelect(profiles);
- expect(wrapper.state().selectedProfiles).toEqual(profiles);
-});
-
-it('should handle form submission', async () => {
- const wrapper = shallowRender({ profile: undefined });
- const profiles = [
- mockQualityProfile({ name: 'foo', key: 'foo' }),
- mockQualityProfile({ name: 'bar', key: 'bar' })
- ];
- wrapper.setState({ selectedProfiles: profiles });
-
- // Activate.
- submit(wrapper.find('form'));
- await waitAndUpdate(wrapper);
- expect(bulkActivateRules).toBeCalledWith(expect.objectContaining({ targetKey: 'foo' }));
-
- await waitAndUpdate(wrapper);
- expect(bulkActivateRules).toBeCalledWith(expect.objectContaining({ targetKey: 'bar' }));
-
- await waitAndUpdate(wrapper);
- expect(wrapper.state().results).toEqual([
- { failed: 0, profile: 'foo', succeeded: 2 },
- { failed: 0, profile: 'bar', succeeded: 2 }
- ]);
-
- // Deactivate.
- wrapper.setProps({ action: 'deactivate' }).setState({ results: [] });
- submit(wrapper.find('form'));
- await waitAndUpdate(wrapper);
- expect(bulkDeactivateRules).toBeCalledWith(expect.objectContaining({ targetKey: 'foo' }));
-
- await waitAndUpdate(wrapper);
- expect(bulkDeactivateRules).toBeCalledWith(expect.objectContaining({ targetKey: 'bar' }));
-
- await waitAndUpdate(wrapper);
- expect(wrapper.state().results).toEqual([
- { failed: 2, profile: 'foo', succeeded: 0 },
- { failed: 2, profile: 'bar', succeeded: 0 }
- ]);
-});
-
-function shallowRender(props: Partial<BulkChangeModal['props']> = {}) {
- return shallow<BulkChangeModal>(
- <BulkChangeModal
- action="activate"
- languages={{ js: mockLanguage() }}
- onClose={jest.fn()}
- profile={mockQualityProfile()}
- query={{ languages: ['js'] } as Query}
- referencedProfiles={{
- foo: mockQualityProfile({ key: 'foo' }),
- bar: mockQualityProfile({ key: 'bar' })
- }}
- total={42}
- {...props}
- />
- );
-}
<div
className="modal-field"
>
- <label>
+ <label
+ id="coding-rules-quality-profile-select"
+ >
coding_rules.quality_profile
</label>
- <SelectLegacy
+ <Select
+ aria-labelledby="coding-rules-quality-profile-select"
className="js-profile"
- clearable={false}
- disabled={false}
+ getOptionLabel={[Function]}
+ isClearable={false}
+ isDisabled={false}
onChange={[Function]}
options={Array []}
- value=""
/>
</div>
<div
className="modal-field"
>
- <label>
+ <label
+ id="coding-rules-severity-select"
+ >
severity
</label>
- <SelectLegacy
+ <Select
+ aria-labelledby="coding-rules-severity-select"
className="js-severity"
- clearable={false}
- disabled={false}
+ components={
+ Object {
+ "Option": [Function],
+ "SingleValue": [Function],
+ }
+ }
+ isClearable={false}
+ isDisabled={false}
+ isSearchable={false}
onChange={[Function]}
- optionRenderer={[Function]}
options={
Array [
Object {
},
]
}
- searchable={false}
- value="MAJOR"
- valueRenderer={[Function]}
+ value={
+ Object {
+ "label": "severity.MAJOR",
+ "value": "MAJOR",
+ }
+ }
/>
</div>
<div
<div
className="modal-field"
>
- <label>
+ <label
+ id="coding-rules-quality-profile-select"
+ >
coding_rules.quality_profile
</label>
- <SelectLegacy
+ <Select
+ aria-labelledby="coding-rules-quality-profile-select"
className="js-profile"
- clearable={false}
- disabled={false}
+ getOptionLabel={[Function]}
+ isClearable={false}
+ isDisabled={false}
onChange={[Function]}
options={Array []}
- value=""
/>
</div>
<div
className="modal-field"
>
- <label>
+ <label
+ id="coding-rules-severity-select"
+ >
severity
</label>
- <SelectLegacy
+ <Select
+ aria-labelledby="coding-rules-severity-select"
className="js-severity"
- clearable={false}
- disabled={false}
+ components={
+ Object {
+ "Option": [Function],
+ "SingleValue": [Function],
+ }
+ }
+ isClearable={false}
+ isDisabled={false}
+ isSearchable={false}
onChange={[Function]}
- optionRenderer={[Function]}
options={
Array [
Object {
},
]
}
- searchable={false}
- value="MAJOR"
- valueRenderer={[Function]}
+ value={
+ Object {
+ "label": "severity.MAJOR",
+ "value": "MAJOR",
+ }
+ }
/>
</div>
<div
<div
className="modal-field"
>
- <label>
+ <label
+ id="coding-rules-quality-profile-select"
+ >
coding_rules.quality_profile
</label>
- <SelectLegacy
+ <Select
+ aria-labelledby="coding-rules-quality-profile-select"
className="js-profile"
- clearable={false}
- disabled={true}
+ getOptionLabel={[Function]}
+ isClearable={false}
+ isDisabled={true}
onChange={[Function]}
options={Array []}
- value=""
/>
</div>
<div
className="modal-field"
>
- <label>
+ <label
+ id="coding-rules-severity-select"
+ >
severity
</label>
- <SelectLegacy
+ <Select
+ aria-labelledby="coding-rules-severity-select"
className="js-severity"
- clearable={false}
- disabled={true}
+ components={
+ Object {
+ "Option": [Function],
+ "SingleValue": [Function],
+ }
+ }
+ isClearable={false}
+ isDisabled={true}
+ isSearchable={false}
onChange={[Function]}
- optionRenderer={[Function]}
options={
Array [
Object {
},
]
}
- searchable={false}
- value="MAJOR"
- valueRenderer={[Function]}
+ value={
+ Object {
+ "label": "severity.MAJOR",
+ "value": "MAJOR",
+ }
+ }
/>
</div>
<div
<div
className="modal-field"
>
- <label>
+ <label
+ id="coding-rules-quality-profile-select"
+ >
coding_rules.quality_profile
</label>
- <SelectLegacy
+ <Select
+ aria-labelledby="coding-rules-quality-profile-select"
className="js-profile"
- clearable={false}
- disabled={false}
+ getOptionLabel={[Function]}
+ isClearable={false}
+ isDisabled={false}
onChange={[Function]}
options={Array []}
- value=""
/>
</div>
<div
className="modal-field"
>
- <label>
+ <label
+ id="coding-rules-severity-select"
+ >
severity
</label>
- <SelectLegacy
+ <Select
+ aria-labelledby="coding-rules-severity-select"
className="js-severity"
- clearable={false}
- disabled={false}
+ components={
+ Object {
+ "Option": [Function],
+ "SingleValue": [Function],
+ }
+ }
+ isClearable={false}
+ isDisabled={false}
+ isSearchable={false}
onChange={[Function]}
- optionRenderer={[Function]}
options={
Array [
Object {
},
]
}
- searchable={false}
- value="MAJOR"
- valueRenderer={[Function]}
+ value={
+ Object {
+ "label": "severity.MAJOR",
+ "value": "MAJOR",
+ }
+ }
/>
</div>
<div
<div
className="modal-field"
>
- <label>
+ <label
+ id="coding-rules-quality-profile-select"
+ >
coding_rules.quality_profile
</label>
- <SelectLegacy
+ <Select
+ aria-labelledby="coding-rules-quality-profile-select"
className="js-profile"
- clearable={false}
- disabled={true}
+ getOptionLabel={[Function]}
+ isClearable={false}
+ isDisabled={true}
onChange={[Function]}
options={
Array [
Object {
- "label": "name",
- "value": "key",
+ "actions": Object {
+ "edit": true,
+ },
+ "activeDeprecatedRuleCount": 2,
+ "activeRuleCount": 10,
+ "childrenCount": 0,
+ "depth": 0,
+ "isBuiltIn": false,
+ "isDefault": false,
+ "isInherited": false,
+ "key": "key",
+ "language": "js",
+ "languageName": "JavaScript",
+ "name": "name",
+ "projectCount": 3,
},
]
}
- value="key"
+ value={
+ Object {
+ "actions": Object {
+ "edit": true,
+ },
+ "activeDeprecatedRuleCount": 2,
+ "activeRuleCount": 10,
+ "childrenCount": 0,
+ "depth": 0,
+ "isBuiltIn": false,
+ "isDefault": false,
+ "isInherited": false,
+ "key": "key",
+ "language": "js",
+ "languageName": "JavaScript",
+ "name": "name",
+ "projectCount": 3,
+ }
+ }
/>
</div>
<div
className="modal-field"
>
- <label>
+ <label
+ id="coding-rules-severity-select"
+ >
severity
</label>
- <SelectLegacy
+ <Select
+ aria-labelledby="coding-rules-severity-select"
className="js-severity"
- clearable={false}
- disabled={false}
+ components={
+ Object {
+ "Option": [Function],
+ "SingleValue": [Function],
+ }
+ }
+ isClearable={false}
+ isDisabled={false}
+ isSearchable={false}
onChange={[Function]}
- optionRenderer={[Function]}
options={
Array [
Object {
},
]
}
- searchable={false}
- value="MAJOR"
- valueRenderer={[Function]}
+ value={
+ Object {
+ "label": "severity.MAJOR",
+ "value": "MAJOR",
+ }
+ }
/>
</div>
<div
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly: deactivate action 1`] = `
-<Modal
- contentLabel="coding_rules.deactivate_in_quality_profile (42 coding_rules._rules)"
- onRequestClose={[MockFunction]}
- size="small"
->
- <form
- onSubmit={[Function]}
- >
- <header
- className="modal-head"
- >
- <h2>
- coding_rules.deactivate_in_quality_profile (42 coding_rules._rules)
- </h2>
- </header>
- <div
- className="modal-body"
- >
- <div
- className="modal-field"
- >
- <h3>
- <label
- id="coding-rules-bulk-change-profile"
- >
- coding_rules.deactivate_in
- </label>
- </h3>
- <span>
- name
- —
- are_you_sure
- </span>
- </div>
- </div>
- <footer
- className="modal-foot"
- >
- <SubmitButton
- disabled={false}
- id="coding-rules-submit-bulk-change"
- >
- apply
- </SubmitButton>
- <ResetButtonLink
- onClick={[MockFunction]}
- >
- cancel
- </ResetButtonLink>
- </footer>
- </form>
-</Modal>
-`;
-
-exports[`should render correctly: default 1`] = `
-<Modal
- contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)"
- onRequestClose={[MockFunction]}
- size="small"
->
- <form
- onSubmit={[Function]}
- >
- <header
- className="modal-head"
- >
- <h2>
- coding_rules.activate_in_quality_profile (42 coding_rules._rules)
- </h2>
- </header>
- <div
- className="modal-body"
- >
- <div
- className="modal-field"
- >
- <h3>
- <label
- id="coding-rules-bulk-change-profile"
- >
- coding_rules.activate_in
- </label>
- </h3>
- <span>
- name
- —
- are_you_sure
- </span>
- </div>
- </div>
- <footer
- className="modal-foot"
- >
- <SubmitButton
- disabled={false}
- id="coding-rules-submit-bulk-change"
- >
- apply
- </SubmitButton>
- <ResetButtonLink
- onClick={[MockFunction]}
- >
- cancel
- </ResetButtonLink>
- </footer>
- </form>
-</Modal>
-`;
-
-exports[`should render correctly: finished 1`] = `
-<Modal
- contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)"
- onRequestClose={[MockFunction]}
- size="small"
->
- <form
- onSubmit={[Function]}
- >
- <header
- className="modal-head"
- >
- <h2>
- coding_rules.activate_in_quality_profile (42 coding_rules._rules)
- </h2>
- </header>
- <div
- className="modal-body"
- />
- <footer
- className="modal-foot"
- >
- <ResetButtonLink
- onClick={[MockFunction]}
- >
- close
- </ResetButtonLink>
- </footer>
- </form>
-</Modal>
-`;
-
-exports[`should render correctly: no profile pre-selected 1`] = `
-<Modal
- contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)"
- onRequestClose={[MockFunction]}
- size="small"
->
- <form
- onSubmit={[Function]}
- >
- <header
- className="modal-head"
- >
- <h2>
- coding_rules.activate_in_quality_profile (42 coding_rules._rules)
- </h2>
- </header>
- <div
- className="modal-body"
- >
- <div
- className="modal-field"
- >
- <h3>
- <label
- id="coding-rules-bulk-change-profile"
- >
- coding_rules.activate_in
- </label>
- </h3>
- <Select
- aria-labelledby="coding-rules-bulk-change-profile"
- getOptionLabel={[Function]}
- getOptionValue={[Function]}
- isClearable={false}
- isMulti={true}
- isSearchable={true}
- noOptionsMessage={[Function]}
- onChange={[Function]}
- options={Array []}
- value={Array []}
- />
- </div>
- </div>
- <footer
- className="modal-foot"
- >
- <SubmitButton
- disabled={false}
- id="coding-rules-submit-bulk-change"
- >
- apply
- </SubmitButton>
- <ResetButtonLink
- onClick={[MockFunction]}
- >
- cancel
- </ResetButtonLink>
- </footer>
- </form>
-</Modal>
-`;
-
-exports[`should render correctly: results 1`] = `
-<Modal
- contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)"
- onRequestClose={[MockFunction]}
- size="small"
->
- <form
- onSubmit={[Function]}
- >
- <header
- className="modal-head"
- >
- <h2>
- coding_rules.activate_in_quality_profile (42 coding_rules._rules)
- </h2>
- </header>
- <div
- className="modal-body"
- >
- <Alert
- key="foo"
- variant="warning"
- >
- coding_rules.bulk_change.warning.name.CSS.0.2
- </Alert>
- <Alert
- key="bar"
- variant="success"
- >
- coding_rules.bulk_change.success.name.CSS.2
- </Alert>
- <div
- className="modal-field"
- >
- <h3>
- <label
- id="coding-rules-bulk-change-profile"
- >
- coding_rules.activate_in
- </label>
- </h3>
- <span>
- name
- —
- are_you_sure
- </span>
- </div>
- </div>
- <footer
- className="modal-foot"
- >
- <SubmitButton
- disabled={false}
- id="coding-rules-submit-bulk-change"
- >
- apply
- </SubmitButton>
- <ResetButtonLink
- onClick={[MockFunction]}
- >
- cancel
- </ResetButtonLink>
- </footer>
- </form>
-</Modal>
-`;
-
-exports[`should render correctly: submitting 1`] = `
-<Modal
- contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)"
- onRequestClose={[MockFunction]}
- size="small"
->
- <form
- onSubmit={[Function]}
- >
- <header
- className="modal-head"
- >
- <h2>
- coding_rules.activate_in_quality_profile (42 coding_rules._rules)
- </h2>
- </header>
- <div
- className="modal-body"
- />
- <footer
- className="modal-foot"
- >
- <i
- className="spinner spacer-right"
- />
- <SubmitButton
- disabled={true}
- id="coding-rules-submit-bulk-change"
- >
- apply
- </SubmitButton>
- <ResetButtonLink
- onClick={[MockFunction]}
- >
- cancel
- </ResetButtonLink>
- </footer>
- </form>
-</Modal>
-`;
line-height: 1;
}
-.coding-rules-details-tag-edit-cancel {
+.coding-rules-details-tag-edit-cancel,
+.coding-rules-severity-value svg {
vertical-align: middle;
}
import { DocumentationEntry } from '../apps/documentation/utils';
import { Exporter, Profile } from '../apps/quality-profiles/types';
import { AppState } from '../types/appstate';
+import { RuleRepository } from '../types/coding-rules';
import { EditionKey } from '../types/editions';
import { RawIssue } from '../types/issues';
import { Language } from '../types/languages';
};
}
-export function mockRuleRepository(
- override: Partial<{ key: string; language: string; name: string }> = {}
-) {
+export function mockRuleRepository(override: Partial<RuleRepository> = {}) {
return { key: 'css', language: 'css', name: 'SonarQube', ...override };
}
import { Store } from 'redux';
import AppStateContextProvider from '../app/components/app-state/AppStateContextProvider';
import CurrentUserContextProvider from '../app/components/current-user/CurrentUserContextProvider';
+import { LanguagesContext } from '../app/components/languages/LanguagesContext';
import { MetricsContext } from '../app/components/metrics/MetricsContext';
import getStore from '../app/utils/getStore';
import { RouteWithChildRoutes } from '../app/utils/startReactApp';
import { Store as State } from '../store/rootReducer';
import { AppState } from '../types/appstate';
-import { Dict, Metric } from '../types/types';
+import { Dict, Languages, Metric } from '../types/types';
import { CurrentUser } from '../types/users';
import { DEFAULT_METRICS } from './mocks/metrics';
import { mockAppState, mockCurrentUser } from './testMocks';
store?: Store<State, any>;
history?: History;
appState?: AppState;
+ languages?: Languages;
currentUser?: CurrentUser;
}
export function renderApp(
indexPath: string,
routes: RouteConfig,
- context: RenderContext
+ context?: RenderContext
): RenderResult {
return renderRoutedApp(
<RouteWithChildRoutes path={indexPath} childRoutes={routes} />,
metrics = DEFAULT_METRICS,
store = getStore(),
appState = mockAppState(),
- history = createMemoryHistory()
+ history = createMemoryHistory(),
+ languages = {}
}: RenderContext = {}
): RenderResult {
history.push(`/${indexPath}`);
<IntlProvider defaultLocale="en" locale="en">
<MetricsContext.Provider value={metrics}>
<Provider store={store}>
- <CurrentUserContextProvider currentUser={currentUser}>
- <AppStateContextProvider appState={appState}>
- <Router history={history}>{children}</Router>
- </AppStateContextProvider>
- </CurrentUserContextProvider>
+ <LanguagesContext.Provider value={languages}>
+ <CurrentUserContextProvider currentUser={currentUser}>
+ <AppStateContextProvider appState={appState}>
+ <Router history={history}>{children}</Router>
+ </AppStateContextProvider>
+ </CurrentUserContextProvider>
+ </LanguagesContext.Provider>
</Provider>
</MetricsContext.Provider>
</IntlProvider>
*/
import { Dict, Rule, RuleActivation } from './types';
+export interface RuleRepository {
+ key: string;
+ language: string;
+ name: string;
+}
+
export interface GetRulesAppResponse {
canWrite?: boolean;
- repositories: { key: string; language: string; name: string }[];
+ repositories: RuleRepository[];
}
export interface SearchRulesResponse {
Deprecated = 'DEPRECATED',
Removed = 'REMOVED'
}
+
+export interface SearchRulesQuery {
+ activation?: boolean | string;
+ active_severities?: string;
+ asc?: boolean | string;
+ available_since?: string;
+ cwe?: string;
+ f?: string;
+ facets?: string;
+ include_external?: boolean | string;
+ inheritance?: string;
+ is_template?: boolean | string;
+ languages?: string;
+ owaspTop10?: string;
+ p?: number;
+ ps?: number;
+ q?: string;
+ qprofile?: string;
+ repositories?: string;
+ rule_key?: string;
+ s?: string;
+ sansTop25?: string;
+ severities?: string;
+ sonarsourceSecurity?: string;
+ statuses?: string;
+ tags?: string;
+ template_key?: string;
+ types?: string;
+}