'/project/links',
'/project/import_export',
'/project/quality_gate',
+ '/project/quality_profiles',
];
export default function GlobalContainer() {
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { addGlobalSuccessMessage } from 'design-system';
import { differenceBy } from 'lodash';
import * as React from 'react';
import {
} from '../../api/quality-profiles';
import withComponentContext from '../../app/components/componentContext/withComponentContext';
import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
-import { addGlobalSuccessMessage } from '../../helpers/globalMessages';
import { translateWithParameters } from '../../helpers/l10n';
import { isDefined } from '../../helpers/types';
import { Component } from '../../types/types';
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import {
+ ActionCell,
+ ButtonPrimary,
+ ContentCell,
+ HelperHintIcon,
+ InteractiveIcon,
+ LargeCenteredLayout,
+ Link,
+ PageContentFontWrapper,
+ PencilIcon,
+ Spinner,
+ Table,
+ TableRow,
+ TableRowInteractive,
+ Title,
+} from 'design-system';
import { groupBy, orderBy } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { Profile } from '../../api/quality-profiles';
import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
-import Link from '../../components/common/Link';
import HelpTooltip from '../../components/controls/HelpTooltip';
-import { Button } from '../../components/controls/buttons';
import Suggestions from '../../components/embed-docs-modal/Suggestions';
-import EditIcon from '../../components/icons/EditIcon';
-import PlusCircleIcon from '../../components/icons/PlusCircleIcon';
import { translate } from '../../helpers/l10n';
import { getRulesUrl } from '../../helpers/urls';
import { Component } from '../../types/types';
const profilesByLanguage = groupBy(allProfiles, 'language');
const orderedProfiles = orderBy(projectProfiles, (p) => p.profile.languageName);
+ const COLUMN_WIDTHS_WITH_PURGE_SETTING = ['auto', 'auto', 'auto', '5%'];
+
+ const header = (
+ <TableRow>
+ <ContentCell>{translate('language')}</ContentCell>
+ <ContentCell>{translate('project_quality_profile.current')}</ContentCell>
+ <ContentCell>{translate('coding_rules.filters.activation.active_rules')}</ContentCell>
+ <ActionCell>{translate('actions')}</ActionCell>
+ </TableRow>
+ );
+
return (
- <div className="page page-limited" id="project-quality-profiles">
- <Suggestions suggestions="project_quality_profiles" />
- <Helmet defer={false} title={translate('project_quality_profiles.page')} />
- <A11ySkipTarget anchor="profiles_main" />
+ <LargeCenteredLayout id="project-quality-profiles">
+ <PageContentFontWrapper className="sw-my-8 sw-body-sm">
+ <Suggestions suggestions="project_quality_profiles" />
+ <Helmet defer={false} title={translate('project_quality_profiles.page')} />
+ <A11ySkipTarget anchor="profiles_main" />
- <header className="page-header">
- <div className="page-title display-flex-center">
- <h1>{translate('project_quality_profiles.page')} </h1>
+ <header className="sw-mb-2 sw-flex sw-items-center">
+ <Title>{translate('project_quality_profiles.page')}</Title>
<HelpTooltip
- className="spacer-left"
- overlay={
- <div className="big-padded-top big-padded-bottom">
- {translate('quality_profiles.list.projects.help')}
- </div>
- }
- />
- </div>
- </header>
+ className="sw-ml-2 sw-mb-4"
+ overlay={translate('quality_profiles.list.projects.help')}
+ >
+ <HelperHintIcon aria-label="help-tooltip" />
+ </HelpTooltip>
+ </header>
- <div className="boxed-group">
- <h2 className="boxed-group-header">{translate('project_quality_profile.subtitle')}</h2>
+ <div>
+ <p>{translate('project_quality_profiles.page.description')}</p>
+ <div className="sw-mt-16">
+ <Spinner loading={loading}>
+ {!loading && orderedProfiles.length > 0 && (
+ <Table
+ noHeaderTopBorder
+ className="sw-w-[60%]"
+ columnCount={COLUMN_WIDTHS_WITH_PURGE_SETTING.length}
+ columnWidths={COLUMN_WIDTHS_WITH_PURGE_SETTING}
+ header={header}
+ >
+ {orderedProfiles.map((projectProfile) => {
+ const { profile, selected } = projectProfile;
- <div className="boxed-group-inner">
- <p className="big-spacer-bottom">
- {translate('project_quality_profiles.page.description')}
- </p>
+ return (
+ <TableRowInteractive key={profile.language}>
+ <ContentCell>
+ <span>{profile.languageName}</span>
+ </ContentCell>
+ <ContentCell>
+ <span>
+ {!selected && profile.isDefault ? (
+ <em>{translate('project_quality_profile.instance_default')}</em>
+ ) : (
+ <>
+ {profile.name}
+ {profile.isBuiltIn && (
+ <BuiltInQualityProfileBadge className="spacer-left" />
+ )}
+ </>
+ )}
+ </span>
+ </ContentCell>
+ <ContentCell>
+ <Link to={getRulesUrl({ activation: 'true', qprofile: profile.key })}>
+ {profile.activeRuleCount}
+ </Link>
+ </ContentCell>
- {loading && <i className="spinner spacer-left" />}
+ <ActionCell>
+ <InteractiveIcon
+ Icon={PencilIcon}
+ aria-label={translate('project_quality_profile.change_profile')}
+ onClick={() => {
+ props.onOpenSetProfileModal(projectProfile);
+ }}
+ size="small"
+ stopPropagation={false}
+ />
+ </ActionCell>
+ </TableRowInteractive>
+ );
+ })}
+ </Table>
+ )}
- {!loading && orderedProfiles.length > 0 && (
- <table className="data zebra">
- <thead>
- <tr>
- <th>{translate('language')}</th>
- <th className="thin nowrap">{translate('project_quality_profile.current')}</th>
- <th className="thin nowrap text-right">
- {translate('coding_rules.filters.activation.active_rules')}
- </th>
- <th aria-label={translate('actions')} />
- </tr>
- </thead>
- <tbody>
- {orderedProfiles.map((projectProfile) => {
- const { profile, selected } = projectProfile;
- return (
- <tr key={profile.language}>
- <td>{profile.languageName}</td>
- <td className="thin nowrap">
- <span className="display-inline-flex-center">
- {!selected && profile.isDefault ? (
- <em>{translate('project_quality_profile.instance_default')}</em>
- ) : (
- <>
- {profile.name}
- {profile.isBuiltIn && (
- <BuiltInQualityProfileBadge className="spacer-left" />
- )}
- </>
- )}
- </span>
- </td>
- <td className="nowrap text-right">
- <Link to={getRulesUrl({ activation: 'true', qprofile: profile.key })}>
- {profile.activeRuleCount}
- </Link>
- </td>
- <td className="text-right">
- <Button
- onClick={() => {
- props.onOpenSetProfileModal(projectProfile);
- }}
- >
- <EditIcon className="spacer-right" />
- {translate('project_quality_profile.change_profile')}
- </Button>
- </td>
- </tr>
- );
- })}
- </tbody>
- </table>
- )}
+ <div className="sw-mt-8">
+ <div className="sw-mb-4">
+ {translate('project_quality_profile.add_language.description')}
+ </div>
- <div className="big-spacer-top">
- <h2>{translate('project_quality_profile.add_language.title')}</h2>
+ <ButtonPrimary disabled={loading} onClick={props.onOpenAddLanguageModal}>
+ {translate('project_quality_profile.add_language.action')}
+ </ButtonPrimary>
+ </div>
- <p className="spacer-top big-spacer-bottom">
- {translate('project_quality_profile.add_language.description')}
- </p>
+ {showProjectProfileInModal && (
+ <SetQualityProfileModal
+ availableProfiles={profilesByLanguage[showProjectProfileInModal.profile.language]}
+ component={component}
+ currentProfile={showProjectProfileInModal.profile}
+ onClose={props.onCloseModal}
+ onSubmit={props.onSetProfile}
+ usesDefault={!showProjectProfileInModal.selected}
+ />
+ )}
- <Button disabled={loading} onClick={props.onOpenAddLanguageModal}>
- <PlusCircleIcon className="little-spacer-right" />
- {translate('project_quality_profile.add_language.action')}
- </Button>
+ {showAddLanguageModal && projectProfiles && (
+ <AddLanguageModal
+ profilesByLanguage={profilesByLanguage}
+ onClose={props.onCloseModal}
+ onSubmit={props.onAddLanguage}
+ unavailableLanguages={projectProfiles.map((p) => p.profile.language)}
+ />
+ )}
+ </Spinner>
</div>
-
- {showProjectProfileInModal && (
- <SetQualityProfileModal
- availableProfiles={profilesByLanguage[showProjectProfileInModal.profile.language]}
- component={component}
- currentProfile={showProjectProfileInModal.profile}
- onClose={props.onCloseModal}
- onSubmit={props.onSetProfile}
- usesDefault={!showProjectProfileInModal.selected}
- />
- )}
-
- {showAddLanguageModal && projectProfiles && (
- <AddLanguageModal
- profilesByLanguage={profilesByLanguage}
- onClose={props.onCloseModal}
- onSubmit={props.onAddLanguage}
- unavailableLanguages={projectProfiles.map((p) => p.profile.language)}
- />
- )}
</div>
- </div>
- </div>
+ </PageContentFontWrapper>
+ </LargeCenteredLayout>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-
import userEvent from '@testing-library/user-event';
+import { addGlobalSuccessMessage } from 'design-system';
import selectEvent from 'react-select-event';
import {
ProfileProject,
searchQualityProfiles,
} from '../../../api/quality-profiles';
import handleRequiredAuthorization from '../../../app/utils/handleRequiredAuthorization';
-import { addGlobalSuccessMessage } from '../../../helpers/globalMessages';
import { mockComponent } from '../../../helpers/mocks/component';
import {
RenderContext,
};
});
-jest.mock('../../../helpers/globalMessages', () => {
- const globalMessages = jest.requireActual('../../../helpers/globalMessages');
- return {
- ...globalMessages,
- addGlobalSuccessMessage: jest.fn(),
- };
-});
+jest.mock('design-system', () => ({
+ ...jest.requireActual('design-system'),
+ addGlobalSuccessMessage: jest.fn(),
+}));
jest.mock('../../../app/utils/handleRequiredAuthorization', () => jest.fn());
pageTitle: byText('project_quality_profiles.page'),
pageSubTitle: byText('project_quality_profile.subtitle'),
pageDescription: byText('project_quality_profiles.page.description'),
- helpTooltip: byLabelText('help'),
+ helpTooltip: byLabelText('help-tooltip'),
profileRows: byRole('row'),
addLanguageButton: byRole('button', { name: 'project_quality_profile.add_language.action' }),
modalAddLanguageTitle: byText('project_quality_profile.add_language_modal.title'),
});
expect(ui.pageTitle.get()).toBeInTheDocument();
- expect(ui.pageSubTitle.get()).toBeInTheDocument();
expect(ui.pageDescription.get()).toBeInTheDocument();
- expect(ui.addLanguageButton.get()).toBeInTheDocument();
+ expect(await ui.addLanguageButton.find()).toBeInTheDocument();
await expect(ui.helpTooltip.get()).toHaveATooltipWithContent(
'quality_profiles.list.projects.help',
);
// Updates the page after API call
const htmlRow = byRole('row', {
- name: 'HTML html profile 10 project_quality_profile.change_profile',
+ name: 'HTML html profile 10',
});
expect(ui.htmlLanguage.get()).toBeInTheDocument();
expect(handleRequiredAuthorization).toHaveBeenCalled();
});
-it('should still show page with add language button when api fails', () => {
+it('should still show page with add language button when api fails', async () => {
jest.mocked(searchQualityProfiles).mockRejectedValueOnce(null);
jest.mocked(getProfileProjects).mockRejectedValueOnce(null);
renderProjectQualityProfilesApp();
expect(ui.pageTitle.get()).toBeInTheDocument();
- expect(ui.pageSubTitle.get()).toBeInTheDocument();
expect(ui.pageDescription.get()).toBeInTheDocument();
- expect(ui.addLanguageButton.get()).toBeInTheDocument();
+ expect(await ui.addLanguageButton.find()).toBeInTheDocument();
});
function renderProjectQualityProfilesApp(
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ButtonPrimary, FormField, InputSelect, Modal } from 'design-system';
import { difference } from 'lodash';
import * as React from 'react';
import { Profile } from '../../../api/quality-profiles';
import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
-import Select, { LabelValueSelectOption } from '../../../components/controls/Select';
-import SimpleModal from '../../../components/controls/SimpleModal';
-import { ButtonLink, SubmitButton } from '../../../components/controls/buttons';
+import { LabelValueSelectOption } from '../../../components/controls/Select';
import { translate } from '../../../helpers/l10n';
import { Languages } from '../../../types/languages';
import { Dict } from '../../../types/types';
}))
: [];
- return (
- <SimpleModal
- header={header}
- onClose={props.onClose}
- onSubmit={() => {
- if (language && key) {
- props.onSubmit(key);
- }
- }}
- >
- {({ onCloseClick, onFormSubmit, submitting }) => (
- <>
- <div className="modal-head">
- <h2>{header}</h2>
- </div>
+ const onFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
+ event.preventDefault();
+ if (language && key) {
+ props.onSubmit(key);
+ }
+ };
- <form onSubmit={onFormSubmit}>
- <div className="modal-body">
- <div className="big-spacer-bottom">
- <div className="little-spacer-bottom">
- <label className="text-bold" htmlFor="language">
- {translate('project_quality_profile.add_language_modal.choose_language')}
- </label>
- </div>
- <Select
- className="abs-width-300"
- isDisabled={submitting}
- id="language"
- aria-label={translate(
- 'project_quality_profile.add_language_modal.choose_language',
- )}
- onChange={({ value }: LabelValueSelectOption) => {
- setSelected({ language: value, key: undefined });
- }}
- options={languageOptions}
- />
- </div>
+ const renderForm = (
+ <form id="add-language-quality-profile" onSubmit={onFormSubmit}>
+ <div>
+ <FormField
+ className="sw-mb-4"
+ label={translate('project_quality_profile.add_language_modal.choose_language')}
+ htmlFor="language"
+ >
+ <InputSelect
+ size="full"
+ id="language"
+ aria-label={translate('project_quality_profile.add_language_modal.choose_language')}
+ onChange={({ value }: LabelValueSelectOption) => {
+ setSelected({ language: value, key: undefined });
+ }}
+ options={languageOptions}
+ />
+ </FormField>
- <div className="big-spacer-bottom">
- <div className="little-spacer-bottom">
- <label className="text-bold" htmlFor="profiles">
- {translate('project_quality_profile.add_language_modal.choose_profile')}
- </label>
- </div>
- <Select
- className="abs-width-300"
- isDisabled={submitting || !language}
- id="profiles"
- aria-label={translate(
- 'project_quality_profile.add_language_modal.choose_profile',
- )}
- onChange={({ value }: ProfileOption) => setSelected({ language, key: value })}
- options={profileOptions}
- components={{
- Option: LanguageProfileSelectOption,
- }}
- value={profileOptions.find((o) => o.value === key) ?? null}
- />
- </div>
- </div>
+ <FormField
+ className="sw-mb-4"
+ label={translate('project_quality_profile.add_language_modal.choose_profile')}
+ htmlFor="profiles"
+ >
+ <InputSelect
+ size="full"
+ isDisabled={!language}
+ id="profiles"
+ aria-label={translate('project_quality_profile.add_language_modal.choose_profile')}
+ onChange={({ value }: ProfileOption) => setSelected({ language, key: value })}
+ options={profileOptions}
+ components={{
+ Option: LanguageProfileSelectOption,
+ }}
+ value={profileOptions.find((o) => o.value === key) ?? null}
+ />
+ </FormField>
+ </div>
+ </form>
+ );
- <div className="modal-foot">
- {submitting && <i className="spinner spacer-right" />}
- <SubmitButton disabled={submitting || !language || !key}>
- {translate('save')}
- </SubmitButton>
- <ButtonLink disabled={submitting} onClick={onCloseClick}>
- {translate('cancel')}
- </ButtonLink>
- </div>
- </form>
- </>
- )}
- </SimpleModal>
+ return (
+ <Modal
+ onClose={props.onClose}
+ headerTitle={header}
+ isOverflowVisible
+ body={renderForm}
+ primaryButton={
+ <ButtonPrimary
+ disabled={!language || !key}
+ form="add-language-quality-profile"
+ type="submit"
+ >
+ {translate('save')}
+ </ButtonPrimary>
+ }
+ secondaryButtonLabel={translate('cancel')}
+ />
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Link } from 'design-system';
import * as React from 'react';
import { components, OptionProps } from 'react-select';
import DisableableSelectOption from '../../../components/common/DisableableSelectOption';
-import Link from '../../../components/common/Link';
import { LabelValueSelectOption } from '../../../components/controls/Select';
import { translate } from '../../../helpers/l10n';
import { getQualityProfileUrl } from '../../../helpers/urls';
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import {
+ ButtonPrimary,
+ FlagMessage,
+ InputSelect,
+ LightLabel,
+ Modal,
+ RadioButton,
+} from 'design-system';
import * as React from 'react';
import { Profile } from '../../../api/quality-profiles';
-import Radio from '../../../components/controls/Radio';
-import Select from '../../../components/controls/Select';
-import SimpleModal from '../../../components/controls/SimpleModal';
-import { ButtonLink, SubmitButton } from '../../../components/controls/buttons';
-import { Alert } from '../../../components/ui/Alert';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Component } from '../../../types/types';
import BuiltInQualityProfileBadge from '../../quality-profiles/components/BuiltInQualityProfileBadge';
hasSelectedSysDefault ? p.key === defaultProfile.key : p.key === selected,
);
- return (
- <SimpleModal
- header={header}
- onClose={props.onClose}
- onSubmit={() =>
- props.onSubmit(hasSelectedSysDefault ? undefined : selected, currentProfile.key)
- }
- >
- {({ onCloseClick, onFormSubmit, submitting }) => (
- <>
- <div className="modal-head">
- <h2>{header}</h2>
+ const handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+ event.preventDefault();
+ props.onSubmit(hasSelectedSysDefault ? undefined : selected, currentProfile.key);
+ };
+
+ const renderForm = (
+ <form id="change-quality-profile" onSubmit={handleFormSubmit}>
+ <div>
+ <RadioButton
+ className="sw-mb-4"
+ checked={hasSelectedSysDefault}
+ onCheck={() => setSelected(USE_SYSTEM_DEFAULT)}
+ value={USE_SYSTEM_DEFAULT}
+ >
+ <div className="sw-ml-2">
+ <div>{translate('project_quality_profile.always_use_default')}</div>
+ <LightLabel>
+ <span>
+ {translate('current_noun')}: {defaultProfile?.name}
+ </span>
+ {defaultProfile?.isBuiltIn && <BuiltInQualityProfileBadge className="sw-ml-2" />}
+ </LightLabel>
</div>
+ </RadioButton>
- <form onSubmit={onFormSubmit}>
- <div className="modal-body">
- <div className="big-spacer-bottom">
- <Radio
- className="display-flex-start"
- checked={hasSelectedSysDefault}
- disabled={submitting}
- onCheck={() => setSelected(USE_SYSTEM_DEFAULT)}
- value={USE_SYSTEM_DEFAULT}
- >
- <div className="spacer-left">
- <div className="little-spacer-bottom">
- {translate('project_quality_profile.always_use_default')}
- </div>
- <div className="display-flex-center">
- <span className="text-muted spacer-right">{translate('current_noun')}:</span>
- {defaultProfile.name}
- {defaultProfile.isBuiltIn && (
- <BuiltInQualityProfileBadge className="spacer-left" />
- )}
- </div>
- </div>
- </Radio>
- </div>
+ <RadioButton
+ className="sw-mb-2"
+ checked={!hasSelectedSysDefault}
+ onCheck={(value) => {
+ if (hasSelectedSysDefault) {
+ setSelected(value);
+ }
+ }}
+ value={currentProfile.key}
+ >
+ <div className="sw-ml-2">{translate('project_quality_profile.always_use_specific')}</div>
+ </RadioButton>
- <div className="big-spacer-bottom">
- <Radio
- className="display-flex-start"
- checked={!hasSelectedSysDefault}
- disabled={submitting}
- onCheck={(value) => {
- if (hasSelectedSysDefault) {
- setSelected(value);
- }
- }}
- value={currentProfile.key}
- >
- <div className="spacer-left">
- <div className="little-spacer-bottom">
- {translate('project_quality_profile.always_use_specific')}
- </div>
- <div className="display-flex-center">
- <Select
- className="abs-width-300"
- aria-label={translate('project_quality_profile.always_use_specific')}
- isDisabled={submitting || hasSelectedSysDefault}
- onChange={({ value }: ProfileOption) => setSelected(value)}
- options={profileOptions}
- components={{
- Option: LanguageProfileSelectOption,
- }}
- value={profileOptions.find(
- (option) =>
- option.value ===
- (!hasSelectedSysDefault ? selected : currentProfile.key),
- )}
- />
- </div>
- </div>
- </Radio>
- </div>
+ <InputSelect
+ className="sw-ml-8"
+ aria-label={translate('project_quality_profile.always_use_specific')}
+ isDisabled={hasSelectedSysDefault}
+ onChange={({ value }: ProfileOption) => setSelected(value)}
+ options={profileOptions}
+ components={{
+ Option: LanguageProfileSelectOption,
+ }}
+ value={profileOptions.find(
+ (option) => option.value === (!hasSelectedSysDefault ? selected : currentProfile.key),
+ )}
+ />
- {needsReanalysis && (
- <Alert variant="warning">
- {translate('project_quality_profile.requires_new_analysis')}
- </Alert>
- )}
- </div>
+ {needsReanalysis && (
+ <FlagMessage className="sw-w-full sw-mt-4" variant="warning">
+ {translate('project_quality_profile.requires_new_analysis')}
+ </FlagMessage>
+ )}
+ </div>
+ </form>
+ );
- <div className="modal-foot">
- {submitting && <i className="spinner spacer-right" />}
- <SubmitButton disabled={submitting || !hasChanged}>{translate('save')}</SubmitButton>
- <ButtonLink disabled={submitting} onClick={onCloseClick}>
- {translate('cancel')}
- </ButtonLink>
- </div>
- </form>
- </>
- )}
- </SimpleModal>
+ return (
+ <Modal
+ onClose={props.onClose}
+ headerTitle={header}
+ isOverflowVisible
+ body={renderForm}
+ primaryButton={
+ <ButtonPrimary disabled={!hasChanged} form="change-quality-profile" type="submit">
+ {translate('save')}
+ </ButtonPrimary>
+ }
+ secondaryButtonLabel={translate('cancel')}
+ />
);
}