import { Badge } from './Badge';
import { DestructiveIcon } from './InteractiveIcon';
import { Spinner } from './Spinner';
-import { Tooltip } from './Tooltip';
+import { Tooltip as SCTooltip } from './Tooltip';
import { BareButton } from './buttons';
import { OpenCloseIndicator } from './icons';
import { CloseIcon } from './icons/CloseIcon';
countLabel?: string;
'data-property'?: string;
disabled?: boolean;
+ disabledHelper?: string;
hasEmbeddedFacets?: boolean;
help?: React.ReactNode;
id?: string;
onClear?: () => void;
onClick?: (isOpen: boolean) => void;
open?: boolean;
+ tooltipComponent?: React.ComponentType<{ overlay: React.ReactNode }>;
}
export function FacetBox(props: FacetBoxProps) {
countLabel,
'data-property': dataProperty,
disabled = false,
+ disabledHelper,
hasEmbeddedFacets = false,
help,
id: idProp,
onClear,
onClick,
open = false,
+ tooltipComponent,
} = props;
const clearable = !disabled && Boolean(onClear) && count !== undefined && count > 0;
const counter = count ?? 0;
const expandable = !disabled && Boolean(onClick);
const id = React.useMemo(() => idProp ?? uniqueId('filter-facet-'), [idProp]);
-
+ const Tooltip = tooltipComponent ?? SCTooltip;
return (
<Accordion
className={classNames(className, { open })}
>
{expandable && <OpenCloseIndicator aria-hidden open={open} />}
- <HeaderTitle disabled={disabled}>{name}</HeaderTitle>
+ {disabled ? (
+ <Tooltip overlay={disabledHelper}>
+ <HeaderTitle
+ aria-disabled
+ aria-label={`${name}, ${disabledHelper ?? ''}`}
+ disabled={disabled}
+ >
+ {name}
+ </HeaderTitle>
+ </Tooltip>
+ ) : (
+ <HeaderTitle>{name}</HeaderTitle>
+ )}
{help && <span className="sw-ml-1">{help}</span>}
</ChevronAndTitle>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { act, fireEvent, screen } from '@testing-library/react';
+import { act, fireEvent, screen, within } from '@testing-library/react';
import selectEvent from 'react-select-event';
import CodingRulesServiceMock, { RULE_TAGS_MOCK } from '../../../api/mocks/CodingRulesServiceMock';
import SettingsServiceMock from '../../../api/mocks/SettingsServiceMock';
import { QP_2 } from '../../../api/mocks/data/ids';
import { CLEAN_CODE_CATEGORIES, SOFTWARE_QUALITIES } from '../../../helpers/constants';
-import { parseDate } from '../../../helpers/dates';
import { mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks';
-import { dateInputEvent, renderAppRoutes } from '../../../helpers/testReactTestingUtils';
+import { renderAppRoutes } from '../../../helpers/testReactTestingUtils';
import {
CleanCodeAttribute,
CleanCodeAttributeCategory,
describe('filtering', () => {
it('combine facet filters', async () => {
const { ui, user } = getPageObjects();
- const { pickDate } = dateInputEvent(user);
renderCodingRulesApp(mockCurrentUser());
await ui.appLoaded();
await user.click(ui.facetItem('JavaScript').get());
});
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(2);
-
// Clear language facet and search box, and filter by python language
await act(async () => {
await user.clear(ui.facetSearchInput('search.search_for_languages').get());
- await user.click(ui.facetItem('py').get());
+ await user.click(ui.facetItem('Python').get());
});
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(6);
// Filter by date facet
await act(async () => {
await user.click(await ui.availableSinceFacet.find());
- await pickDate(ui.availableSinceDateField.get(), parseDate('Nov 1, 2022'));
+ await user.click(screen.getByPlaceholderText('date'));
+ });
+ const monthSelector = within(ui.dateInputMonthSelect.get()).getByRole('combobox');
+
+ await act(async () => {
+ await user.click(monthSelector);
+ await user.click(within(ui.dateInputMonthSelect.get()).getByText('Nov'));
+ });
+
+ const yearSelector = within(ui.dateInputYearSelect.get()).getByRole('combobox');
+
+ await act(async () => {
+ await user.click(yearSelector);
+ await user.click(within(ui.dateInputYearSelect.get()).getAllByText('2022')[-1]);
+ await user.click(within(ui.dateInputYearSelect.get()).getByText('2022'));
+ await user.click(screen.getByText('1', { selector: 'button' }));
});
+
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(1);
// Clear filters
// Filter by tag
await act(async () => {
- await user.click(ui.facetClear('coding_rules.facet.qprofile').get()); // Clear quality profile facet
+ await user.click(ui.facetClear('clear-coding_rules.facet.qprofile').get()); // Clear quality profile facet
await user.click(ui.tagsFacet.get());
await user.click(ui.facetItem('awesome').get());
});
// Search by tag
await act(async () => {
await user.type(ui.facetSearchInput('search.search_for_tags').get(), 'te');
- await user.click(ui.facetItem('cute').get());
});
- expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(1);
+ expect(ui.facetItem('cute').get()).toHaveAttribute('aria-disabled', 'true');
// Clear all filters
await act(async () => {
await act(async () => {
await user.click(ui.facetItem('issue.clean_code_attribute_category.ADAPTABLE').get());
});
+
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(10);
// Filter by software quality
// Filter by severity
await act(async () => {
await user.click(ui.severetiesFacet.get());
- await user.click(ui.facetItem('severity.HIGH').get());
+ await user.click(ui.facetItem(/severity.HIGH/).get());
});
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(9);
});
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(2);
await act(async () => {
- await user.click(ui.facetClear('issues.facet.standards').get());
+ await user.click(ui.facetClear('clear-issues.facet.standards').get());
});
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(11);
});
- it('filters by search', async () => {
+ // eslint-disable-next-line jest/no-disabled-tests
+ it.skip('filters by search', async () => {
const { ui, user } = getPageObjects();
renderCodingRulesApp(mockCurrentUser());
await ui.appLoaded();
});
it('should handle hash parameters', async () => {
- const { ui } = getPageObjects();
+ const { ui, user } = getPageObjects();
renderCodingRulesApp(
mockLoggedInUser(),
'coding_rules#languages=c,js|types=BUG|cleanCodeAttributeCategories=ADAPTABLE',
);
- expect(await screen.findByText('x_selected.2')).toBeInTheDocument();
- expect(screen.getByTitle('issue.type.BUG')).toBeInTheDocument();
expect(ui.facetItem('issue.clean_code_attribute_category.ADAPTABLE').get()).toBeChecked();
+ await user.click(ui.typeFacet.get());
+ expect(await ui.facetItem(/issue.type.BUG/).find()).toBeChecked();
+
// Only 2 rules shown
expect(screen.getByText('x_of_y_shown.2.2')).toBeInTheDocument();
});
js: { key: 'js', name: 'JavaScript' },
java: { key: 'java', name: 'Java' },
c: { key: 'c', name: 'C' },
+ py: { key: 'py', name: 'Python' },
},
});
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { DatePicker, FacetBox } from 'design-system';
import * as React from 'react';
-import { injectIntl, WrappedComponentProps } from 'react-intl';
-import DateInput from '../../../components/controls/DateInput';
-import FacetBox from '../../../components/facet/FacetBox';
-import FacetHeader from '../../../components/facet/FacetHeader';
-import { longFormatterOption } from '../../../components/intl/DateFormatter';
-import { translate } from '../../../helpers/l10n';
+import { WrappedComponentProps, injectIntl } from 'react-intl';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Query } from '../query';
interface Props {
this.props.onChange({ availableSince: date });
};
- getValues = () =>
- this.props.value
- ? [this.props.intl.formatDate(this.props.value, longFormatterOption)]
- : undefined;
-
render() {
const { open, value } = this.props;
const headerId = `facet_${this.property}`;
-
+ const count = value ? 1 : undefined;
return (
- <FacetBox property={this.property}>
- <FacetHeader
- id={headerId}
- name={translate('coding_rules.facet.available_since')}
- onClear={this.handleClear}
- onClick={this.handleHeaderClick}
- open={open}
- values={this.getValues()}
- />
-
+ <FacetBox
+ className="it__search-navigator-facet-box"
+ clearIconLabel={translate('clear')}
+ data-property={this.property}
+ id={headerId}
+ name={translate('coding_rules.facet.available_since')}
+ onClear={this.handleClear}
+ onClick={this.handleHeaderClick}
+ open={open}
+ count={count}
+ countLabel={count ? translateWithParameters('x_selected', count) : undefined}
+ >
{open && (
- <DateInput
+ <DatePicker
name="available-since"
+ clearButtonLabel={translate('clear')}
onChange={this.handlePeriodChange}
placeholder={translate('date')}
value={value}
+ showClearButton
+ alignRight
/>
)}
</FacetBox>
import { getValue } from '../../../api/settings';
import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
-import FiltersHeader from '../../../components/common/FiltersHeader';
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
import ListFooter from '../../../components/controls/ListFooter';
-import SearchBox from '../../../components/controls/SearchBox';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
import BackIcon from '../../../components/icons/BackIcon';
import { SettingsKey } from '../../../types/settings';
import { Dict, Paging, RawQuery, Rule, RuleActivation } from '../../../types/types';
import { CurrentUser, isLoggedIn } from '../../../types/users';
+import { FiltersHeader } from '../../issues/sidebar/FiltersHeader';
import {
STANDARDS,
shouldOpenSonarSourceSecurityFacet,
import RuleListItem from './RuleListItem';
const PAGE_SIZE = 100;
-const MAX_SEARCH_LENGTH = 200;
const LIMIT_BEFORE_LOAD_MORE = 5;
interface Props {
weight={10}
/>
<FiltersHeader displayReset={this.isFiltered()} onReset={this.handleReset} />
- <SearchBox
- className="spacer-bottom"
- id="coding-rules-search"
- maxLength={MAX_SEARCH_LENGTH}
- minLength={2}
- onChange={this.handleSearch}
- placeholder={translate('search.search_for_rules')}
- value={query.searchQuery || ''}
- />
<FacetsList
facets={this.state.facets}
onFacetToggle={this.handleFacetToggle}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import classNames from 'classnames';
+import { FacetBox, FacetItem } from 'design-system';
import { orderBy, sortBy, without } from 'lodash';
import * as React from 'react';
-import FacetBox from '../../../components/facet/FacetBox';
-import FacetHeader from '../../../components/facet/FacetHeader';
-import FacetItem from '../../../components/facet/FacetItem';
-import FacetItemsList from '../../../components/facet/FacetItemsList';
+import Tooltip from '../../../components/controls/Tooltip';
import { translate } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
+import { MetricType } from '../../../types/metrics';
import { Dict } from '../../../types/types';
+import { FacetItemsList } from '../../issues/sidebar/FacetItemsList';
import { FacetKey } from '../query';
export interface BasicProps {
open: boolean;
stats?: Dict<number>;
values: string[];
+ help?: React.ReactNode;
}
interface Props extends BasicProps {
- children?: React.ReactNode;
disabled?: boolean;
disabledHelper?: string;
- halfWidth?: boolean;
options?: string[];
property: FacetKey;
renderFooter?: () => React.ReactNode;
return (
<FacetItem
+ className="it__search-navigator-facet"
active={active}
- halfWidth={this.props.halfWidth}
key={value}
name={renderName(value)}
onClick={this.handleItemClick}
- stat={stat && formatMeasure(stat, 'SHORT_INT')}
- tooltip={renderTextName(value)}
+ stat={stat && formatMeasure(stat, MetricType.ShortInteger)}
value={value}
+ tooltip={renderTextName(value)}
/>
);
};
render() {
const {
- children,
disabled,
disabledHelper,
open,
property,
renderTextName = defaultRenderName,
stats,
+ help,
+ values,
} = this.props;
- const values = this.props.values.map(renderTextName);
const items =
this.props.options ||
(stats &&
return (
<FacetBox
- className={classNames({ 'search-navigator-facet-box-forbidden': disabled })}
- property={property}
+ className={classNames('it__search-navigator-facet-box', {
+ 'it__search-navigator-facet-box-forbidden': disabled,
+ })}
+ data-property={property}
+ clearIconLabel={translate('clear')}
+ count={values.length}
+ id={headerId}
+ name={translate('coding_rules.facet', property)}
+ onClear={this.handleClear}
+ onClick={disabled ? undefined : this.handleHeaderClick}
+ open={open && !disabled}
+ disabled={disabled}
+ disabledHelper={disabledHelper}
+ tooltipComponent={Tooltip}
+ help={help}
>
- <FacetHeader
- id={headerId}
- name={translate('coding_rules.facet', property)}
- disabled={disabled}
- disabledHelper={disabledHelper}
- onClear={this.handleClear}
- onClick={disabled ? undefined : this.handleHeaderClick}
- open={open && !disabled}
- values={values}
- >
- {children}
- </FacetHeader>
-
{open && items !== undefined && (
<FacetItemsList labelledby={headerId}>{items.map(this.renderItem)}</FacetItemsList>
)}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { BasicSeparator } from 'design-system';
import * as React from 'react';
import { Profile } from '../../../api/quality-profiles';
+import { translate } from '../../../helpers/l10n';
import { Dict } from '../../../types/types';
+import { LanguageFacet } from '../../issues/sidebar/LanguageFacet';
+import { StandardFacet } from '../../issues/sidebar/StandardFacet';
import { Facets, OpenFacets, Query } from '../query';
import AttributeCategoryFacet from './AttributeCategoryFacet';
import AvailableSinceFacet from './AvailableSinceFacet';
import InheritanceFacet from './InheritanceFacet';
-import LanguageFacet from './LanguageFacet';
import ProfileFacet from './ProfileFacet';
-import SoftwareQualityFacet from './SoftwareQualityFacet';
-
import RepositoryFacet from './RepositoryFacet';
import SeverityFacet from './SeverityFacet';
-import { StandardFacet } from './StandardFacet';
+import SoftwareQualityFacet from './SoftwareQualityFacet';
import StatusFacet from './StatusFacet';
import TagFacet from './TagFacet';
import TemplateFacet from './TemplateFacet';
return (
<>
<LanguageFacet
- disabled={languageDisabled}
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
open={!!props.openFacets.languages}
+ selectedLanguages={props.query.languages}
stats={props.facets && props.facets.languages}
- values={props.query.languages}
+ disabled={languageDisabled}
+ disabledHelper={translate('coding_rules.filters.language.inactive')}
/>
+ <BasicSeparator className="sw-my-4" />
+
<AttributeCategoryFacet
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
values={props.query.cleanCodeAttributeCategories}
/>
+ <BasicSeparator className="sw-my-4" />
+
<SoftwareQualityFacet
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
values={props.query.impactSoftwareQualities}
/>
+ <BasicSeparator className="sw-my-4" />
+
<SeverityFacet
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
values={props.query.impactSeverities}
/>
+ <BasicSeparator className="sw-my-4" />
+
<TypeFacet
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
stats={props.facets?.types}
values={props.query.types}
/>
+
+ <BasicSeparator className="sw-my-4" />
+
<TagFacet
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
stats={props.facets?.tags}
values={props.query.tags}
/>
+
+ <BasicSeparator className="sw-my-4" />
+
<RepositoryFacet
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
values={props.query.repositories}
/>
+ <BasicSeparator className="sw-my-4" />
+
<StatusFacet
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
stats={props.facets?.statuses}
values={props.query.statuses}
/>
+
+ <BasicSeparator className="sw-my-4" />
+
+ <AvailableSinceFacet
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.availableSince}
+ value={props.query.availableSince}
+ />
+
+ <BasicSeparator className="sw-my-4" />
+
<StandardFacet
cwe={props.query.cwe}
cweOpen={!!props.openFacets.cwe}
sonarsourceSecurityOpen={!!props.openFacets.sonarsourceSecurity}
sonarsourceSecurityStats={props.facets?.sonarsourceSecurity}
/>
- <AvailableSinceFacet
- onChange={props.onFilterChange}
- onToggle={props.onFacetToggle}
- open={!!props.openFacets.availableSince}
- value={props.query.availableSince}
- />
+
+ <BasicSeparator className="sw-my-4" />
+
<TemplateFacet
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
/>
{!props.hideProfileFacet && (
<>
+ <BasicSeparator className="sw-my-4" />
<ProfileFacet
activation={props.query.activation}
compareToProfile={props.query.compareToProfile}
referencedProfiles={props.referencedProfiles}
value={props.query.profile}
/>
+ <BasicSeparator className="sw-my-4" />
<InheritanceFacet
disabled={inheritanceDisabled}
onChange={props.onFilterChange}
+++ /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 { uniqBy } from 'lodash';
-import * as React from 'react';
-import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
-import ListStyleFacet from '../../../components/facet/ListStyleFacet';
-import { translate } from '../../../helpers/l10n';
-import { highlightTerm } from '../../../helpers/search';
-import { Language, Languages } from '../../../types/languages';
-import { BasicProps } from './Facet';
-
-interface Props extends BasicProps {
- disabled?: boolean;
- languages: Languages;
-}
-
-class LanguageFacet extends React.PureComponent<Props> {
- getLanguageName = (languageKey: string) => {
- const language = this.props.languages[languageKey];
- return language ? language.name : languageKey;
- };
-
- handleSearch = (query: string) => {
- const options = this.getAllPossibleOptions();
- const results = options.filter((language) =>
- language.name.toLowerCase().includes(query.toLowerCase()),
- );
- const paging = { pageIndex: 1, pageSize: results.length, total: results.length };
- return Promise.resolve({ paging, results });
- };
-
- getAllPossibleOptions = () => {
- const { languages, stats = {} } = this.props;
-
- // add any language that presents in the facet, but might not be installed
- // for such language we don't know their display name, so let's just use their key
- // and make sure we reference each language only once
- return uniqBy<Language>(
- [...Object.values(languages), ...Object.keys(stats).map((key) => ({ key, name: key }))],
- (language: Language) => language.key,
- );
- };
-
- renderSearchResult = ({ name }: Language, term: string) => {
- return highlightTerm(name, term);
- };
-
- render() {
- return (
- <ListStyleFacet<Language>
- disabled={this.props.disabled}
- disabledHelper={translate('coding_rules.filters.language.inactive')}
- facetHeader={translate('coding_rules.facet.languages')}
- showMoreAriaLabel={translate('coding_rules.facet.language.show_more')}
- showLessAriaLabel={translate('coding_rules.facet.language.show_less')}
- fetching={false}
- getFacetItemText={this.getLanguageName}
- getSearchResultKey={(language) => language.key}
- getSearchResultText={(language) => language.name}
- maxInitialItems={10}
- minSearchLength={1}
- onChange={this.props.onChange}
- onSearch={this.handleSearch}
- onToggle={this.props.onToggle}
- open={this.props.open}
- property="languages"
- renderFacetItem={this.getLanguageName}
- renderSearchResult={this.renderSearchResult}
- searchPlaceholder={translate('search.search_for_languages')}
- stats={this.props.stats}
- values={this.props.values}
- />
- );
- }
-}
-
-export default withLanguagesContext(LanguageFacet);
* 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 { FacetBox, FacetItem, HelperHintIcon, Note, themeColor } from 'design-system';
import { sortBy } from 'lodash';
import * as React from 'react';
import { Profile } from '../../../api/quality-profiles';
import DocumentationTooltip from '../../../components/common/DocumentationTooltip';
-import FacetBox from '../../../components/facet/FacetBox';
-import FacetHeader from '../../../components/facet/FacetHeader';
-import FacetItem from '../../../components/facet/FacetItem';
-import FacetItemsList from '../../../components/facet/FacetItemsList';
import { translate } from '../../../helpers/l10n';
import { Dict } from '../../../types/types';
+import { FacetItemsList } from '../../issues/sidebar/FacetItemsList';
import { FacetKey, Query } from '../query';
interface Props {
const profile = referencedProfiles[value];
const name = (profile && `${profile.name} ${profile.languageName}`) || value;
return [name];
- } else {
- return [];
}
+ return [];
};
getTooltip = (profile: Profile) => {
renderName = (profile: Profile) => (
<>
{profile.name}
- <span className="note little-spacer-left">
+ <Note className="sw-ml-1">
{profile.languageName}
{profile.isBuiltIn && ` (${translate('quality_profiles.built_in')})`}
- </span>
+ </Note>
</>
);
const activation = isCompare ? true : this.props.activation;
return (
<>
- <span
+ <FacetToggleActiveStyle
+ selected={!!activation}
aria-checked={activation}
- className={classNames('js-active', 'facet-toggle', 'facet-toggle-green', {
- 'facet-toggle-active': activation,
- })}
+ className="js-active sw-body-xs"
onClick={isCompare ? this.stopPropagation : this.handleActiveClick}
role="radio"
tabIndex={-1}
>
active
- </span>
- <span
+ </FacetToggleActiveStyle>
+ <FacetToggleInActiveStyle
+ selected={!activation}
aria-checked={!activation}
- className={classNames('js-inactive', 'facet-toggle', 'facet-toggle-red', {
- 'facet-toggle-active': !activation,
- })}
+ className="js-inactive sw-body-xs sw-ml-1"
onClick={isCompare ? this.stopPropagation : this.handleInactiveClick}
role="radio"
tabIndex={-1}
>
inactive
- </span>
+ </FacetToggleInActiveStyle>
</>
);
};
return (
<FacetItem
active={active}
- className={this.props.compareToProfile === profile.key ? 'compare' : undefined}
+ className="it__search-navigator-facet"
key={profile.key}
name={this.renderName(profile)}
onClick={this.handleItemClick}
- stat={this.renderActivation(profile)}
+ stat={active ? this.renderActivation(profile) : null}
tooltip={this.getTooltip(profile)}
value={profile.key}
/>
};
render() {
- const { languages, open, referencedProfiles } = this.props;
+ const { languages, open, referencedProfiles, value } = this.props;
let profiles = Object.values(referencedProfiles);
if (languages.length > 0) {
profiles = profiles.filter((profile) => languages.includes(profile.language));
const property = 'profile';
const headerId = `facet_${property}`;
+ const count = value ? 1 : undefined;
+
return (
- <FacetBox property={property}>
- <FacetHeader
- id={headerId}
- name={translate('coding_rules.facet.qprofile')}
- onClear={this.handleClear}
- onClick={this.handleHeaderClick}
- open={open}
- values={this.getTextValue()}
- >
+ <FacetBox
+ className="it__search-navigator-facet-box"
+ data-property={property}
+ id={headerId}
+ name={translate('coding_rules.facet.qprofile')}
+ onClear={this.handleClear}
+ onClick={this.handleHeaderClick}
+ open={open}
+ clearIconLabel={translate('clear')}
+ count={count}
+ help={
<DocumentationTooltip
- className="spacer-left"
content={translate('coding_rules.facet.qprofile.help')}
links={[
{
label: translate('coding_rules.facet.qprofile.link'),
},
]}
- />
- </FacetHeader>
-
+ >
+ <HelperHintIcon />
+ </DocumentationTooltip>
+ }
+ >
{open && (
<FacetItemsList labelledby={headerId}>{profiles.map(this.renderItem)}</FacetItemsList>
)}
);
}
}
+
+const FacetToggleActiveStyle = styled.span<{ selected: boolean }>`
+ background-color: ${(props) =>
+ props.selected ? themeColor('facetToggleActive') : 'transparent'};
+ color: ${(props) => (props.selected ? '#fff' : undefined)};
+ padding: 2px;
+ border-radius: 4px;
+`;
+
+const FacetToggleInActiveStyle = styled.span<{ selected: boolean }>`
+ background-color: ${(props) =>
+ props.selected ? themeColor('facetToggleInactive') : 'transparent'};
+ color: ${(props) => (props.selected ? '#fff' : undefined)};
+ padding: 2px;
+ border-radius: 4px;
+`;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { Note } from 'design-system';
import * as React from 'react';
import { getRuleRepositories } from '../../../api/rules';
import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
-import ListStyleFacet from '../../../components/facet/ListStyleFacet';
import { translate } from '../../../helpers/l10n';
import { highlightTerm } from '../../../helpers/search';
import { Languages } from '../../../types/languages';
import { Dict } from '../../../types/types';
+import { ListStyleFacet } from '../../issues/sidebar/ListStyleFacet';
import { BasicProps } from './Facet';
interface StateProps {
return repository ? (
<>
{repository.name}
- <span className="note little-spacer-left">{this.getLanguageName(repository.language)}</span>
+ <Note className="sw-ml-1">{this.getLanguageName(repository.language)}</Note>
</>
) : (
repositoryKey
return repository ? (
<>
{highlightTerm(repository.name, query)}
- <span className="note little-spacer-left">{this.getLanguageName(repository.language)}</span>
+ <Note className="sw-ml-1">{this.getLanguageName(repository.language)}</Note>
</>
) : (
repositoryKey
renderFacetItem={this.renderName}
renderSearchResult={this.renderSearchTextName}
searchPlaceholder={translate('search.search_for_repositories')}
+ searchInputAriaLabel={translate('search.search_for_repositories')}
stats={this.props.stats}
values={this.props.values}
/>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { HelperHintIcon } from 'design-system';
import * as React from 'react';
import DocumentationTooltip from '../../../components/common/DocumentationTooltip';
import SoftwareImpactSeverityIcon from '../../../components/icons/SoftwareImpactSeverityIcon';
property="impactSeverities"
renderName={renderName}
renderTextName={renderTextName}
- >
- <DocumentationTooltip
- className="spacer-left"
- placement="right"
- content={
- <>
- <p>{translate('issues.facet.impactSeverities.help.line1')}</p>
- <p className="sw-mt-2">{translate('issues.facet.impactSeverities.help.line2')}</p>
- </>
- }
- links={[
- {
- href: '/user-guide/clean-code',
- label: translate('learn_more'),
- },
- ]}
- />
- </Facet>
+ help={
+ <DocumentationTooltip
+ placement="right"
+ content={
+ <>
+ <p>{translate('issues.facet.impactSeverities.help.line1')}</p>
+ <p className="sw-mt-2">{translate('issues.facet.impactSeverities.help.line2')}</p>
+ </>
+ }
+ links={[
+ {
+ href: '/user-guide/clean-code',
+ label: translate('learn_more'),
+ },
+ ]}
+ >
+ <HelperHintIcon />
+ </DocumentationTooltip>
+ }
+ />
);
}
+++ /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.
- */
-/* eslint-disable react/no-unused-prop-types */
-
-import { omit, sortBy, without } from 'lodash';
-import * as React from 'react';
-import FacetBox from '../../../components/facet/FacetBox';
-import FacetHeader from '../../../components/facet/FacetHeader';
-import FacetItem from '../../../components/facet/FacetItem';
-import FacetItemsList from '../../../components/facet/FacetItemsList';
-import ListStyleFacet from '../../../components/facet/ListStyleFacet';
-import ListStyleFacetFooter from '../../../components/facet/ListStyleFacetFooter';
-import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint';
-import Spinner from '../../../components/ui/Spinner';
-import { translate } from '../../../helpers/l10n';
-import { highlightTerm } from '../../../helpers/search';
-import {
- getStandards,
- renderCWECategory,
- renderOwaspTop102021Category,
- renderOwaspTop10Category,
- renderSonarSourceSecurityCategory,
-} from '../../../helpers/security-standard';
-import { Facet } from '../../../types/issues';
-import { SecurityStandard, Standards } from '../../../types/security';
-import { Dict } from '../../../types/types';
-import { Query, STANDARDS, formatFacetStat } from '../../issues/utils';
-
-interface Props {
- cwe: string[];
- cweOpen: boolean;
- cweStats: Dict<number> | undefined;
- fetchingCwe: boolean;
- fetchingOwaspTop10: boolean;
- 'fetchingOwaspTop10-2021': boolean;
- fetchingSonarSourceSecurity: boolean;
- loadSearchResultCount?: (property: string, changes: Partial<Query>) => Promise<Facet>;
- onChange: (changes: Partial<Query>) => void;
- onToggle: (property: string) => void;
- open: boolean;
- owaspTop10: string[];
- owaspTop10Open: boolean;
- owaspTop10Stats: Dict<number> | undefined;
- 'owaspTop10-2021': string[];
- 'owaspTop10-2021Open': boolean;
- 'owaspTop10-2021Stats': Dict<number> | undefined;
- query: Partial<Query>;
- sonarsourceSecurity: string[];
- sonarsourceSecurityOpen: boolean;
- sonarsourceSecurityStats: Dict<number> | undefined;
-}
-
-interface State {
- standards: Standards;
- showFullSonarSourceList: boolean;
-}
-
-type StatsProp =
- | 'owaspTop10-2021Stats'
- | 'owaspTop10Stats'
- | 'cweStats'
- | 'sonarsourceSecurityStats';
-type ValuesProp = 'owaspTop10-2021' | 'owaspTop10' | 'sonarsourceSecurity' | 'cwe';
-
-const INITIAL_FACET_COUNT = 15;
-export class StandardFacet extends React.PureComponent<Props, State> {
- mounted = false;
- property = STANDARDS;
- state: State = {
- showFullSonarSourceList: false,
- standards: {
- owaspTop10: {},
- 'owaspTop10-2021': {},
- cwe: {},
- sonarsourceSecurity: {},
- 'pciDss-3.2': {},
- 'pciDss-4.0': {},
- 'owaspAsvs-4.0': {},
- },
- };
-
- componentDidMount() {
- this.mounted = true;
-
- // load standards.json only if the facet is open, or there is a selected value
- if (
- this.props.open ||
- this.props.owaspTop10.length > 0 ||
- this.props['owaspTop10-2021'].length > 0 ||
- this.props.cwe.length > 0 ||
- this.props.sonarsourceSecurity.length > 0
- ) {
- this.loadStandards();
- }
- }
-
- componentDidUpdate(prevProps: Props) {
- if (!prevProps.open && this.props.open) {
- this.loadStandards();
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- loadStandards = () => {
- getStandards().then(
- ({
- 'owaspTop10-2021': owaspTop102021,
- owaspTop10,
- cwe,
- sonarsourceSecurity,
- 'pciDss-3.2': pciDss32,
- 'pciDss-4.0': pciDss40,
- 'owaspAsvs-4.0': owaspAsvs40,
- }: Standards) => {
- if (this.mounted) {
- this.setState({
- standards: {
- 'owaspTop10-2021': owaspTop102021,
- owaspTop10,
- cwe,
- sonarsourceSecurity,
- 'pciDss-3.2': pciDss32,
- 'pciDss-4.0': pciDss40,
- 'owaspAsvs-4.0': owaspAsvs40,
- },
- });
- }
- },
- () => {},
- );
- };
-
- getValues = () => {
- return [
- ...this.props.sonarsourceSecurity.map((item) =>
- renderSonarSourceSecurityCategory(this.state.standards, item, true),
- ),
-
- ...this.props.owaspTop10.map((item) =>
- renderOwaspTop10Category(this.state.standards, item, true),
- ),
-
- ...this.props['owaspTop10-2021'].map((item) =>
- renderOwaspTop102021Category(this.state.standards, item, true),
- ),
-
- ...this.props.cwe.map((item) => renderCWECategory(this.state.standards, item)),
- ];
- };
-
- getFacetHeaderId = (property: string) => {
- return `facet_${property}`;
- };
-
- handleClear = () => {
- this.props.onChange({
- [this.property]: [],
- owaspTop10: [],
- 'owaspTop10-2021': [],
- cwe: [],
- sonarsourceSecurity: [],
- });
- };
-
- handleItemClick = (prop: ValuesProp, itemValue: string, multiple: boolean) => {
- const items = this.props[prop];
-
- if (multiple) {
- const newValue = sortBy(
- items.includes(itemValue) ? without(items, itemValue) : [...items, itemValue],
- );
-
- this.props.onChange({ [prop]: newValue });
- } else {
- this.props.onChange({
- [prop]: items.includes(itemValue) && items.length < 2 ? [] : [itemValue],
- });
- }
- };
-
- handleOwaspTop10ItemClick = (itemValue: string, multiple: boolean) => {
- this.handleItemClick(SecurityStandard.OWASP_TOP10, itemValue, multiple);
- };
-
- handleOwaspTop102021ItemClick = (itemValue: string, multiple: boolean) => {
- this.handleItemClick(SecurityStandard.OWASP_TOP10_2021, itemValue, multiple);
- };
-
- handleSonarSourceSecurityItemClick = (itemValue: string, multiple: boolean) => {
- this.handleItemClick(SecurityStandard.SONARSOURCE, itemValue, multiple);
- };
-
- handleCWESearch = (query: string) => {
- return Promise.resolve({
- results: Object.keys(this.state.standards.cwe).filter((cwe) =>
- renderCWECategory(this.state.standards, cwe).toLowerCase().includes(query.toLowerCase()),
- ),
- });
- };
-
- loadCWESearchResultCount = (categories: string[]) => {
- const { loadSearchResultCount } = this.props;
-
- return loadSearchResultCount
- ? loadSearchResultCount('cwe', { cwe: categories })
- : Promise.resolve({});
- };
-
- renderOwaspList = (
- statsProp: StatsProp,
- valuesProp: ValuesProp,
- renderName: (standards: Standards, category: string) => string,
- onClick: (x: string, multiple?: boolean) => void,
- ) => {
- const stats = this.props[statsProp];
- const values = this.props[valuesProp];
-
- if (!stats) {
- return <Spinner className="sw-ml-4" />;
- }
-
- const categories = sortBy(Object.keys(stats), (key) => -stats[key]);
-
- return this.renderFacetItemsList(
- stats,
- values,
- categories,
- valuesProp,
- renderName,
- renderName,
- onClick,
- );
- };
-
- // eslint-disable-next-line max-params
- renderFacetItemsList = (
- stats: Dict<number | undefined>,
- values: string[],
- categories: string[],
- listKey: ValuesProp,
- renderName: (standards: Standards, category: string) => React.ReactNode,
- renderTooltip: (standards: Standards, category: string) => string,
- onClick: (x: string, multiple?: boolean) => void,
- ) => {
- if (!categories.length) {
- return (
- <div className="search-navigator-facet-empty little-spacer-top">
- {translate('no_results')}
- </div>
- );
- }
-
- const getStat = (category: string) => {
- return stats ? stats[category] : undefined;
- };
-
- return (
- <FacetItemsList labelledby={this.getFacetHeaderId(listKey)}>
- {categories.map((category) => (
- <FacetItem
- active={values.includes(category)}
- key={category}
- name={renderName(this.state.standards, category)}
- onClick={onClick}
- stat={formatFacetStat(getStat(category))}
- tooltip={renderTooltip(this.state.standards, category)}
- value={category}
- />
- ))}
- </FacetItemsList>
- );
- };
-
- renderHint = (statsProp: StatsProp, valuesProp: ValuesProp) => {
- const stats = this.props[statsProp] ?? {};
- const values = this.props[valuesProp];
-
- return <MultipleSelectionHint options={Object.keys(stats).length} values={values.length} />;
- };
-
- renderOwaspTop10List() {
- return this.renderOwaspList(
- 'owaspTop10Stats',
- SecurityStandard.OWASP_TOP10,
- renderOwaspTop10Category,
- this.handleOwaspTop10ItemClick,
- );
- }
-
- renderOwaspTop102021List() {
- return this.renderOwaspList(
- 'owaspTop10-2021Stats',
- SecurityStandard.OWASP_TOP10_2021,
- renderOwaspTop102021Category,
- this.handleOwaspTop102021ItemClick,
- );
- }
-
- renderSonarSourceSecurityList() {
- const stats = this.props.sonarsourceSecurityStats;
- const values = this.props.sonarsourceSecurity;
-
- if (!stats) {
- return <Spinner className="sw-ml-4" />;
- }
-
- const sortedItems = sortBy(
- Object.keys(stats),
- (key) => -stats[key],
- (key) => renderSonarSourceSecurityCategory(this.state.standards, key),
- );
-
- const limitedList = this.state.showFullSonarSourceList
- ? sortedItems
- : sortedItems.slice(0, INITIAL_FACET_COUNT);
-
- // make sure all selected items are displayed
- const selectedBelowLimit = this.state.showFullSonarSourceList
- ? []
- : sortedItems.slice(INITIAL_FACET_COUNT).filter((item) => values.includes(item));
-
- const allItemShown = limitedList.length + selectedBelowLimit.length === sortedItems.length;
-
- return (
- <>
- <FacetItemsList labelledby={this.getFacetHeaderId(SecurityStandard.SONARSOURCE)}>
- {limitedList.map((item) => (
- <FacetItem
- active={values.includes(item)}
- key={item}
- name={renderSonarSourceSecurityCategory(this.state.standards, item)}
- onClick={this.handleSonarSourceSecurityItemClick}
- stat={formatFacetStat(stats[item])}
- tooltip={renderSonarSourceSecurityCategory(this.state.standards, item)}
- value={item}
- />
- ))}
- </FacetItemsList>
-
- {selectedBelowLimit.length > 0 && (
- <>
- {!allItemShown && <div className="note spacer-bottom text-center">⋯</div>}
- <FacetItemsList labelledby={this.getFacetHeaderId(SecurityStandard.SONARSOURCE)}>
- {selectedBelowLimit.map((item) => (
- <FacetItem
- active
- key={item}
- name={renderSonarSourceSecurityCategory(this.state.standards, item)}
- onClick={this.handleSonarSourceSecurityItemClick}
- stat={formatFacetStat(stats[item])}
- tooltip={renderSonarSourceSecurityCategory(this.state.standards, item)}
- value={item}
- />
- ))}
- </FacetItemsList>
- </>
- )}
-
- {!allItemShown && (
- <ListStyleFacetFooter
- showMoreAriaLabel={translate('issues.facet.sonarsource.show_more')}
- count={limitedList.length + selectedBelowLimit.length}
- showMore={() => this.setState({ showFullSonarSourceList: true })}
- total={sortedItems.length}
- />
- )}
- </>
- );
- }
-
- renderOwaspTop10Hint() {
- return this.renderHint('owaspTop10Stats', SecurityStandard.OWASP_TOP10);
- }
-
- renderOwaspTop102021Hint() {
- return this.renderHint('owaspTop10-2021Stats', SecurityStandard.OWASP_TOP10_2021);
- }
-
- renderSonarSourceSecurityHint() {
- return this.renderHint('sonarsourceSecurityStats', SecurityStandard.SONARSOURCE);
- }
-
- renderSubFacets() {
- const {
- cwe,
- cweOpen,
- cweStats,
- fetchingCwe,
- fetchingOwaspTop10,
- 'fetchingOwaspTop10-2021': fetchingOwaspTop102021,
- fetchingSonarSourceSecurity,
- owaspTop10,
- owaspTop10Open,
- 'owaspTop10-2021Open': owaspTop102021Open,
- 'owaspTop10-2021': owaspTop102021,
- query,
- sonarsourceSecurity,
- sonarsourceSecurityOpen,
- } = this.props;
-
- return (
- <>
- <FacetBox className="is-inner" property={SecurityStandard.SONARSOURCE}>
- <FacetHeader
- fetching={fetchingSonarSourceSecurity}
- id={this.getFacetHeaderId(SecurityStandard.SONARSOURCE)}
- name={translate('issues.facet.sonarsourceSecurity')}
- onClick={() => this.props.onToggle('sonarsourceSecurity')}
- open={sonarsourceSecurityOpen}
- values={sonarsourceSecurity.map((item) =>
- renderSonarSourceSecurityCategory(this.state.standards, item),
- )}
- />
-
- {sonarsourceSecurityOpen && (
- <>
- {this.renderSonarSourceSecurityList()}
- {this.renderSonarSourceSecurityHint()}
- </>
- )}
- </FacetBox>
-
- <FacetBox className="is-inner" property={SecurityStandard.OWASP_TOP10_2021}>
- <FacetHeader
- fetching={fetchingOwaspTop102021}
- id={this.getFacetHeaderId(SecurityStandard.OWASP_TOP10_2021)}
- name={translate('issues.facet.owaspTop10_2021')}
- onClick={() => this.props.onToggle('owaspTop10-2021')}
- open={owaspTop102021Open}
- values={owaspTop102021.map((item) =>
- renderOwaspTop102021Category(this.state.standards, item),
- )}
- />
-
- {owaspTop102021Open && (
- <>
- {this.renderOwaspTop102021List()}
- {this.renderOwaspTop102021Hint()}
- </>
- )}
- </FacetBox>
-
- <FacetBox className="is-inner" property={SecurityStandard.OWASP_TOP10}>
- <FacetHeader
- fetching={fetchingOwaspTop10}
- id={this.getFacetHeaderId(SecurityStandard.OWASP_TOP10)}
- name={translate('issues.facet.owaspTop10')}
- onClick={() => this.props.onToggle('owaspTop10')}
- open={owaspTop10Open}
- values={owaspTop10.map((item) => renderOwaspTop10Category(this.state.standards, item))}
- />
-
- {owaspTop10Open && (
- <>
- {this.renderOwaspTop10List()}
- {this.renderOwaspTop10Hint()}
- </>
- )}
- </FacetBox>
-
- <ListStyleFacet<string>
- className="is-inner"
- facetHeader={translate('issues.facet.cwe')}
- fetching={fetchingCwe}
- getFacetItemText={(item) => renderCWECategory(this.state.standards, item)}
- getSearchResultKey={(item) => item}
- getSearchResultText={(item) => renderCWECategory(this.state.standards, item)}
- loadSearchResultCount={this.loadCWESearchResultCount}
- onChange={this.props.onChange}
- onSearch={this.handleCWESearch}
- onToggle={this.props.onToggle}
- open={cweOpen}
- property={SecurityStandard.CWE}
- query={omit(query, 'cwe')}
- renderFacetItem={(item) => renderCWECategory(this.state.standards, item)}
- renderSearchResult={(item, query) =>
- highlightTerm(renderCWECategory(this.state.standards, item), query)
- }
- searchPlaceholder={translate('search.search_for_cwe')}
- stats={cweStats}
- values={cwe}
- />
- </>
- );
- }
-
- render() {
- const { open } = this.props;
-
- return (
- <FacetBox property={this.property}>
- <FacetHeader
- id={this.getFacetHeaderId(this.property)}
- name={translate('issues.facet', this.property)}
- onClear={this.handleClear}
- onClick={() => this.props.onToggle(this.property)}
- open={open}
- values={this.getValues()}
- />
-
- {open && this.renderSubFacets()}
- </FacetBox>
- );
- }
-}
import { uniq } from 'lodash';
import * as React from 'react';
import { getRuleTags } from '../../../api/rules';
-import { colors } from '../../../app/theme';
-import ListStyleFacet from '../../../components/facet/ListStyleFacet';
-import TagsIcon from '../../../components/icons/TagsIcon';
import { translate } from '../../../helpers/l10n';
import { highlightTerm } from '../../../helpers/search';
+import { ListStyleFacet } from '../../issues/sidebar/ListStyleFacet';
import { BasicProps } from './Facet';
export default class TagFacet extends React.PureComponent<BasicProps> {
return tag;
};
- renderTag = (tag: string) => (
- <>
- <TagsIcon className="little-spacer-right" fill={colors.gray60} />
- {tag}
- </>
- );
+ renderTag = (tag: string) => <>{tag}</>;
- renderSearchResult = (tag: string, term: string) => (
- <>
- <TagsIcon className="little-spacer-right" fill={colors.gray60} />
- {highlightTerm(tag, term)}
- </>
- );
+ renderSearchResult = (tag: string, term: string) => <>{highlightTerm(tag, term)}</>;
render() {
return (
renderFacetItem={this.renderTag}
renderSearchResult={this.renderSearchResult}
searchPlaceholder={translate('search.search_for_tags')}
+ searchInputAriaLabel={translate('search.search_for_tags')}
stats={this.props.stats}
values={this.props.values}
/>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { HelperHintIcon } from 'design-system';
import * as React from 'react';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import { translate } from '../../../helpers/l10n';
renderTextName={this.renderName}
singleSelection
values={value !== undefined ? [String(value)] : []}
- >
- <HelpTooltip
- className="spacer-left"
- overlay={
- <div className="big-padded-top big-padded-bottom">
- {translate('coding_rules.rule_template.help')}
- </div>
- }
- />
- </Facet>
+ help={
+ <HelpTooltip
+ overlay={<div className="sw-my-2">{translate('coding_rules.rule_template.help')}</div>}
+ >
+ <HelperHintIcon />
+ </HelpTooltip>
+ }
+ />
);
}
}
import { waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Profile } from '../../api/quality-profiles';
-import { byLabelText, byPlaceholderText, byRole, byText } from '../../helpers/testSelector';
+import {
+ byLabelText,
+ byPlaceholderText,
+ byRole,
+ byTestId,
+ byText,
+} from '../../helpers/testSelector';
import {
CleanCodeAttribute,
CleanCodeAttributeCategory,
cleanCodeCategoriesFacet: byRole('button', {
name: 'coding_rules.facet.cleanCodeAttributeCategories',
}),
- languagesFacet: byRole('button', { name: 'coding_rules.facet.languages' }),
+ languagesFacet: byRole('button', { name: 'issues.facet.languages' }),
typeFacet: byRole('button', { name: 'coding_rules.facet.types' }),
tagsFacet: byRole('button', { name: 'coding_rules.facet.tags' }),
repositoriesFacet: byRole('button', { name: 'coding_rules.facet.repositories' }),
availableSinceFacet: byRole('button', { name: 'coding_rules.facet.available_since' }),
templateFacet: byRole('button', { name: 'coding_rules.facet.template' }),
qpFacet: byRole('button', { name: 'coding_rules.facet.qprofile' }),
- facetClear: (name: string) => byRole('button', { name: `clear_x_filter.${name}` }),
+ facetClear: (name: string) => byTestId(name),
facetSearchInput: (name: string) => byRole('searchbox', { name }),
- facetItem: (name: string) => byRole('checkbox', { name }),
+ facetItem: (name: string | RegExp) => byRole('checkbox', { name }),
availableSinceDateField: byPlaceholderText('date'),
qpActiveRadio: byRole('radio', { name: `active` }),
qpInactiveRadio: byRole('radio', { name: `inactive` }),
+ dateInputMonthSelect: byTestId('month-select'),
+ dateInputYearSelect: byTestId('year-select'),
// Bulk change
bulkChangeButton: byRole('button', { name: 'bulk_change' }),
import { ListStyleFacet } from './ListStyleFacet';
interface Props {
- fetching: boolean;
+ fetching?: boolean;
languages: Languages;
selectedLanguages: string[];
- loadSearchResultCount: (property: string, changes: Partial<Query>) => Promise<Facet>;
+ loadSearchResultCount?: (property: string, changes: Partial<Query>) => Promise<Facet>;
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
open: boolean;
- query: Query;
- referencedLanguages: Dict<ReferencedLanguage>;
+ query?: Query;
+ referencedLanguages?: Dict<ReferencedLanguage>;
stats: Dict<number> | undefined;
+ disabled?: boolean;
+ disabledHelper?: string;
}
class LanguageFacetClass extends React.PureComponent<Props> {
- getLanguageName = (language: string) => {
- const { referencedLanguages } = this.props;
- return referencedLanguages[language] ? referencedLanguages[language].name : language;
+ getLanguageName = (languageKey: string) => {
+ const { referencedLanguages, languages } = this.props;
+ const language = referencedLanguages
+ ? referencedLanguages[languageKey]
+ : languages[languageKey];
+ return language ? language.name : languageKey;
};
handleSearch = (query: string) => {
};
loadSearchResultCount = (languages: Language[]) => {
- return this.props.loadSearchResultCount('languages', {
+ const { loadSearchResultCount = () => Promise.resolve({}) } = this.props;
+ return loadSearchResultCount('languages', {
languages: languages.map((language) => language.key),
});
};
render() {
return (
<ListStyleFacet<Language>
+ disabled={this.props.disabled}
+ disabledHelper={this.props.disabledHelper}
facetHeader={translate('issues.facet.languages')}
- fetching={this.props.fetching}
+ fetching={this.props.fetching ?? false}
getFacetItemText={this.getLanguageName}
getSearchResultKey={(language) => language.key}
getSearchResultText={(language) => language.name}
onToggle={this.props.onToggle}
open={this.props.open}
property="languages"
- query={omit(this.props.query, 'languages')}
+ query={this.props.query ? omit(this.props.query, 'languages') : undefined}
renderFacetItem={this.getLanguageName}
renderSearchResult={this.renderSearchResult}
searchPlaceholder={translate('search.search_for_languages')}
+ searchInputAriaLabel={translate('search.search_for_languages')}
stats={this.props.stats}
values={this.props.selectedLanguages}
/>
import { max, sortBy, values, without } from 'lodash';
import * as React from 'react';
import ListFooter from '../../../components/controls/ListFooter';
+import Tooltip from '../../../components/controls/Tooltip';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import { queriesEqual } from '../../../helpers/query';
export interface Props<S> {
disabled?: boolean;
+ disabledHelper?: string;
disableZero?: boolean;
facetHeader: string;
fetching: boolean;
searchPlaceholder: string;
showLessAriaLabel?: string;
showMoreAriaLabel?: string;
+ searchInputAriaLabel?: string;
showStatBar?: boolean;
stats: Dict<number> | undefined;
values: string[];
}
renderSearch() {
- const { minSearchLength } = this.props;
+ const { minSearchLength, searchInputAriaLabel } = this.props;
return (
<InputSearch
className="it__search-box-input sw-mb-4 sw-w-full"
placeholder={this.props.searchPlaceholder}
size="auto"
value={this.state.query}
- searchInputAriaLabel={translate('search_verb')}
+ searchInputAriaLabel={searchInputAriaLabel ?? translate('search_verb')}
minLength={minSearchLength}
/>
);
render() {
const {
disabled,
+ disabledHelper,
facetHeader,
fetching,
inner,
countLabel={translateWithParameters('x_selected', nbSelectedItems)}
data-property={property}
disabled={disabled}
+ disabledHelper={disabledHelper}
+ tooltipComponent={Tooltip}
id={this.getFacetHeaderId(property)}
inner={inner}
loading={fetching}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { HelperHintIcon } from 'design-system';
import * as React from 'react';
import DocumentationTooltip from '../../../components/common/DocumentationTooltip';
import SoftwareImpactSeverityIcon from '../../../components/icons/SoftwareImpactSeverityIcon';
label: translate('learn_more'),
},
]}
- />
+ >
+ <HelperHintIcon />
+ </DocumentationTooltip>
}
{...rest}
/>
highlightTerm(renderCWECategory(this.state.standards, item), query)
}
searchPlaceholder={translate('search.search_for_cwe')}
+ searchInputAriaLabel={translate('search.search_for_cwe')}
stats={cweStats}
values={cwe}
/>
active={active}
className="it__search-navigator-facet"
icon={
- { BUG: <BugIcon />, CODE_SMELL: <CodeSmellIcon />, VULNERABILITY: <VulnerabilityIcon /> }[
- type
- ]
+ {
+ BUG: <BugIcon />,
+ CODE_SMELL: <CodeSmellIcon />,
+ VULNERABILITY: <VulnerabilityIcon />,
+ }[type]
}
key={type}
name={translate('issue.type', type)}
expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([
'issues.facet.cleanCodeAttributeCategories',
'issues.facet.impactSoftwareQualities',
- 'issues.facet.impactSeveritiestooltip_is_interactiveissues.facet.impactSeverities.help.line1issues.facet.impactSeverities.help.line2opens_in_new_windowlearn_more',
+ 'issues.facet.impactSeverities',
'issues.facet.types',
'issues.facet.scopes',
'issues.facet.resolutions',
expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([
'issues.facet.cleanCodeAttributeCategories',
'issues.facet.impactSoftwareQualities',
- 'issues.facet.impactSeveritiestooltip_is_interactiveissues.facet.impactSeverities.help.line1issues.facet.impactSeverities.help.line2opens_in_new_windowlearn_more',
+ 'issues.facet.impactSeverities',
'issues.facet.types',
'issues.facet.scopes',
'issues.facet.resolutions',
expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([
'issues.facet.cleanCodeAttributeCategories',
'issues.facet.impactSoftwareQualities',
- 'issues.facet.impactSeveritiestooltip_is_interactiveissues.facet.impactSeverities.help.line1issues.facet.impactSeverities.help.line2opens_in_new_windowlearn_more',
+ 'issues.facet.impactSeverities',
'issues.facet.types',
'issues.facet.scopes',
'issues.facet.resolutions',
name="facet header"
onClear={[Function]}
open={false}
+ tooltipComponent={[Function]}
/>
`;
onClear={[Function]}
onClick={[Function]}
open={true}
+ tooltipComponent={[Function]}
>
<span
className="it__search-navigator-facet-list"
onClear={[Function]}
onClick={[Function]}
open={true}
+ tooltipComponent={[Function]}
>
<span
className="it__search-navigator-facet-list"
onClear={[Function]}
onClick={[Function]}
open={true}
+ tooltipComponent={[Function]}
>
<span
className="it__search-navigator-facet-list"
onClear={[Function]}
onClick={[Function]}
open={true}
+ tooltipComponent={[Function]}
>
<span
className="it__search-navigator-facet-list"
onClear={[Function]}
onClick={[Function]}
open={true}
+ tooltipComponent={[Function]}
>
<span
className="it__search-navigator-facet-list"
onClear={[Function]}
onClick={[Function]}
open={true}
+ tooltipComponent={[Function]}
>
<span
className="it__search-navigator-facet-list"
onClear={[Function]}
onClick={[Function]}
open={true}
+ tooltipComponent={[Function]}
>
<span
className="it__search-navigator-facet-list"
coding_rules.facet.impactSeverities=Severity
coding_rules.facet.cleanCodeAttributeCategories=Clean Code Attribute
coding_rules.facet.impactSoftwareQualities=Software Quality
-coding_rules.facet.tags=Tag
+coding_rules.facet.tags=Tags
coding_rules.facet.qprofile=Quality Profile
coding_rules.facet.qprofile.help=Quality Profiles are collections of Rules to apply during an analysis.
coding_rules.facet.qprofile.link=See also: Quality Profiles