--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { MultiSelectMenu } from './input/MultiSelectMenu';
+
+interface Props {
+ allowNewElements?: boolean;
+ allowSearch?: boolean;
+ createElementLabel: string;
+ elements: string[];
+ headerLabel: string;
+ listSize?: number;
+ noResultsLabel: string;
+ onSearch?: (query: string) => Promise<void>;
+ onSelect: (item: string) => void;
+ onUnselect: (item: string) => void;
+ searchInputAriaLabel: string;
+ selectedElements: string[];
+}
+
+const LIST_SIZE = 10;
+
+export function MultiSelector(props: Readonly<Props>) {
+ const {
+ allowNewElements,
+ createElementLabel,
+ headerLabel,
+ noResultsLabel,
+ searchInputAriaLabel,
+ selectedElements,
+ elements,
+ allowSearch = true,
+ listSize = LIST_SIZE,
+ } = props;
+
+ return (
+ <MultiSelectMenu
+ allowNewElements={allowNewElements}
+ allowSearch={allowSearch}
+ createElementLabel={createElementLabel}
+ elements={elements}
+ headerNode={<div className="sw-mt-4 sw-font-semibold">{headerLabel}</div>}
+ listSize={listSize}
+ noResultsLabel={noResultsLabel}
+ onSearch={props.onSearch}
+ onSelect={props.onSelect}
+ onUnselect={props.onUnselect}
+ placeholder={searchInputAriaLabel}
+ searchInputAriaLabel={searchInputAriaLabel}
+ selectedElements={selectedElements}
+ validateSearchInput={validateElement}
+ />
+ );
+}
+
+export function validateElement(value: string) {
+ // Allow only a-z, 0-9, '+', '-', '#', '.'
+ return value.toLowerCase().replace(/[^-a-z0-9+#.]/gi, '');
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { MultiSelectMenu } from './input/MultiSelectMenu';
-
-interface Props {
- allowNewElements?: boolean;
- createElementLabel: string;
- headerLabel: string;
- noResultsLabel: string;
- onSearch: (query: string) => Promise<void>;
- onSelect: (item: string) => void;
- onUnselect: (item: string) => void;
- searchInputAriaLabel: string;
- selectedTags: string[];
- tags: string[];
-}
-
-const LIST_SIZE = 10;
-
-export function TagsSelector(props: Props) {
- const {
- allowNewElements,
- createElementLabel,
- headerLabel,
- noResultsLabel,
- searchInputAriaLabel,
- selectedTags,
- tags,
- } = props;
-
- return (
- <MultiSelectMenu
- allowNewElements={allowNewElements}
- createElementLabel={createElementLabel}
- elements={tags}
- headerNode={<div className="sw-mt-4 sw-font-semibold">{headerLabel}</div>}
- listSize={LIST_SIZE}
- noResultsLabel={noResultsLabel}
- onSearch={props.onSearch}
- onSelect={props.onSelect}
- onUnselect={props.onUnselect}
- placeholder={searchInputAriaLabel}
- searchInputAriaLabel={searchInputAriaLabel}
- selectedElements={selectedTags}
- validateSearchInput={validateTag}
- />
- );
-}
-
-export function validateTag(value: string) {
- // Allow only a-z, 0-9, '+', '-', '#', '.'
- return value.toLowerCase().replace(/[^-a-z0-9+#.]/gi, '');
-}
import { useState } from 'react';
import { renderWithContext } from '../../helpers/testUtils';
import { FCProps } from '../../types/misc';
+import { MultiSelector } from '../MultiSelector';
import { Tags } from '../Tags';
-import { TagsSelector } from '../TagsSelector';
it('should display "no tags"', () => {
renderTags({ tags: [] });
const [selectedTags, setSelectedTags] = useState<string[]>(overrides.tags ?? ['tag1']);
const overlay = (
- <TagsSelector
+ <MultiSelector
createElementLabel="create new tag"
+ elements={['tag1', 'tag2', 'tag3']}
headerLabel="edit tags"
noResultsLabel="no results"
onSearch={jest.fn().mockResolvedValue(undefined)}
}
}}
searchInputAriaLabel="search"
- selectedTags={selectedTags}
- tags={['tag1', 'tag2', 'tag3']}
+ selectedElements={selectedTags}
/>
);
export * from './MainMenu';
export * from './MainMenuItem';
export * from './MetricsRatingBadge';
+export * from './MultiSelector';
export * from './NavBarTabs';
export * from './NewCodeLegend';
export * from './OutsideClickHandler';
export * from './SpotlightTour';
export * from './Table';
export * from './Tags';
-export * from './TagsSelector';
export * from './Text';
export * from './Title';
export { ToggleButton } from './ToggleButton';
interface Props {
allowNewElements?: boolean;
+ allowSearch?: boolean;
allowSelection?: boolean;
createElementLabel: string;
elements: string[];
inputId?: string;
listSize: number;
noResultsLabel: string;
- onSearch: (query: string) => Promise<void>;
+ onSearch?: (query: string) => Promise<void>;
onSelect: (item: string) => void;
onUnselect: (item: string) => void;
placeholder: string;
};
onSearchQuery = (query: string) => {
- this.setState({ activeIdx: 0, loading: true, query });
- this.props.onSearch(query).then(this.stopLoading, this.stopLoading);
+ if (this.props.onSearch) {
+ this.setState({ activeIdx: 0, loading: true, query });
+ this.props.onSearch(query).then(this.stopLoading, this.stopLoading);
+ }
};
onSelectItem = (item: string) => {
return {
unselectedElements: difference(this.props.elements, this.props.selectedElements).slice(
0,
- listSize - state.selectedElements.length
+ listSize - state.selectedElements.length,
),
};
});
render() {
const {
+ allowSearch = true,
allowSelection = true,
allowNewElements = true,
createElementLabel,
return (
<div ref={(div) => (this.container = div)}>
- <div className="sw-px-3">
- <InputSearch
- autoFocus
- className="sw-mt-1"
- id={inputId}
- loading={this.state.loading}
- onChange={this.handleSearchChange}
- placeholder={this.props.placeholder}
- searchInputAriaLabel={searchInputAriaLabel}
- size="full"
- value={query}
- />
- </div>
- <ItemHeader>{headerNode}</ItemHeader>
+ {allowSearch && (
+ <>
+ <div className="sw-px-3">
+ <InputSearch
+ autoFocus
+ className="sw-mt-1"
+ id={inputId}
+ loading={this.state.loading}
+ onChange={this.handleSearchChange}
+ placeholder={this.props.placeholder}
+ searchInputAriaLabel={searchInputAriaLabel}
+ size="full"
+ value={query}
+ />
+ </div>
+ <ItemHeader>{headerNode}</ItemHeader>
+ </>
+ )}
<ul
- className={classNames('sw-mt-2', {
+ className={classNames({
+ 'sw-mt-2': allowSearch,
'sw-max-h-abs-200 sw-overflow-y-auto': isFixedHeight,
})}
>
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(11);
});
- // eslint-disable-next-line jest/no-disabled-tests
- it.skip('filters by search', async () => {
+ it('filters by search', async () => {
const { ui, user } = getPageObjects();
renderCodingRulesApp(mockCurrentUser());
await ui.appLoaded();
await user.click(ui.bulkChangeButton.get());
await user.click(ui.activateIn.get());
- const dialog = ui.bulkChangeDialog(1).get();
- expect(dialog).toBeInTheDocument();
+ const dialog = ui.bulkChangeDialog(1);
+ expect(dialog.get()).toBeInTheDocument();
- selectEvent.openMenu(ui.activateInSelect.get());
- expect(ui.noQualityProfiles.get(dialog)).toBeInTheDocument();
+ await user.click(ui.activateInSelect.get());
+
+ expect(ui.noQualityProfiles.get(dialog.get())).toBeInTheDocument();
});
it('should be able to bulk activate quality profile', async () => {
* 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,
+ ButtonSecondary,
+ ChevronDownIcon,
+ Dropdown,
+ ItemButton,
+ PopupPlacement,
+ PopupZLevel,
+} from 'design-system';
import * as React from 'react';
import { Profile } from '../../../api/quality-profiles';
-import Dropdown from '../../../components/controls/Dropdown';
import Tooltip from '../../../components/controls/Tooltip';
-import { Button } from '../../../components/controls/buttons';
-import { PopupPlacement } from '../../../components/ui/popups';
import { translate } from '../../../helpers/l10n';
import { Dict } from '../../../types/types';
import { Query } from '../query';
closeModal = () => this.setState({ action: undefined, modal: false, profile: undefined });
- handleActivateClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
- event.preventDefault();
- event.currentTarget.blur();
+ handleActivateClick = () => {
this.setState({ action: 'activate', modal: true, profile: undefined });
};
- handleActivateInProfileClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
- event.preventDefault();
- event.currentTarget.blur();
+ handleActivateInProfileClick = () => {
this.setState({ action: 'activate', modal: true, profile: this.getSelectedProfile() });
};
- handleDeactivateClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
- event.preventDefault();
- event.currentTarget.blur();
+ handleDeactivateClick = () => {
this.setState({ action: 'deactivate', modal: true, profile: undefined });
};
- handleDeactivateInProfileClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
- event.preventDefault();
- event.currentTarget.blur();
+ handleDeactivateInProfileClick = () => {
this.setState({ action: 'deactivate', modal: true, profile: this.getSelectedProfile() });
};
if (!canBulkChange) {
return (
<Tooltip overlay={translate('coding_rules.can_not_bulk_change')}>
- <Button className="js-bulk-change" disabled>
- {translate('bulk_change')}
- </Button>
+ <ButtonPrimary disabled>{translate('bulk_change')}</ButtonPrimary>
</Tooltip>
);
}
return (
<>
<Dropdown
- overlayPlacement={PopupPlacement.BottomLeft}
+ id="issue-bulkaction-menu"
+ size="auto"
+ placement={PopupPlacement.BottomRight}
+ zLevel={PopupZLevel.Global}
overlay={
- <ul className="menu">
- <li>
- <a href="#" onClick={this.handleActivateClick}>
- {translate('coding_rules.activate_in')}…
- </a>
- </li>
+ <>
+ <ItemButton onClick={this.handleActivateClick}>
+ {translate('coding_rules.activate_in')}
+ </ItemButton>
+
{allowActivateOnProfile && profile && (
- <li>
- <a href="#" onClick={this.handleActivateInProfileClick}>
- {translate('coding_rules.activate_in')} <strong>{profile.name}</strong>
- </a>
- </li>
+ <ItemButton onClick={this.handleActivateInProfileClick}>
+ {translate('coding_rules.activate_in')} <strong>{profile.name}</strong>
+ </ItemButton>
)}
- <li>
- <a href="#" onClick={this.handleDeactivateClick}>
- {translate('coding_rules.deactivate_in')}…
- </a>
- </li>
+
+ <ItemButton onClick={this.handleDeactivateClick}>
+ {translate('coding_rules.deactivate_in')}
+ </ItemButton>
+
{allowDeactivateOnProfile && profile && (
- <li>
- <a href="#" onClick={this.handleDeactivateInProfileClick}>
- {translate('coding_rules.deactivate_in')} <strong>{profile.name}</strong>
- </a>
- </li>
+ <ItemButton onClick={this.handleDeactivateInProfileClick}>
+ {translate('coding_rules.deactivate_in')} <strong>{profile.name}</strong>
+ </ItemButton>
)}
- </ul>
+ </>
}
>
- <Button className="js-bulk-change">{translate('bulk_change')}</Button>
+ <ButtonSecondary>
+ {translate('bulk_change')}
+ <ChevronDownIcon className="sw-ml-1" />
+ </ButtonSecondary>
</Dropdown>
{this.state.modal && this.state.action && (
<BulkChangeModal
* 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, FormField, Modal, Spinner } from 'design-system';
import * as React from 'react';
-import { bulkActivateRules, bulkDeactivateRules, Profile } from '../../../api/quality-profiles';
+import { Profile, bulkActivateRules, bulkDeactivateRules } from '../../../api/quality-profiles';
import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
-import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
-import Modal from '../../../components/controls/Modal';
-import Select from '../../../components/controls/Select';
-import { Alert } from '../../../components/ui/Alert';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import { Languages } from '../../../types/languages';
import { MetricType } from '../../../types/metrics';
import { Dict } from '../../../types/types';
import { Query, serializeQuery } from '../query';
+import { QualityProfileSelector } from './QualityProfileSelector';
interface Props {
action: string;
interface State {
finished: boolean;
- modalWrapperNode: HTMLDivElement | null;
results: ActivationResult[];
selectedProfiles: Profile[];
submitting: boolean;
this.state = {
finished: false,
- modalWrapperNode: null,
results: [],
selectedProfiles,
submitting: false,
this.mounted = false;
}
- setModalWrapperNode = (node: HTMLDivElement | null) => {
- this.setState({ modalWrapperNode: node });
- };
-
handleProfileSelect = (selectedProfiles: Profile[]) => {
this.setState({ selectedProfiles });
};
? languages[profile.language].name
: profile.language;
return (
- <Alert key={result.profile} variant={result.failed === 0 ? 'success' : 'warning'}>
+ <FlagMessage
+ className="sw-mb-4"
+ key={result.profile}
+ variant={result.failed === 0 ? 'success' : 'warning'}
+ >
{result.failed
? translateWithParameters(
'coding_rules.bulk_change.warning',
language,
result.succeeded,
)}
- </Alert>
+ </FlagMessage>
);
};
renderProfileSelect = () => {
const profiles = this.getAvailableQualityProfiles();
+ const { selectedProfiles } = this.state;
return (
- <Select
- aria-labelledby="coding-rules-bulk-change-profile-header"
- isMulti
- isClearable={false}
- isSearchable
- menuPortalTarget={this.state.modalWrapperNode}
- menuPosition="fixed"
- noOptionsMessage={() => translate('coding_rules.bulk_change.no_quality_profile')}
- getOptionLabel={(profile) => `${profile.name} - ${profile.languageName}`}
- getOptionValue={(profile) => profile.key}
+ <QualityProfileSelector
+ inputId="coding-rules-bulk-change-profile-select"
+ profiles={profiles}
+ selectedProfiles={selectedProfiles}
onChange={this.handleProfileSelect}
- options={profiles}
- value={this.state.selectedProfiles}
/>
);
};
MetricType.Integer,
)} ${translate('coding_rules._rules')})`;
- return (
- <Modal contentLabel={header} onRequestClose={this.props.onClose} size="medium">
- <div ref={this.setModalWrapperNode}>
- <form onSubmit={this.handleFormSubmit}>
- <header className="modal-head">
- <h2>{header}</h2>
- </header>
+ const FORM_ID = `coding-rules-bulk-change-form-${action}`;
- <div className="modal-body modal-container">
- {this.state.results.map(this.renderResult)}
-
- {!this.state.finished && !this.state.submitting && (
- <div className="modal-field huge-spacer-bottom">
- <h3>
- <label id="coding-rules-bulk-change-profile-header">
- {action === 'activate'
- ? translate('coding_rules.activate_in')
- : translate('coding_rules.deactivate_in')}
- </label>
- </h3>
- {profile ? (
- <span>
- {profile.name}
- {' — '}
- {translate('are_you_sure')}
- </span>
- ) : (
- this.renderProfileSelect()
- )}
- </div>
- )}
- </div>
+ const formBody = (
+ <form id={FORM_ID} onSubmit={this.handleFormSubmit}>
+ <div>
+ {this.state.results.map(this.renderResult)}
- <footer className="modal-foot">
- {this.state.submitting && <i className="spinner spacer-right" />}
- {!this.state.finished && (
- <SubmitButton disabled={this.state.submitting} id="coding-rules-submit-bulk-change">
- {translate('apply')}
- </SubmitButton>
+ {!this.state.finished && !this.state.submitting && (
+ <FormField
+ id="coding-rules-bulk-change-profile-header"
+ htmlFor="coding-rules-bulk-change-profile-select"
+ label={
+ action === 'activate'
+ ? translate('coding_rules.activate_in')
+ : translate('coding_rules.deactivate_in')
+ }
+ >
+ {profile ? (
+ <span>
+ {profile.name}
+ {' — '}
+ {translate('are_you_sure')}
+ </span>
+ ) : (
+ this.renderProfileSelect()
)}
- <ResetButtonLink onClick={this.props.onClose}>
- {this.state.finished ? translate('close') : translate('cancel')}
- </ResetButtonLink>
- </footer>
- </form>
+ </FormField>
+ )}
</div>
- </Modal>
+ </form>
+ );
+
+ return (
+ <Modal
+ headerTitle={header}
+ isScrollable
+ onClose={this.props.onClose}
+ body={<Spinner loading={this.state.submitting}>{formBody}</Spinner>}
+ primaryButton={
+ !this.state.finished && (
+ <ButtonPrimary
+ autoFocus
+ type="submit"
+ disabled={this.state.submitting || this.state.selectedProfiles.length === 0}
+ form={FORM_ID}
+ >
+ {translate('apply')}
+ </ButtonPrimary>
+ )
+ }
+ secondaryButtonLabel={this.state.finished ? translate('close') : 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 { InputSearch } from 'design-system';
import { keyBy } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import RuleListItem from './RuleListItem';
const PAGE_SIZE = 100;
+const MAX_SEARCH_LENGTH = 200;
const LIMIT_BEFORE_LOAD_MORE = 5;
interface Props {
<div className="layout-page-main-inner">
<A11ySkipTarget anchor="rules_main" />
<div className="display-flex-space-between">
+ <InputSearch
+ className="sw-min-w-abs-250 sw-max-w-abs-350 sw-mr-4"
+ id="coding-rules-search"
+ maxLength={MAX_SEARCH_LENGTH}
+ minLength={2}
+ onChange={this.handleSearch}
+ placeholder={translate('search.search_for_rules')}
+ value={query.searchQuery ?? ''}
+ size="auto"
+ />
+
{openRule ? (
<a
className="js-back display-inline-flex-center link-no-underline"
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { KeyboardHint } from 'design-system';
import * as React from 'react';
import PageCounter from '../../../components/common/PageCounter';
-import PageShortcutsTooltip from '../../../components/ui/PageShortcutsTooltip';
import { translate } from '../../../helpers/l10n';
import { Paging } from '../../../types/types';
export default function PageActions(props: PageActionsProps) {
return (
- <div className="display-flex-center">
- <PageShortcutsTooltip
- className="big-spacer-right"
- leftAndRightLabel={translate('issues.to_navigate')}
- upAndDownLabel={translate('coding_rules.to_select_rules')}
- />
+ <div className="sw-body-sm sw-flex sw-items-center sw-gap-6 sw-justify-end sw-flex-1">
+ <KeyboardHint title={translate('coding_rules.to_select_rules')} command="ArrowUp ArrowDown" />
+ <KeyboardHint title={translate('coding_rules.to_navigate')} command="ArrowLeft ArrowRight" />
{props.paging && (
<PageCounter
- className="spacer-left"
+ className="sw-ml-2"
current={props.selectedIndex}
label={translate('coding_rules._rules')}
total={props.paging.total}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 {
+ Dropdown,
+ InputMultiSelect,
+ MultiSelector,
+ PopupPlacement,
+ PopupZLevel,
+} from 'design-system';
+import * as React from 'react';
+import { Profile } from '../../../api/quality-profiles';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ inputId?: string;
+ profiles: Profile[];
+ onChange: (selected: Profile[]) => void;
+ selectedProfiles: Profile[];
+}
+
+const LIST_SIZE = 0;
+
+export function QualityProfileSelector(props: Readonly<Props>) {
+ const { inputId, onChange, selectedProfiles, profiles } = props;
+
+ const onSelect = React.useCallback(
+ (selected: string) => {
+ const profileFound = profiles.find(
+ (profile) => `${profile.name} - ${profile.languageName}` === selected,
+ );
+ if (profileFound) {
+ onChange([profileFound, ...selectedProfiles]);
+ }
+ },
+ [profiles, onChange, selectedProfiles],
+ );
+
+ const onUnselect = React.useCallback(
+ (selected: string) => {
+ const selectedProfilesWithoutUnselected = selectedProfiles.filter(
+ (profile) => `${profile.name} - ${profile.languageName}` !== selected,
+ );
+ onChange(selectedProfilesWithoutUnselected);
+ },
+ [onChange, selectedProfiles],
+ );
+
+ return (
+ <Dropdown
+ allowResizing
+ closeOnClick={false}
+ id="quality-profile-selector"
+ overlay={
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
+ <div onMouseDown={handleMousedown}>
+ <MultiSelector
+ allowSearch={false}
+ createElementLabel="" // Cannot create
+ headerLabel={translate('coding_rules.select_profile')}
+ noResultsLabel={translate('coding_rules.bulk_change.no_quality_profile')}
+ onSelect={onSelect}
+ onUnselect={onUnselect}
+ searchInputAriaLabel={translate('search.search_for_profiles')}
+ selectedElements={selectedProfiles.map(
+ (profile) => `${profile.name} - ${profile.languageName}`,
+ )}
+ elements={profiles.map((profile) => `${profile.name} - ${profile.languageName}`)}
+ listSize={LIST_SIZE}
+ />
+ </div>
+ }
+ placement={PopupPlacement.BottomLeft}
+ zLevel={PopupZLevel.Global}
+ >
+ {({ onToggleClick }): JSX.Element => (
+ <InputMultiSelect
+ className="sw-w-full sw-mb-2"
+ id={inputId}
+ onClick={onToggleClick}
+ placeholder={translate('select_verb')}
+ selectedLabel={translate('coding_rules.selected_profiles')}
+ count={selectedProfiles.length}
+ />
+ )}
+ </Dropdown>
+ );
+}
+
+/*
+ * Prevent click from triggering a change of focus that would close the dropdown
+ */
+function handleMousedown(e: React.MouseEvent) {
+ if ((e.target as HTMLElement).tagName !== 'INPUT') {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { TagsSelector } from 'design-system';
+import { MultiSelector } from 'design-system';
import { difference, uniq, without } from 'lodash';
import * as React from 'react';
import { getRuleTags } from '../../../api/rules';
render() {
const availableTags = difference(this.state.searchResult, this.props.tags);
return (
- <TagsSelector
+ <MultiSelector
createElementLabel={translate('coding_rules.create_tag')}
headerLabel={translate('tags')}
searchInputAriaLabel={translate('search.search_for_tags')}
onSearch={this.onSearch}
onSelect={this.onSelect}
onUnselect={this.onUnselect}
- selectedTags={this.props.tags}
- tags={availableTags}
+ selectedElements={this.props.tags}
+ elements={availableTags}
/>
);
}
// Bulk change
bulkChangeButton: byRole('button', { name: 'bulk_change' }),
- activateIn: byRole('link', { name: 'coding_rules.activate_in…' }),
- deactivateIn: byRole('link', { name: 'coding_rules.deactivate_in…' }),
+ activateIn: byRole('menuitem', { name: 'coding_rules.activate_in' }),
+ deactivateIn: byRole('menuitem', { name: 'coding_rules.deactivate_in' }),
bulkChangeDialog: (count: number, activate = true) =>
byRole('dialog', {
name: `coding_rules.${
import {
Dropdown,
InputMultiSelect,
+ MultiSelector,
PopupPlacement,
PopupZLevel,
- TagsSelector,
} from 'design-system';
import * as React from 'react';
import { translate, translateWithParameters } from '../../../helpers/l10n';
overlay={
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div onMouseDown={handleMousedown}>
- <TagsSelector
+ <MultiSelector
allowNewElements={allowCreation}
createElementLabel={translateWithParameters('issue.create_tag')}
headerLabel={translate('issue_bulk_change.select_tags')}
onSelect={onSelect}
onUnselect={onUnselect}
searchInputAriaLabel={translate('search.search_for_tags')}
- selectedTags={selectedTags}
+ selectedElements={selectedTags}
onSearch={doSearch}
- tags={searchResults}
+ elements={searchResults}
/>
</div>
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { Tags, TagsSelector } from 'design-system';
+import { MultiSelector, Tags } from 'design-system';
import { difference, without } from 'lodash';
import React, { useState } from 'react';
import { searchProjectTags, setApplicationTags, setProjectTags } from '../../../../api/components';
};
return (
- <TagsSelector
+ <MultiSelector
headerLabel={translate('tags')}
searchInputAriaLabel={translate('search.search_for_tags')}
createElementLabel={translate('issue.create_tag')}
onSearch={onSearch}
onSelect={onSelect}
onUnselect={onUnselect}
- selectedTags={selectedTags}
- tags={availableTags}
+ selectedElements={selectedTags}
+ elements={availableTags}
/>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import classNames from 'classnames';
import * as React from 'react';
import { formatMeasure } from '../../helpers/measures';
+import { MetricType } from '../../types/metrics';
export interface PageCounterProps {
className?: string;
export default function PageCounter({ className, current, label, total }: PageCounterProps) {
return (
- <div className={classNames('display-inline-block', className)}>
- <strong className="little-spacer-right">
- {current !== undefined && formatMeasure(current + 1, 'INT') + ' / '}
- <span className="it__page-counter-total">{formatMeasure(total, 'INT')}</span>
+ <div className={className}>
+ <strong className="sw-ml-1">
+ {current !== undefined && formatMeasure(current + 1, MetricType.Integer) + ' / '}
+ <span className="it__page-counter-total">{formatMeasure(total, MetricType.Integer)}</span>
</strong>
{label}
</div>
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should render correctly 1`] = `
-<div
- className="display-inline-block"
->
+<div>
<strong
- className="little-spacer-right"
+ className="sw-ml-1"
>
124 /
<span
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { TagsSelector } from 'design-system';
+import { MultiSelector } from 'design-system';
import { difference, noop, without } from 'lodash';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
const availableTags = difference(searchResult, selectedTags);
return (
- <TagsSelector
+ <MultiSelector
headerLabel={translate('issue.tags')}
searchInputAriaLabel={translate('search.search_for_tags')}
createElementLabel={translate('issue.create_tag')}
onSearch={onSearch}
onSelect={onSelect}
onUnselect={onUnselect}
- selectedTags={selectedTags}
- tags={availableTags}
+ selectedElements={selectedTags}
+ elements={availableTags}
/>
);
}
search.search_for_files=Search for files...
search.search_for_modules=Search for modules...
search.search_for_metrics=Search for metrics...
+search.search_for_profiles=Search for Quality Profiles...
search.tooShort=Please enter at least {0} characters
global_search.shortcut_hint=Hint: Press 'S' from anywhere to open this search bar.
coding_rules._rules=rules
coding_rules.show_template=Show Template
coding_rules.skip_to_filters=Skip to rules filters
-coding_rules.to_select_rules=to select rules
+coding_rules.to_select_rules=Select rules
+coding_rules.to_navigate=Navtigate to rule
coding_rules.type.tooltip.CODE_SMELL=Code Smell Detection Rule
coding_rules.type.tooltip.BUG=Bug Detection Rule
coding_rules.type.tooltip.VULNERABILITY=Vulnerability Detection Rule
coding_rules.detail.extend_description.form=Extend this rule's description
coding_rules.create_tag=Create Tag
+coding_rules.select_profile=Select Profile
+coding_rules.selected_profiles=Selected Profiles
+
rule.impact.severity.tooltip=Issues found for this rule will have a {severity} impact on the {quality} of your software.
rule.clean_code_attribute_category.CONSISTENT=Consistency