await ui.appLoaded();
await user.click(ui.tagsDropdown.get());
- expect(ui.tagsMenu.get()).toBeInTheDocument();
RULE_TAGS_MOCK.forEach((tag) => {
expect(ui.tagCheckbox(tag).get()).toBeInTheDocument();
loadMore={this.fetchMoreRules}
ready={!this.state.loading}
total={paging.total}
+ useMIUIButtons
/>
)}
</>
import { colors } from '../../../app/theme';
import DocumentationTooltip from '../../../components/common/DocumentationTooltip';
import Link from '../../../components/common/Link';
-import Dropdown from '../../../components/controls/Dropdown';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import Tooltip from '../../../components/controls/Tooltip';
-import { ButtonLink } from '../../../components/controls/buttons';
import IssueTypeIcon from '../../../components/icons/IssueTypeIcon';
import LinkIcon from '../../../components/icons/LinkIcon';
import DateFormatter from '../../../components/intl/DateFormatter';
import SeverityHelper from '../../../components/shared/SeverityHelper';
import SoftwareImpactPill from '../../../components/shared/SoftwareImpactPill';
import TagsList from '../../../components/tags/TagsList';
-import { PopupPlacement } from '../../../components/ui/popups';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getRuleUrl } from '../../../helpers/urls';
import { Dict, RuleDetails } from '../../../types/types';
return (
<div className="coding-rules-detail-property null-spacer-bottom" data-meta="tags">
- {this.props.canWrite ? (
- <Dropdown
- closeOnClick={false}
- closeOnClickOutside
- overlay={
+ <TagsList
+ allowUpdate={canWrite}
+ tags={allTags.length > 0 ? allTags : [translate('coding_rules.no_tags')]}
+ overlay={
+ canWrite ? (
<RuleDetailsTagsPopup
setTags={this.props.onTagsChange}
sysTags={sysTags}
tags={tags}
/>
- }
- overlayPlacement={PopupPlacement.BottomRight}
- >
- <ButtonLink>
- <TagsList
- allowUpdate={canWrite}
- tags={allTags.length > 0 ? allTags : [translate('coding_rules.no_tags')]}
- />
- </ButtonLink>
- </Dropdown>
- ) : (
- <TagsList
- allowUpdate={canWrite}
- className="display-flex-center"
- tags={allTags.length > 0 ? allTags : [translate('coding_rules.no_tags')]}
- />
- )}
+ ) : undefined
+ }
+ />
</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 { TagsSelector } from 'design-system';
import { difference, uniq, without } from 'lodash';
import * as React from 'react';
import { getRuleTags } from '../../../api/rules';
-import TagsSelector from '../../../components/tags/TagsSelector';
+import { translate } from '../../../helpers/l10n';
export interface Props {
setTags: (tags: string[]) => void;
const availableTags = difference(this.state.searchResult, this.props.tags);
return (
<TagsSelector
- listSize={LIST_SIZE}
+ createElementLabel={translate('coding_rules.create_tag')}
+ headerLabel={translate('tags')}
+ searchInputAriaLabel={translate('search.search_for_tags')}
+ noResultsLabel={translate('no_results')}
onSearch={this.onSearch}
onSelect={this.onSelect}
onUnselect={this.onUnselect}
+++ /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 * as React from 'react';
-import { colors } from '../../../app/theme';
-import { RuleInheritance } from '../../../types/types';
-
-interface Props {
- className?: string;
- inheritance: RuleInheritance;
-}
-
-export default function RuleInheritanceIcon({ className, inheritance, ...other }: Props) {
- const fill = inheritance === 'OVERRIDES' ? colors.red : colors.baseFontColor;
-
- return (
- <svg
- className={className}
- height={16}
- version="1.1"
- viewBox="0 0 16 16"
- width={16}
- xmlSpace="preserve"
- xmlnsXlink="https://www.w3.org/1999/xlink"
- {...other}
- >
- <path
- d="M6.25 12.5a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0zm0-9a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0zm5 1a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0zm.75 0a1.5 1.5 0 0 1-.75 1.297c-.023 2.82-2.023 3.445-3.352 3.867-1.242.39-1.648.578-1.648 1.336v.203A1.5 1.5 0 1 1 4 12.5a1.5 1.5 0 0 1 .75-1.297V4.797A1.5 1.5 0 1 1 7 3.5a1.5 1.5 0 0 1-.75 1.297V8.68c.398-.196.82-.328 1.203-.446 1.453-.46 2.281-.804 2.297-2.437A1.5 1.5 0 1 1 12 4.5z"
- style={{ fill, fillRule: 'nonzero' }}
- />
- </svg>
- );
-}
* 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 styled from '@emotion/styled';
+import {
+ Badge,
+ DangerButtonSecondary,
+ InheritanceIcon,
+ Link,
+ Note,
+ themeBorder,
+} from 'design-system';
import * as React from 'react';
-import { deactivateRule, Profile } from '../../../api/quality-profiles';
-import Link from '../../../components/common/Link';
-import { Button } from '../../../components/controls/buttons';
+import { Profile, deactivateRule } from '../../../api/quality-profiles';
import ConfirmButton from '../../../components/controls/ConfirmButton';
import Tooltip from '../../../components/controls/Tooltip';
import SeverityIcon from '../../../components/icons/SeverityIcon';
import { Rule } from '../../../types/types';
import { Activation } from '../query';
import ActivationButton from './ActivationButton';
-import RuleInheritanceIcon from './RuleInheritanceIcon';
interface Props {
activation?: Activation;
}
return (
- <td className="coding-rule-table-meta-cell coding-rule-activation">
+ <div className="sw-mr-2">
<SeverityIcon severity={activation.severity} />
{selectedProfile && selectedProfile.parentName && (
<>
selectedProfile.parentName,
)}
>
- <RuleInheritanceIcon
- className="little-spacer-left"
- inheritance={activation.inherit}
- />
+ <InheritanceIcon className="sw-ml-1" fill="destructiveIconFocus" />
</Tooltip>
)}
{activation.inherit === 'INHERITED' && (
selectedProfile.parentName,
)}
>
- <RuleInheritanceIcon
- className="little-spacer-left"
- inheritance={activation.inherit}
- />
+ <InheritanceIcon className="sw-ml-1" fill="currentColor" />
</Tooltip>
)}
</>
)}
- </td>
+ </div>
);
};
const canCopy = selectedProfile.actions?.copy;
if (selectedProfile.isBuiltIn && canCopy) {
return (
- <td className="coding-rule-table-meta-cell coding-rule-activation-actions">
+ <div className="sw-ml-4">
<Tooltip overlay={translate('coding_rules.need_extend_or_copy')}>
- <Button className="coding-rules-detail-quality-profile-deactivate button-red disabled">
+ <DangerButtonSecondary disabled>
{translate('coding_rules', activation ? 'deactivate' : 'activate')}
- </Button>
+ </DangerButtonSecondary>
</Tooltip>
- </td>
+ </div>
);
}
if (activation) {
return (
- <td className="coding-rule-table-meta-cell coding-rule-activation-actions">
+ <div className="sw-ml-4">
{activation.inherit === 'NONE' || canDeactivateInherited ? (
<ConfirmButton
confirmButtonText={translate('yes')}
onConfirm={this.handleDeactivate}
>
{({ onClick }) => (
- <Button
- className="coding-rules-detail-quality-profile-deactivate button-red"
- onClick={onClick}
- >
+ <DangerButtonSecondary onClick={onClick}>
{translate('coding_rules.deactivate')}
- </Button>
+ </DangerButtonSecondary>
)}
</ConfirmButton>
) : (
<Tooltip overlay={translate('coding_rules.can_not_deactivate')}>
- <Button
- className="coding-rules-detail-quality-profile-deactivate button-red"
- disabled
- >
+ <DangerButtonSecondary disabled>
{translate('coding_rules.deactivate')}
- </Button>
+ </DangerButtonSecondary>
</Tooltip>
)}
- </td>
+ </div>
);
}
return (
- <td className="coding-rule-table-meta-cell coding-rule-activation-actions">
+ <div className="sw-ml-4">
{!rule.isTemplate && (
<ActivationButton
buttonText={translate('coding_rules.activate')}
- className="coding-rules-detail-quality-profile-activate"
modalHeader={translate('coding_rules.activate_in_quality_profile')}
onDone={this.handleActivate}
profiles={[selectedProfile]}
rule={rule}
/>
)}
- </td>
+ </div>
);
};
const { rule, selected } = this.props;
const allTags = [...(rule.tags || []), ...(rule.sysTags || [])];
return (
- <li
- className={classNames('coding-rule', { selected })}
+ <ListItemStyled
+ selected={selected}
+ className="it__coding-rule sw-p-3 sw-mb-4 sw-rounded-1"
aria-current={selected}
data-rule={rule.key}
>
- <table className="coding-rule-table">
- <tbody>
- <tr>
+ <div className="sw-flex sw-flex-col">
+ <div className="sw-mb-4">
+ {rule.cleanCodeAttributeCategory !== undefined && (
+ <CleanCodeAttributePill
+ cleanCodeAttributeCategory={rule.cleanCodeAttributeCategory}
+ type="rule"
+ />
+ )}
+ </div>
+ <div className="sw-flex sw-justify-between sw-items-center">
+ <div className="sw-flex sw-items-center">
{this.renderActivation()}
- <td>
- <div className="coding-rule-title">
- <Link
- className="link-no-underline"
- onClick={this.handleNameClick}
- to={getRuleUrl(rule.key)}
- >
- {rule.name}
- </Link>
- {rule.isTemplate && (
- <Tooltip overlay={translate('coding_rules.rule_template.title')}>
- <span className="badge spacer-left">
- {translate('coding_rules.rule_template')}
- </span>
- </Tooltip>
- )}
- </div>
- </td>
-
- <td className="coding-rule-table-meta-cell">
- <div className="display-flex-center coding-rule-meta">
- {rule.cleanCodeAttributeCategory !== undefined && (
- <CleanCodeAttributePill
- className="spacer-left"
- cleanCodeAttributeCategory={rule.cleanCodeAttributeCategory}
- type="rule"
- />
- )}
- {rule.status !== 'READY' && (
- <span className="spacer-left badge badge-error">
- {translate('rules.status', rule.status)}
- </span>
- )}
- <span className="display-inline-flex-center spacer-left note">
- {rule.langName}
+ <div>
+ <Link
+ className="sw-body-sm-highlight"
+ onClick={this.handleNameClick}
+ to={getRuleUrl(rule.key)}
+ >
+ {rule.name}
+ </Link>
+ </div>
+ {rule.isTemplate && (
+ <Tooltip overlay={translate('coding_rules.rule_template.title')}>
+ <span>
+ <Badge className="sw-ml-2">{translate('coding_rules.rule_template')}</Badge>
</span>
- {rule.impacts.map(({ severity, softwareQuality }) => (
- <SoftwareImpactPill
- className="spacer-left"
- key={softwareQuality}
- severity={severity}
- quality={softwareQuality}
- type="rule"
- />
- ))}
- {allTags.length > 0 && (
- <TagsList
- allowUpdate={false}
- className="display-inline-flex-center note spacer-left"
- tags={allTags}
- />
- )}
- </div>
- </td>
-
+ </Tooltip>
+ )}
+ {rule.status !== 'READY' && (
+ <Badge variant="deleted" className="sw-ml-2">
+ {translate('rules.status', rule.status)}
+ </Badge>
+ )}
+ </div>
+ <div className="sw-flex sw-items-center sw-ml-2">
+ <Note>{rule.langName}</Note>
+ {rule.impacts.map(({ severity, softwareQuality }) => (
+ <SoftwareImpactPill
+ className="sw-ml-3"
+ key={softwareQuality}
+ severity={severity}
+ quality={softwareQuality}
+ type="rule"
+ />
+ ))}
+ {allTags.length > 0 && (
+ <TagsList allowUpdate={false} className="sw-ml-3" tags={allTags} />
+ )}
{this.renderActions()}
- </tr>
- </tbody>
- </table>
- </li>
+ </div>
+ </div>
+ </div>
+ </ListItemStyled>
);
}
}
+
+const ListItemStyled = styled.li<{ selected: boolean }>`
+ border: ${(props) =>
+ props.selected ? themeBorder('default', 'primary') : themeBorder('default', 'almCardBorder')};
+
+ border-width: ${(props) => (props.selected ? '2px' : '1px')};
+`;
ruleSoftwareQuality: (quality: SoftwareQuality) => byText(`issue.software_quality.${quality}`),
// Rule tags
- tagsDropdown: byRole('button', { name: /tags_list_x/ }),
- tagsMenu: byRole('group', { name: 'select_tags' }),
+ tagsDropdown: byLabelText(/tags_list_x/).byRole('button'),
tagCheckbox: (tag: string) => byRole('checkbox', { name: tag }),
tagSearch: byRole('searchbox', { name: 'search.search_for_tags' }),
* 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 { PopupPlacement, Tags, Tooltip } from 'design-system';
import * as React from 'react';
-import DropdownIcon from '../../components/icons/DropdownIcon';
-import TagsIcon from '../../components/icons/TagsIcon';
-import { translateWithParameters } from '../../helpers/l10n';
+import { translate, translateWithParameters } from '../../helpers/l10n';
import './TagsList.css';
interface Props {
allowUpdate?: boolean;
className?: string;
tags: string[];
+ overlay?: React.ReactNode;
}
-export default function TagsList({ allowUpdate = false, className, tags }: Props) {
+const TAGS_TO_DISPLAY = 2;
+
+export default function TagsList({
+ allowUpdate = false,
+ className,
+ tags,
+ overlay,
+}: Readonly<Props>) {
+ const [open, setOpen] = React.useState(false);
+
return (
- <span
- aria-label={translateWithParameters('tags_list_x', tags.join(', '))}
- role="note"
- className={classNames('tags-list', className)}
- >
- <TagsIcon className="text-middle" />
- <span aria-hidden className="text-ellipsis text-middle" title={tags.join(', ')}>
- {tags.join(', ')}
- </span>
- {allowUpdate && <DropdownIcon className="text-middle" />}
- </span>
+ <Tags
+ allowUpdate={allowUpdate}
+ ariaTagsListLabel={translateWithParameters('tags_list_x', tags.join(', '))}
+ className={className}
+ emptyText={translate('no_tags')}
+ menuId="rule-tags-menu"
+ onClose={() => setOpen(false)}
+ open={open}
+ overlay={overlay}
+ popupPlacement={PopupPlacement.Bottom}
+ tags={tags}
+ tagsToDisplay={TAGS_TO_DISPLAY}
+ tooltip={Tooltip}
+ />
);
}
+++ /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 { shallow } from 'enzyme';
-import * as React from 'react';
-import TagsList from '../TagsList';
-
-const tags = ['foo', 'bar'];
-
-it('should render with a list of tag', () => {
- expect(shallow(<TagsList tags={tags} />)).toMatchSnapshot();
-});
-
-it('should render with a caret on the right if update is allowed', () => {
- expect(shallow(<TagsList allowUpdate tags={tags} />)).toMatchSnapshot();
-});
+++ /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 { shallow } from 'enzyme';
-import * as React from 'react';
-import TagsSelector, { validateTag } from '../TagsSelector';
-
-const props = {
- listSize: 10,
- onSearch: () => Promise.resolve(),
- onSelect: () => {},
- onUnselect: () => {},
- renderLabel: (element: string) => element,
- position: { right: 0, top: 0 },
- selectedTags: ['bar'],
- tags: ['foo', 'bar', 'baz'],
-};
-
-it('should render with selected tags', () => {
- const tagsSelector = shallow(<TagsSelector {...props} />);
- expect(tagsSelector).toMatchSnapshot();
-});
-
-it('should render without tags at all', () => {
- expect(shallow(<TagsSelector {...props} selectedTags={[]} tags={[]} />)).toMatchSnapshot();
-});
-
-it('should validate tags correctly', () => {
- const validChars = 'abcdefghijklmnopqrstuvwxyz0123456789+-#.';
- expect(validateTag('test')).toBe('test');
- expect(validateTag(validChars)).toBe(validChars);
- expect(validateTag(validChars.toUpperCase())).toBe(validChars);
- expect(validateTag('T E$ST')).toBe('test');
- expect(validateTag('T E$st!^àéèing1')).toBe('testing1');
-});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render with a caret on the right if update is allowed 1`] = `
-<span
- aria-label="tags_list_x.foo, bar"
- className="tags-list"
- role="note"
->
- <TagsIcon
- className="text-middle"
- />
- <span
- aria-hidden={true}
- className="text-ellipsis text-middle"
- title="foo, bar"
- >
- foo, bar
- </span>
- <DropdownIcon
- className="text-middle"
- />
-</span>
-`;
-
-exports[`should render with a list of tag 1`] = `
-<span
- aria-label="tags_list_x.foo, bar"
- className="tags-list"
- role="note"
->
- <TagsIcon
- className="text-middle"
- />
- <span
- aria-hidden={true}
- className="text-ellipsis text-middle"
- title="foo, bar"
- >
- foo, bar
- </span>
-</span>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render with selected tags 1`] = `
-<MultiSelect
- elements={
- [
- "foo",
- "bar",
- "baz",
- ]
- }
- filterSelected={[Function]}
- legend="select_tags"
- listSize={10}
- onSearch={[Function]}
- onSelect={[Function]}
- onUnselect={[Function]}
- placeholder="search.search_for_tags"
- renderLabel={[Function]}
- selectedElements={
- [
- "bar",
- ]
- }
- validateSearchInput={[Function]}
-/>
-`;
-
-exports[`should render without tags at all 1`] = `
-<MultiSelect
- elements={[]}
- filterSelected={[Function]}
- legend="select_tags"
- listSize={10}
- onSearch={[Function]}
- onSelect={[Function]}
- onUnselect={[Function]}
- placeholder="search.search_for_tags"
- renderLabel={[Function]}
- selectedElements={[]}
- validateSearchInput={[Function]}
-/>
-`;
coding_rules.more_info.scroll_message=Scroll down to Code Quality principles
coding_rules.detail.extend_description.form=Extend this rule's description
+coding_rules.create_tag=Create Tag
rule.impact.severity.tooltip=Issues found for this rule will have a {severity} impact on the {quality} of your software.