it('should support OWASP Top 10 version 2021', async () => {
const user = userEvent.setup();
renderIssueApp();
+ await waitOnDataLoaded();
await user.click(screen.getByRole('button', { name: 'issues.facet.standards' }));
const owaspTop102021 = screen.getByRole('button', { name: 'issues.facet.owaspTop10_2021' });
expect(owaspTop102021).toBeInTheDocument();
}),
);
});
+
+ it('should close all filters if there is a filter from other mode', async () => {
+ let component = renderIssueApp();
+ await waitOnDataLoaded();
+ expect(screen.getAllByRole('button', { expanded: true })).toHaveLength(3);
+
+ component.unmount();
+
+ component = renderIssueApp(undefined, undefined, 'issues?types=CODE_SMELL');
+ await waitOnDataLoaded();
+ expect(screen.queryByRole('button', { expanded: true })).not.toBeInTheDocument();
+
+ component.unmount();
+
+ settingsHandler.set(SettingsKey.MQRMode, 'false');
+
+ renderIssueApp(undefined, undefined, 'issues?impactSeverities=BLOCKER');
+ await waitOnDataLoaded();
+ expect(screen.queryByRole('button', { expanded: true })).not.toBeInTheDocument();
+ });
});
describe('issues app when reindexing', () => {
expect(await ui.fixedIssuesHeading.find()).toBeInTheDocument();
});
+
+ it('should show issue type if old filter exists', async () => {
+ const component = renderProjectIssuesApp('project/issues?id=my-project');
+
+ expect(await ui.issueItem1.find()).not.toHaveTextContent('issue.type.VULNERABILITY');
+
+ component.unmount();
+
+ renderProjectIssuesApp('project/issues?id=my-project&types=VULNERABILITY');
+
+ expect(await ui.issueItem1.find()).toHaveTextContent('issue.type.VULNERABILITY');
+ });
});
describe('navigation', () => {
renderIssueApp(currentUser);
// Check that the bulk button has correct behavior
- expect(screen.getByRole('button', { name: 'bulk_change' })).toBeDisabled();
+ expect(await screen.findByRole('button', { name: 'bulk_change' })).toBeDisabled();
// Select all issues
await user.click(await screen.findByRole('checkbox', { name: 'issues.select_all_issues' }));
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { screen, waitForElementToBeRemoved } from '@testing-library/react';
+import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { keyBy } from 'lodash';
import { byLabelText, byRole } from '~sonar-aligned/helpers/testSelector';
it('should show source across components', async () => {
const user = userEvent.setup();
renderProjectIssuesApp('project/issues?issues=issue101&open=issue101&id=myproject');
- await waitOnDataLoaded();
expect(await screen.findByLabelText('test1.js')).toBeInTheDocument();
expect(screen.getByLabelText('test2.js')).toBeInTheDocument();
renderProjectIssuesApp('project/issues?issues=issue1&open=issue1&id=myproject');
await waitOnDataLoaded();
- // Line 44 is between both snippets, it should not be shown
- expect(ui.line44.query()).not.toBeInTheDocument();
-
// There currently are two snippet shown
expect(await screen.findAllByRole('table')).toHaveLength(2);
+ // Line 44 is between both snippets, it should not be shown
+ expect(ui.line44.query()).not.toBeInTheDocument();
+
// Expand lines above second snippet
await user.click(ui.expandLinesAbove.get());
issuesHandler.setIssueList([JUPYTER_ISSUE]);
sourcesHandler.setSource('{not a JSON file}');
renderProjectIssuesApp('project/issues?issues=some-issue&open=some-issue&id=myproject');
- await waitOnDataLoaded();
// Preview tab should be shown
- expect(ui.preview.get()).toBeChecked();
+ expect(await ui.preview.find()).toBeChecked();
expect(ui.code.get()).toBeInTheDocument();
expect(
},
]);
renderProjectIssuesApp('project/issues?issues=some-issue&open=some-issue&id=myproject');
- await waitOnDataLoaded();
// Preview tab should be shown
- expect(ui.preview.get()).toBeChecked();
+ expect(await ui.preview.find()).toBeChecked();
expect(ui.code.get()).toBeInTheDocument();
expect(
it('should show preview tab when jupyter notebook issue', async () => {
issuesHandler.setIssueList([JUPYTER_ISSUE]);
renderProjectIssuesApp('project/issues?issues=some-issue&open=some-issue&id=myproject');
- await waitOnDataLoaded();
// Preview tab should be shown
- expect(ui.preview.get()).toBeChecked();
+ expect(await ui.preview.find()).toBeChecked();
expect(ui.code.get()).toBeInTheDocument();
expect(
await screen.findByRole('button', { name: 'Issue on Jupyter Notebook' }),
).toBeInTheDocument();
- await waitForElementToBeRemoved(screen.queryByText('issue.preview.jupyter_notebook.error'));
+ expect(screen.queryByText('issue.preview.jupyter_notebook.error')).not.toBeInTheDocument();
expect(screen.getByTestId('hljs-sonar-underline')).toHaveTextContent('matplotlib');
expect(screen.getByText(/pylab/, { exact: false })).toBeInTheDocument();
});
]);
const user = userEvent.setup();
renderProjectIssuesApp('project/issues?issues=some-issue&open=some-issue&id=myproject');
- await waitOnDataLoaded();
- await user.click(ui.code.get());
+ await user.click(await ui.code.find());
expect(screen.getAllByRole('button', { name: 'Issue on Jupyter Notebook' })).toHaveLength(2);
expect(screen.queryByText('Another unrelated issue')).not.toBeInTheDocument();
},
]);
renderProjectIssuesApp('project/issues?issues=some-issue&open=some-issue&id=myproject');
- await waitOnDataLoaded();
// Preview tab should be shown
- expect(ui.preview.get()).toBeChecked();
+ expect(await ui.preview.find()).toBeChecked();
expect(ui.code.get()).toBeInTheDocument();
expect(
await screen.findByRole('button', { name: 'Issue on Jupyter Notebook' }),
).toBeInTheDocument();
- await waitForElementToBeRemoved(screen.queryByText('issue.preview.jupyter_notebook.error'));
+ expect(screen.queryByText('issue.preview.jupyter_notebook.error')).not.toBeInTheDocument();
const underlined = screen.getAllByTestId('hljs-sonar-underline');
expect(underlined).toHaveLength(4);
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { serializeDate } from '../../../helpers/query';
import { withBranchLikes } from '../../../queries/branch';
+import { useStandardExperienceMode } from '../../../queries/settings';
import { BranchLike } from '../../../types/branch-like';
import { isProject } from '../../../types/component';
import {
component?: Component;
currentUser: CurrentUser;
isFetchingBranch?: boolean;
+ isStandard?: boolean;
location: Location;
router: Router;
}
super(props);
const query = parseQuery(props.location.query, props.component?.needIssueSync);
this.bulkButtonRef = React.createRef();
+ const hasFilterFromOtherMode = props.isStandard
+ ? query.impactSoftwareQualities.length !== 0 || query.impactSeverities.length !== 0
+ : query.types.length !== 0 || query.severities.length !== 0;
this.state = {
bulkChangeModal: false,
loadingMore: false,
locationsNavigator: false,
myIssues: areMyIssuesSelected(props.location.query),
- openFacets: {
- owaspTop10: shouldOpenStandardsChildFacet({}, query, SecurityStandard.OWASP_TOP10),
- 'owaspTop10-2021': shouldOpenStandardsChildFacet(
- {},
- query,
- SecurityStandard.OWASP_TOP10_2021,
- ),
- cleanCodeAttributeCategories: true,
- impactSoftwareQualities: true,
- severities: true,
- types: true,
- impactSeverities: true,
- sonarsourceSecurity: shouldOpenSonarSourceSecurityFacet({}, query),
- standards: shouldOpenStandardsFacet({}, query),
- },
+ openFacets: hasFilterFromOtherMode
+ ? {}
+ : {
+ owaspTop10: shouldOpenStandardsChildFacet({}, query, SecurityStandard.OWASP_TOP10),
+ 'owaspTop10-2021': shouldOpenStandardsChildFacet(
+ {},
+ query,
+ SecurityStandard.OWASP_TOP10_2021,
+ ),
+ cleanCodeAttributeCategories: true,
+ impactSoftwareQualities: true,
+ severities: true,
+ types: true,
+ impactSeverities: true,
+ sonarsourceSecurity: shouldOpenSonarSourceSecurityFacet({}, query),
+ standards: shouldOpenStandardsFacet({}, query),
+ },
query,
referencedComponentsById: {},
referencedComponentsByKey: {},
}
}
+function WrappedApp(props: Readonly<Omit<Props, 'isStandard'>>) {
+ const { data: isStandard, isLoading } = useStandardExperienceMode();
+
+ return (
+ <Spinner ariaLabel={translate('issues.loading_issues')} isLoading={isLoading}>
+ <App {...props} isStandard={isStandard} />
+ </Spinner>
+ );
+}
+
export default withRouter(
withComponentContext(
withCurrentUserContext(
withBranchLikes(
withIndexationContext(
withIndexationGuard<Props & WithIndexationContextProps>({
- Component: App,
+ Component: WrappedApp,
showIndexationMessage: ({
component,
indexationContext: {
const needIssueSync = component?.needIssueSync;
+ const secondLine = translate(
+ `issues.facet.second_line.mode.${isStandardMode ? 'mqr' : 'standard'}`,
+ );
+
return (
<>
{displayPeriodFilter && (
<BasicSeparator className="sw-my-4" />
+ {query.types.length > 0 && (
+ <>
+ <TypeFacet
+ fetching={props.loadingFacets.types === true}
+ needIssueSync={needIssueSync}
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!openFacets.types}
+ stats={facets.types}
+ types={query.types}
+ secondLine={secondLine}
+ />
+ <BasicSeparator className="sw-my-4" />
+ </>
+ )}
+
+ {query.severities.length > 0 && (
+ <>
+ <StandardSeverityFacet
+ fetching={props.loadingFacets.severities === true}
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!openFacets.severities}
+ stats={facets.severities}
+ values={query.severities}
+ headerName={translate('issues.facet.severities')}
+ secondLine={secondLine}
+ />
+
+ <BasicSeparator className="sw-my-4" />
+ </>
+ )}
+
<AttributeCategoryFacet
fetching={props.loadingFacets.cleanCodeAttributeCategories === true}
needIssueSync={needIssueSync}
/>
<BasicSeparator className="sw-my-4" />
+
+ {query.impactSoftwareQualities.length > 0 && (
+ <>
+ <SoftwareQualityFacet
+ fetching={props.loadingFacets.impactSoftwareQualities === true}
+ needIssueSync={needIssueSync}
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!openFacets.impactSoftwareQualities}
+ stats={facets.impactSoftwareQualities}
+ qualities={query.impactSoftwareQualities}
+ secondLine={secondLine}
+ />
+
+ <BasicSeparator className="sw-my-4" />
+ </>
+ )}
+
+ {query.impactSeverities.length > 0 && (
+ <>
+ <SeverityFacet
+ fetching={props.loadingFacets.impactSeverities === true}
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!openFacets.impactSeverities}
+ stats={facets.impactSeverities}
+ values={query.impactSeverities}
+ secondLine={secondLine}
+ />
+
+ <BasicSeparator className="sw-my-4" />
+ </>
+ )}
</>
)}
</>
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
open: boolean;
+ secondLine?: string;
stats: Dict<number> | undefined;
}
fetching,
open,
selectedItems = [],
+ secondLine,
stats = {},
needIssueSync,
property,
onClick={() => props.onToggle(property)}
open={open}
help={help}
+ secondLine={secondLine}
>
<FacetItemsList labelledby={headerId}>
{listItems.map((item) => {
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
open: boolean;
+ secondLine?: string;
stats: Dict<number> | undefined;
types: string[];
}
};
render() {
- const { fetching, open, types } = this.props;
+ const { fetching, open, types, secondLine } = this.props;
const nbSelectableItems = AVAILABLE_TYPES.filter(this.getStat.bind(this)).length;
const nbSelectedItems = types.length;
onClear={this.handleClear}
onClick={this.handleHeaderClick}
open={open}
+ secondLine={secondLine}
>
<FacetItemsList labelledby={typeFacetHeaderId}>
{AVAILABLE_TYPES.map(this.renderItem)}
import { mockQuery } from '../../../../helpers/mocks/issues';
import { mockAppState } from '../../../../helpers/testMocks';
import { renderApp } from '../../../../helpers/testReactTestingUtils';
+import { byRole } from '../../../../sonar-aligned/helpers/testSelector';
+import { SoftwareImpactSeverity, SoftwareQuality } from '../../../../types/clean-code-taxonomy';
import { Feature } from '../../../../types/features';
+import { IssueSeverity, IssueType } from '../../../../types/issues';
import { GlobalSettingKeys, SettingsKey } from '../../../../types/settings';
import { Sidebar } from '../Sidebar';
]);
});
+ it('should show show mqr filters if they exist in query', async () => {
+ let component = renderSidebar({
+ query: mockQuery({ types: [IssueType.CodeSmell] }),
+ });
+
+ expect(
+ await screen.findByRole('button', { name: 'issues.facet.impactSoftwareQualities' }),
+ ).toBeInTheDocument();
+
+ expect(screen.getByRole('button', { name: 'issues.facet.types' })).toBeInTheDocument();
+ expect(
+ byRole('button', { name: 'issues.facet.types' })
+ .byText('issues.facet.second_line.mode.standard')
+ .get(),
+ ).toBeInTheDocument();
+ expect(
+ screen.queryByRole('button', { name: 'issues.facet.severities' }),
+ ).not.toBeInTheDocument();
+
+ component.unmount();
+
+ component = renderSidebar({
+ query: mockQuery({ severities: [IssueSeverity.Blocker] }),
+ });
+
+ expect(
+ await screen.findByRole('button', { name: 'issues.facet.impactSoftwareQualities' }),
+ ).toBeInTheDocument();
+
+ expect(screen.getByRole('button', { name: 'issues.facet.severities' })).toBeInTheDocument();
+ expect(
+ byRole('button', { name: 'issues.facet.severities' })
+ .byText('issues.facet.second_line.mode.standard')
+ .get(),
+ ).toBeInTheDocument();
+ expect(screen.queryByRole('button', { name: 'issues.facet.types' })).not.toBeInTheDocument();
+
+ component.unmount();
+
+ renderSidebar({
+ query: mockQuery({
+ types: [IssueType.CodeSmell],
+ severities: [IssueSeverity.Blocker],
+ }),
+ });
+
+ expect(
+ await screen.findByRole('button', { name: 'issues.facet.impactSoftwareQualities' }),
+ ).toBeInTheDocument();
+
+ expect(screen.getByRole('button', { name: 'issues.facet.types' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'issues.facet.severities' })).toBeInTheDocument();
+ });
+
it('should render correct facets for Application', () => {
renderSidebar({ component: mockComponent({ qualifier: ComponentQualifier.Application }) });
]);
});
+ it('should show show mqr filters if they exist in query', async () => {
+ let component = renderSidebar({
+ query: mockQuery({ impactSeverities: [SoftwareImpactSeverity.Blocker] }),
+ });
+
+ expect(await screen.findByRole('button', { name: 'issues.facet.types' })).toBeInTheDocument();
+
+ expect(
+ screen.getByRole('button', { name: 'coding_rules.facet.impactSeverities' }),
+ ).toBeInTheDocument();
+ expect(
+ byRole('button', { name: 'coding_rules.facet.impactSeverities' })
+ .byText('issues.facet.second_line.mode.mqr')
+ .get(),
+ ).toBeInTheDocument();
+ expect(
+ screen.queryByRole('button', { name: 'issues.facet.impactSoftwareQualities' }),
+ ).not.toBeInTheDocument();
+
+ component.unmount();
+
+ component = renderSidebar({
+ query: mockQuery({ impactSoftwareQualities: [SoftwareQuality.Maintainability] }),
+ });
+
+ expect(await screen.findByRole('button', { name: 'issues.facet.types' })).toBeInTheDocument();
+
+ expect(
+ screen.getByRole('button', { name: 'issues.facet.impactSoftwareQualities' }),
+ ).toBeInTheDocument();
+ expect(
+ byRole('button', { name: 'issues.facet.impactSoftwareQualities' })
+ .byText('issues.facet.second_line.mode.mqr')
+ .get(),
+ ).toBeInTheDocument();
+ expect(
+ screen.queryByRole('button', { name: 'coding_rules.facet.impactSeverities' }),
+ ).not.toBeInTheDocument();
+
+ component.unmount();
+
+ renderSidebar({
+ query: mockQuery({
+ impactSoftwareQualities: [SoftwareQuality.Maintainability],
+ impactSeverities: [SoftwareImpactSeverity.Blocker],
+ }),
+ });
+
+ expect(await screen.findByRole('button', { name: 'issues.facet.types' })).toBeInTheDocument();
+
+ expect(
+ screen.getByRole('button', { name: 'issues.facet.impactSoftwareQualities' }),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByRole('button', { name: 'coding_rules.facet.impactSeverities' }),
+ ).toBeInTheDocument();
+ });
+
it('should render correct facets for Application', async () => {
renderSidebar({ component: mockComponent({ qualifier: ComponentQualifier.Application }) });
},
}),
featureList: Feature[] = [],
+ navigateTo?: string,
) {
- renderApp('issues', <IssuesApp />, { currentUser, featureList });
+ return renderApp('issues', <IssuesApp />, {
+ currentUser,
+ featureList,
+ navigateTo,
+ });
}
export function renderProjectIssuesApp(
}),
featureList = [Feature.BranchSupport],
) {
- renderAppWithComponentContext(
+ return renderAppWithComponentContext(
'project/issues',
() => (
<Route
onChange: (changes: Dict<string | string[] | undefined>) => void;
onToggle: (facet: FacetKey) => void;
open: boolean;
+ secondLine?: string;
stats?: Dict<number>;
values: string[];
}
open,
property,
renderTextName = defaultRenderName,
+ secondLine,
stats,
help,
values,
disabledHelper={disabledHelper}
tooltipComponent={Tooltip}
help={help}
+ secondLine={secondLine}
>
{open && items !== undefined && (
<FacetItemsList labelledby={headerId}>{items.map(this.renderItem)}</FacetItemsList>
const { ui } = getPageObject();
const issue = mockIssue(true, { effort: '2 days', message: 'This is an issue' });
const onClick = jest.fn();
- renderIssue({ issue, onSelect: onClick });
+ renderIssue(
+ { issue, onSelect: onClick },
+ 'scopes=MAIN&impactSeverities=LOW&types=VULNERABILITY',
+ );
expect(ui.effort('2 days').get()).toBeInTheDocument();
expect(ui.issueMessageLink.get()).toHaveAttribute(
function renderIssue(
props: Partial<Omit<ComponentPropsType<typeof Issue>, 'onChange' | 'onPopupToggle'>> = {},
+ query?: string,
) {
function Wrapper(
wrapperProps: Omit<ComponentPropsType<typeof Issue>, 'onChange' | 'onPopupToggle'>,
}
return renderAppRoutes(
- 'issues?scopes=MAIN&impactSeverities=LOW&types=VULNERABILITY',
+ `issues${query ? `?${query}` : ''}`,
() => (
<Route
path="issues"
import { Badge, CommentIcon, SeparatorCircleIcon } from '~design-system';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { isDefined } from '../../../helpers/types';
+import { useStandardExperienceMode } from '../../../queries/settings';
+import { useLocation } from '../../../sonar-aligned/components/hoc/withRouter';
import { Issue } from '../../../types/types';
import Tooltip from '../../controls/Tooltip';
import DateFromNow from '../../intl/DateFromNow';
import { WorkspaceContext } from '../../workspace/context';
import IssuePrioritized from './IssuePrioritized';
+import IssueSeverity from './IssueSeverity';
+import IssueType from './IssueType';
import SonarLintBadge from './SonarLintBadge';
interface Props {
export default function IssueMetaBar(props: Readonly<Props>) {
const { issue, showLine } = props;
+ const location = useLocation();
const { externalRulesRepoNames } = React.useContext(WorkspaceContext);
+ const { data: isStandardMode } = useStandardExperienceMode();
const ruleEngine =
(issue.externalRuleEngine && externalRulesRepoNames[issue.externalRuleEngine]) ||
<IssueMetaListItem className={issueMetaListItemClassNames}>
<DateFromNow date={issue.creationDate} />
</IssueMetaListItem>
+ {!isStandardMode && (location.query.types || location.query.severities) && (
+ <>
+ <SeparatorCircleIcon aria-hidden as="li" />
+
+ <IssueType issue={issue} height={12} width={12} />
+
+ <SeparatorCircleIcon data-guiding-id="issue-4" aria-hidden as="li" />
+
+ <IssueSeverity issue={issue} height={12} width={12} />
+ </>
+ )}
{issue.prioritizedRule && (
<>
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { IconProps, TextSubdued } from '~design-system';
-import DocHelpTooltip from '~sonar-aligned/components/controls/DocHelpTooltip';
-import { DocLink } from '../../../helpers/doc-links';
+import { Text } from '@sonarsource/echoes-react';
+import { IconProps } from '~design-system';
import { translate } from '../../../helpers/l10n';
import { IssueSeverity as IssueSeverityType } from '../../../types/issues';
import { Issue } from '../../../types/types';
-import IssueSeverityIcon from '../../icon-mappers/IssueSeverityIcon';
-import { DeprecatedFieldTooltip } from './DeprecatedFieldTooltip';
+import SoftwareImpactSeverityIcon from '../../icon-mappers/SoftwareImpactSeverityIcon';
interface Props extends IconProps {
issue: Pick<Issue, 'severity'>;
export default function IssueSeverity({ issue, ...iconProps }: Readonly<Props>) {
return (
- <DocHelpTooltip
- content={<DeprecatedFieldTooltip field="severity" />}
- links={[
- {
- href: DocLink.Issues,
- label: translate('learn_more'),
- },
- ]}
- >
- <TextSubdued className="sw-flex sw-items-center sw-gap-1/2">
- <IssueSeverityIcon
- aria-hidden
- fill="var(--echoes-color-icon-disabled)"
- severity={issue.severity as IssueSeverityType}
- {...iconProps}
- />
- {translate('severity', issue.severity)}
- </TextSubdued>
- </DocHelpTooltip>
+ <Text isSubdued className="sw-flex sw-items-center sw-gap-1/2">
+ <SoftwareImpactSeverityIcon
+ aria-hidden
+ disabled
+ severity={issue.severity as IssueSeverityType}
+ {...iconProps}
+ />
+ {translate('severity', issue.severity)}
+ </Text>
);
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { IconProps, TextSubdued } from '~design-system';
-import DocHelpTooltip from '~sonar-aligned/components/controls/DocHelpTooltip';
-import { DocLink } from '../../../helpers/doc-links';
+import { Text } from '@sonarsource/echoes-react';
+import { IconProps } from '~design-system';
import { translate } from '../../../helpers/l10n';
import { Issue } from '../../../types/types';
import IssueTypeIcon from '../../icon-mappers/IssueTypeIcon';
-import { DeprecatedFieldTooltip } from './DeprecatedFieldTooltip';
interface Props extends IconProps {
issue: Pick<Issue, 'type'>;
export default function IssueType({ issue, ...iconProps }: Readonly<Props>) {
return (
- <DocHelpTooltip
- content={<DeprecatedFieldTooltip field="type" />}
- links={[
- {
- href: DocLink.Issues,
- label: translate('learn_more'),
- },
- ]}
- >
- <TextSubdued className="sw-flex sw-items-center sw-gap-1/2">
- <IssueTypeIcon
- aria-hidden
- fill="var(--echoes-color-icon-disabled)"
- type={issue.type}
- {...iconProps}
- />
- {translate('issue.type', issue.type)}
- </TextSubdued>
- </DocHelpTooltip>
+ <Text isSubdued className="sw-flex sw-items-center sw-gap-1/2">
+ <IssueTypeIcon
+ aria-hidden
+ fill="var(--echoes-color-icon-disabled)"
+ type={issue.type}
+ {...iconProps}
+ />
+ {translate('issue.type', issue.type)}
+ </Text>
);
}
*/
import styled from '@emotion/styled';
+import { Text } from '@sonarsource/echoes-react';
import classNames from 'classnames';
import { uniqueId } from 'lodash';
import * as React from 'react';
onClear?: () => void;
onClick?: (isOpen: boolean) => void;
open?: boolean;
+ secondLine?: string;
tooltipComponent?: React.ComponentType<React.PropsWithChildren<{ content: React.ReactNode }>>;
}
'data-property': dataProperty,
disabled = false,
disabledHelper,
+ secondLine,
hasEmbeddedFacets = false,
help,
id: idProp,
const expandable = !disabled && Boolean(onClick);
const id = React.useMemo(() => idProp ?? uniqueId('filter-facet-'), [idProp]);
const Tooltip = tooltipComponent ?? SCTooltip;
+
return (
<Accordion
className={classNames(className, { open })}
</HeaderTitle>
</Tooltip>
) : (
- <HeaderTitle>{name}</HeaderTitle>
+ <div>
+ <HeaderTitle>{name}</HeaderTitle>
+ {secondLine !== undefined && (
+ <Text as="div" isSubdued>
+ {secondLine}
+ </Text>
+ )}
+ </div>
)}
</ChevronAndTitle>
{help && <span className="sw-ml-1">{help}</span>}
issues.facet.sonarsource.show_more=Show more SonarSource categories
issues.facet.prioritized_rule.category=Prioritized rules
issues.facet.prioritized_rule=Issues from prioritized rules
+issues.facet.second_line.mode.standard=Standard Experience
+issues.facet.second_line.mode.mqr=MQR mode
#------------------------------------------------------------------------------
#