From 6338e9e131c0c941c87eaf84082e0a839606df9d Mon Sep 17 00:00:00 2001 From: Viktor Vorona Date: Thu, 24 Oct 2024 10:23:47 +0200 Subject: [PATCH] SONAR-23300 Show filters from other mode if they are needed --- .../__tests__/IssuesApp-Filtering-it.tsx | 21 ++++ .../js/apps/issues/__tests__/IssuesApp-it.tsx | 14 ++- .../__tests__/IssuesSourceViewer-it.tsx | 28 ++--- .../js/apps/issues/components/IssuesApp.tsx | 49 +++++--- .../main/js/apps/issues/sidebar/Sidebar.tsx | 70 +++++++++++ .../issues/sidebar/SimpleListStyleFacet.tsx | 3 + .../main/js/apps/issues/sidebar/TypeFacet.tsx | 4 +- .../issues/sidebar/__tests__/Sidebar-it.tsx | 115 ++++++++++++++++++ .../src/main/js/apps/issues/test-utils.tsx | 9 +- .../src/main/js/components/facets/Facet.tsx | 3 + .../components/issue/__tests__/Issue-it.tsx | 8 +- .../issue/components/IssueMetaBar.tsx | 17 +++ .../issue/components/IssueSeverity.tsx | 36 ++---- .../components/issue/components/IssueType.tsx | 34 ++---- .../js/design-system/components/FacetBox.tsx | 13 +- .../resources/org/sonar/l10n/core.properties | 2 + 16 files changed, 339 insertions(+), 87 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-Filtering-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-Filtering-it.tsx index ed965bf5401..0e99d15c437 100644 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-Filtering-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-Filtering-it.tsx @@ -391,6 +391,7 @@ describe('issues app filtering', () => { 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(); @@ -405,6 +406,26 @@ describe('issues app filtering', () => { }), ); }); + + 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', () => { diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx index e2817522cac..051bdfb50ea 100644 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx @@ -74,6 +74,18 @@ describe('issues app', () => { 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', () => { @@ -229,7 +241,7 @@ describe('issues app', () => { 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' })); diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesSourceViewer-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesSourceViewer-it.tsx index 5f0747d8eea..744dc7bfee7 100644 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesSourceViewer-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesSourceViewer-it.tsx @@ -18,7 +18,7 @@ * 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'; @@ -103,7 +103,6 @@ describe('issues source viewer', () => { 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(); @@ -153,12 +152,12 @@ describe('issues source viewer', () => { 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()); @@ -175,10 +174,9 @@ describe('issues source viewer', () => { 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( @@ -204,10 +202,9 @@ describe('issues source viewer', () => { }, ]); 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( @@ -220,17 +217,16 @@ describe('issues source viewer', () => { 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(); }); @@ -249,9 +245,8 @@ describe('issues source viewer', () => { ]); 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(); @@ -273,17 +268,16 @@ describe('issues source viewer', () => { }, ]); 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); diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx index 3f5cdc1685e..4e2ca45bcd6 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx @@ -60,6 +60,7 @@ import { KeyboardKeys } from '../../../helpers/keycodes'; 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 { @@ -109,6 +110,7 @@ interface Props extends WithIndexationContextProps { component?: Component; currentUser: CurrentUser; isFetchingBranch?: boolean; + isStandard?: boolean; location: Location; router: Router; } @@ -154,6 +156,9 @@ export class App extends React.PureComponent { 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, @@ -165,21 +170,23 @@ export class App extends React.PureComponent { 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: {}, @@ -1228,13 +1235,23 @@ export class App extends React.PureComponent { } } +function WrappedApp(props: Readonly>) { + const { data: isStandard, isLoading } = useStandardExperienceMode(); + + return ( + + + + ); +} + export default withRouter( withComponentContext( withCurrentUserContext( withBranchLikes( withIndexationContext( withIndexationGuard({ - Component: App, + Component: WrappedApp, showIndexationMessage: ({ component, indexationContext: { diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx index d6d2620533e..2efb1a1199f 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx @@ -170,6 +170,10 @@ export function Sidebar(props: Readonly) { const needIssueSync = component?.needIssueSync; + const secondLine = translate( + `issues.facet.second_line.mode.${isStandardMode ? 'mqr' : 'standard'}`, + ); + return ( <> {displayPeriodFilter && ( @@ -201,6 +205,39 @@ export function Sidebar(props: Readonly) { + {query.types.length > 0 && ( + <> + + + + )} + + {query.severities.length > 0 && ( + <> + + + + + )} + ) { /> + + {query.impactSoftwareQualities.length > 0 && ( + <> + + + + + )} + + {query.impactSeverities.length > 0 && ( + <> + + + + + )} )} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/SimpleListStyleFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/SimpleListStyleFacet.tsx index edf2f5aa239..d92b019c58a 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/SimpleListStyleFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/SimpleListStyleFacet.tsx @@ -34,6 +34,7 @@ export interface CommonProps { onChange: (changes: Partial) => void; onToggle: (property: string) => void; open: boolean; + secondLine?: string; stats: Dict | undefined; } @@ -50,6 +51,7 @@ export function SimpleListStyleFacet(props: Props) { fetching, open, selectedItems = [], + secondLine, stats = {}, needIssueSync, property, @@ -77,6 +79,7 @@ export function SimpleListStyleFacet(props: Props) { onClick={() => props.onToggle(property)} open={open} help={help} + secondLine={secondLine} > {listItems.map((item) => { diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.tsx index cc7d661267c..a96858bd4bc 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.tsx @@ -35,6 +35,7 @@ interface Props { onChange: (changes: Partial) => void; onToggle: (property: string) => void; open: boolean; + secondLine?: string; stats: Dict | undefined; types: string[]; } @@ -108,7 +109,7 @@ export class TypeFacet extends React.PureComponent { }; 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; @@ -127,6 +128,7 @@ export class TypeFacet extends React.PureComponent { onClear={this.handleClear} onClick={this.handleHeaderClick} open={open} + secondLine={secondLine} > {AVAILABLE_TYPES.map(this.renderItem)} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx index f2b29ef55f4..57d9bd85aa2 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx @@ -25,7 +25,10 @@ import { mockComponent } from '../../../../helpers/mocks/component'; 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'; @@ -75,6 +78,60 @@ describe('MQR mode', () => { ]); }); + 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 }) }); @@ -179,6 +236,64 @@ describe('Standard mode', () => { ]); }); + 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 }) }); diff --git a/server/sonar-web/src/main/js/apps/issues/test-utils.tsx b/server/sonar-web/src/main/js/apps/issues/test-utils.tsx index dccf87e7503..dc6200c22c5 100644 --- a/server/sonar-web/src/main/js/apps/issues/test-utils.tsx +++ b/server/sonar-web/src/main/js/apps/issues/test-utils.tsx @@ -195,8 +195,13 @@ export function renderIssueApp( }, }), featureList: Feature[] = [], + navigateTo?: string, ) { - renderApp('issues', , { currentUser, featureList }); + return renderApp('issues', , { + currentUser, + featureList, + navigateTo, + }); } export function renderProjectIssuesApp( @@ -210,7 +215,7 @@ export function renderProjectIssuesApp( }), featureList = [Feature.BranchSupport], ) { - renderAppWithComponentContext( + return renderAppWithComponentContext( 'project/issues', () => ( ) => void; onToggle: (facet: FacetKey) => void; open: boolean; + secondLine?: string; stats?: Dict; values: string[]; } @@ -103,6 +104,7 @@ export default class Facet extends React.PureComponent { open, property, renderTextName = defaultRenderName, + secondLine, stats, help, values, @@ -140,6 +142,7 @@ export default class Facet extends React.PureComponent { disabledHelper={disabledHelper} tooltipComponent={Tooltip} help={help} + secondLine={secondLine} > {open && items !== undefined && ( {items.map(this.renderItem)} diff --git a/server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx b/server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx index 8269688ec26..1556548d858 100644 --- a/server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx +++ b/server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx @@ -67,7 +67,10 @@ describe('rendering', () => { 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( @@ -383,6 +386,7 @@ function getPageObject() { function renderIssue( props: Partial, 'onChange' | 'onPopupToggle'>> = {}, + query?: string, ) { function Wrapper( wrapperProps: Omit, 'onChange' | 'onPopupToggle'>, @@ -405,7 +409,7 @@ function renderIssue( } return renderAppRoutes( - 'issues?scopes=MAIN&impactSeverities=LOW&types=VULNERABILITY', + `issues${query ? `?${query}` : ''}`, () => ( ) { const { issue, showLine } = props; + const location = useLocation(); const { externalRulesRepoNames } = React.useContext(WorkspaceContext); + const { data: isStandardMode } = useStandardExperienceMode(); const ruleEngine = (issue.externalRuleEngine && externalRulesRepoNames[issue.externalRuleEngine]) || @@ -139,6 +145,17 @@ export default function IssueMetaBar(props: Readonly) { + {!isStandardMode && (location.query.types || location.query.severities) && ( + <> + + + + + + + + + )} {issue.prioritizedRule && ( <> diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueSeverity.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueSeverity.tsx index e5eb59c552a..447718f0f1a 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueSeverity.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/IssueSeverity.tsx @@ -18,14 +18,12 @@ * 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; @@ -33,24 +31,14 @@ interface Props extends IconProps { export default function IssueSeverity({ issue, ...iconProps }: Readonly) { return ( - } - links={[ - { - href: DocLink.Issues, - label: translate('learn_more'), - }, - ]} - > - - - {translate('severity', issue.severity)} - - + + + {translate('severity', issue.severity)} + ); } diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueType.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueType.tsx index e20acdd9991..a5b4d54ca54 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueType.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/IssueType.tsx @@ -18,13 +18,11 @@ * 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; @@ -32,24 +30,14 @@ interface Props extends IconProps { export default function IssueType({ issue, ...iconProps }: Readonly) { return ( - } - links={[ - { - href: DocLink.Issues, - label: translate('learn_more'), - }, - ]} - > - - - {translate('issue.type', issue.type)} - - + + + {translate('issue.type', issue.type)} + ); } diff --git a/server/sonar-web/src/main/js/design-system/components/FacetBox.tsx b/server/sonar-web/src/main/js/design-system/components/FacetBox.tsx index 4b1a4adee2a..bac0c8b20db 100644 --- a/server/sonar-web/src/main/js/design-system/components/FacetBox.tsx +++ b/server/sonar-web/src/main/js/design-system/components/FacetBox.tsx @@ -19,6 +19,7 @@ */ import styled from '@emotion/styled'; +import { Text } from '@sonarsource/echoes-react'; import classNames from 'classnames'; import { uniqueId } from 'lodash'; import * as React from 'react'; @@ -51,6 +52,7 @@ export interface FacetBoxProps { onClear?: () => void; onClick?: (isOpen: boolean) => void; open?: boolean; + secondLine?: string; tooltipComponent?: React.ComponentType>; } @@ -65,6 +67,7 @@ export function FacetBox(props: FacetBoxProps) { 'data-property': dataProperty, disabled = false, disabledHelper, + secondLine, hasEmbeddedFacets = false, help, id: idProp, @@ -82,6 +85,7 @@ export function FacetBox(props: FacetBoxProps) { const expandable = !disabled && Boolean(onClick); const id = React.useMemo(() => idProp ?? uniqueId('filter-facet-'), [idProp]); const Tooltip = tooltipComponent ?? SCTooltip; + return ( ) : ( - {name} +
+ {name} + {secondLine !== undefined && ( + + {secondLine} + + )} +
)} {help && {help}} diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 9e9d817c5dc..5355fdf72ea 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1263,6 +1263,8 @@ issues.facet.cwe=CWE 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 #------------------------------------------------------------------------------ # -- 2.39.5