Browse Source

SONAR-13924 Improve visibility of new code facet on issues page

tags/9.6.0.59041
Jeremy Davis 1 year ago
parent
commit
765797aaf2

+ 24
- 42
server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.tsx View File

@@ -34,7 +34,6 @@ import DateTimeFormatter, {
import { parseDate } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import { isPortfolioLike } from '../../../types/component';
import { Component, Dict } from '../../../types/types';
import { Query } from '../utils';

@@ -102,16 +101,13 @@ export class CreationDateFacet extends React.PureComponent<Props & WrappedCompon

handlePeriodClick = (period: string) => this.resetTo({ createdInLast: period });

handleLeakPeriodClick = () => this.resetTo({ inNewCodePeriod: true });

getValues() {
const {
createdAfter,
createdAfterIncludesTime,
createdAt,
createdBefore,
createdInLast,
inNewCodePeriod
createdInLast
} = this.props;
const { formatDate } = this.props.intl;
const values = [];
@@ -138,9 +134,6 @@ export class CreationDateFacet extends React.PureComponent<Props & WrappedCompon
if (createdInLast === '1y') {
values.push(translate('issues.facet.createdAt.last_year'));
}
if (inNewCodePeriod) {
values.push(translate('issues.new_code'));
}
return values;
}

@@ -221,7 +214,7 @@ export class CreationDateFacet extends React.PureComponent<Props & WrappedCompon
}

renderPredefinedPeriods() {
const { component, createdInLast, inNewCodePeriod } = this.props;
const { createdInLast } = this.props;
return (
<div className="spacer-top issues-predefined-periods">
<FacetItem
@@ -231,39 +224,28 @@ export class CreationDateFacet extends React.PureComponent<Props & WrappedCompon
tooltip={translate('issues.facet.createdAt.all')}
value=""
/>
{component && !isPortfolioLike(component.qualifier) ? (
<FacetItem
active={inNewCodePeriod}
name={translate('issues.new_code')}
onClick={this.handleLeakPeriodClick}
tooltip={translate('issues.new_code_period')}
value=""
/>
) : (
<>
<FacetItem
active={createdInLast === '1w'}
name={translate('issues.facet.createdAt.last_week')}
onClick={this.handlePeriodClick}
tooltip={translate('issues.facet.createdAt.last_week')}
value="1w"
/>
<FacetItem
active={createdInLast === '1m'}
name={translate('issues.facet.createdAt.last_month')}
onClick={this.handlePeriodClick}
tooltip={translate('issues.facet.createdAt.last_month')}
value="1m"
/>
<FacetItem
active={createdInLast === '1y'}
name={translate('issues.facet.createdAt.last_year')}
onClick={this.handlePeriodClick}
tooltip={translate('issues.facet.createdAt.last_year')}
value="1y"
/>
</>
)}

<FacetItem
active={createdInLast === '1w'}
name={translate('issues.facet.createdAt.last_week')}
onClick={this.handlePeriodClick}
tooltip={translate('issues.facet.createdAt.last_week')}
value="1w"
/>
<FacetItem
active={createdInLast === '1m'}
name={translate('issues.facet.createdAt.last_month')}
onClick={this.handlePeriodClick}
tooltip={translate('issues.facet.createdAt.last_month')}
value="1m"
/>
<FacetItem
active={createdInLast === '1y'}
name={translate('issues.facet.createdAt.last_year')}
onClick={this.handlePeriodClick}
tooltip={translate('issues.facet.createdAt.last_year')}
value="1y"
/>
</div>
);
}

+ 87
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/PeriodFilter.tsx View File

@@ -0,0 +1,87 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import 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 { formatFacetStat, Query } from '../utils';

export interface PeriodFilterProps {
fetching: boolean;
onChange: (changes: Partial<Query>) => void;
stats: Dict<number> | undefined;
newCodeSelected: boolean;
}

enum Period {
NewCode = 'inNewCodePeriod'
}

const PROPERTY = 'period';

export default function PeriodFilter(props: PeriodFilterProps) {
const { fetching, newCodeSelected, stats = {} } = props;

const [open, setOpen] = React.useState(true);

const { onChange } = props;
const handleClick = React.useCallback(() => {
// We need to clear creation date filters they conflict with the new code period
onChange({
createdAfter: undefined,
createdAt: undefined,
createdBefore: undefined,
createdInLast: undefined,
[Period.NewCode]: !newCodeSelected
});
}, [newCodeSelected, onChange]);

const handleClear = React.useCallback(() => {
onChange({ [Period.NewCode]: undefined });
}, [onChange]);

return (
<FacetBox property={PROPERTY}>
<FacetHeader
fetching={fetching}
name={translate('issues.facet', PROPERTY)}
onClear={handleClear}
onClick={() => setOpen(!open)}
open={open}
values={newCodeSelected ? [translate('issues.new_code')] : undefined}
/>

{open && (
<FacetItemsList>
<FacetItem
active={newCodeSelected}
name={translate('issues.new_code')}
onClick={handleClick}
stat={formatFacetStat(stats[Period.NewCode])}
value={Period.NewCode}
/>
</FacetItemsList>
)}
</FacetBox>
);
}

+ 17
- 3
server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx View File

@@ -22,7 +22,12 @@ import withAppStateContext from '../../../app/components/app-state/withAppStateC
import { isBranch, isPullRequest } from '../../../helpers/branch-like';
import { AppState } from '../../../types/appstate';
import { BranchLike } from '../../../types/branch-like';
import { ComponentQualifier, isApplication, isPortfolioLike } from '../../../types/component';
import {
ComponentQualifier,
isApplication,
isPortfolioLike,
isView
} from '../../../types/component';
import {
Facet,
ReferencedComponent,
@@ -39,6 +44,7 @@ import CreationDateFacet from './CreationDateFacet';
import DirectoryFacet from './DirectoryFacet';
import FileFacet from './FileFacet';
import LanguageFacet from './LanguageFacet';
import PeriodFilter from './PeriodFilter';
import ProjectFacet from './ProjectFacet';
import ResolutionFacet from './ResolutionFacet';
import RuleFacet from './RuleFacet';
@@ -127,12 +133,20 @@ export class Sidebar extends React.PureComponent<Props> {
(isPullRequest(branchLike) && branchLike.branch) ||
undefined;

const displayProjectsFacet =
!component || !['TRK', 'BRC', 'DIR', 'DEV_PRJ'].includes(component.qualifier);
const displayPeriodFilter = component !== undefined && !isPortfolioLike(component.qualifier);
const displayProjectsFacet = !component || isView(component.qualifier);
const displayAuthorFacet = !component || component.qualifier !== 'DEV';

return (
<>
{displayPeriodFilter && (
<PeriodFilter
fetching={this.props.loadingFacets.period === true}
onChange={this.props.onFilterChange}
stats={facets.period}
newCodeSelected={query.inNewCodePeriod}
/>
)}
<TypeFacet
fetching={this.props.loadingFacets.types === true}
onChange={this.props.onFilterChange}

+ 78
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/PeriodFilter-test.tsx View File

@@ -0,0 +1,78 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import * as React from 'react';
import PeriodFilter, { PeriodFilterProps } from '../PeriodFilter';

it('should be collapsible', async () => {
const user = userEvent.setup();

renderPeriodFilter();

expect(screen.getByText('issues.new_code')).toBeInTheDocument();

await user.click(screen.getByText('issues.facet.period'));

expect(screen.queryByText('issues.new_code')).not.toBeInTheDocument();
});

it('should filter when clicked', async () => {
const user = userEvent.setup();
const onChange = jest.fn();

renderPeriodFilter({ onChange });

await user.click(screen.getByText('issues.new_code'));

expect(onChange).toBeCalledWith({
createdAfter: undefined,
createdAt: undefined,
createdBefore: undefined,
createdInLast: undefined,
inNewCodePeriod: true
});
});

it('should be clearable', async () => {
const user = userEvent.setup();
const onChange = jest.fn();

renderPeriodFilter({ onChange, newCodeSelected: true });

await user.click(screen.getByText('clear'));

expect(onChange).toBeCalledWith({
inNewCodePeriod: undefined
});
});

function renderPeriodFilter(overrides: Partial<PeriodFilterProps> = {}) {
return render(
<PeriodFilter
fetching={false}
newCodeSelected={false}
onChange={jest.fn()}
stats={{}}
{...overrides}
/>
);
}

+ 1
- 3
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-test.tsx View File

@@ -38,9 +38,7 @@ it('should render facets for project', () => {
it.each([
[ComponentQualifier.Application],
[ComponentQualifier.Portfolio],
[ComponentQualifier.SubPortfolio],
[ComponentQualifier.Directory],
[ComponentQualifier.Developper]
[ComponentQualifier.SubPortfolio]
])('should render facets for %p', qualifier => {
expect(renderSidebar({ component: mockComponent({ qualifier }) })).toMatchSnapshot();
});

+ 92
- 12
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/CreationDateFacet-test.tsx.snap View File

@@ -48,10 +48,30 @@ exports[`should render correctly for createdInLast month 1`] = `
disabled={false}
halfWidth={false}
loading={false}
name="issues.new_code"
name="issues.facet.createdAt.last_week"
onClick={[Function]}
tooltip="issues.new_code_period"
value=""
tooltip="issues.facet.createdAt.last_week"
value="1w"
/>
<FacetItem
active={true}
disabled={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_month"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_month"
value="1m"
/>
<FacetItem
active={false}
disabled={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_year"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_year"
value="1y"
/>
</div>
</div>
@@ -101,15 +121,35 @@ exports[`should render correctly for createdInLast week 1`] = `
tooltip="issues.facet.createdAt.all"
value=""
/>
<FacetItem
active={true}
disabled={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_week"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_week"
value="1w"
/>
<FacetItem
active={false}
disabled={false}
halfWidth={false}
loading={false}
name="issues.new_code"
name="issues.facet.createdAt.last_month"
onClick={[Function]}
tooltip="issues.new_code_period"
value=""
tooltip="issues.facet.createdAt.last_month"
value="1m"
/>
<FacetItem
active={false}
disabled={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_year"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_year"
value="1y"
/>
</div>
</div>
@@ -164,10 +204,30 @@ exports[`should render correctly for createdInLast year 1`] = `
disabled={false}
halfWidth={false}
loading={false}
name="issues.new_code"
name="issues.facet.createdAt.last_week"
onClick={[Function]}
tooltip="issues.new_code_period"
value=""
tooltip="issues.facet.createdAt.last_week"
value="1w"
/>
<FacetItem
active={false}
disabled={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_month"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_month"
value="1m"
/>
<FacetItem
active={true}
disabled={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_year"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_year"
value="1y"
/>
</div>
</div>
@@ -475,10 +535,30 @@ exports[`should render correctly: project 1`] = `
disabled={false}
halfWidth={false}
loading={false}
name="issues.new_code"
name="issues.facet.createdAt.last_week"
onClick={[Function]}
tooltip="issues.new_code_period"
value=""
tooltip="issues.facet.createdAt.last_week"
value="1w"
/>
<FacetItem
active={false}
disabled={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_month"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_month"
value="1m"
/>
<FacetItem
active={false}
disabled={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_year"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_year"
value="1y"
/>
</div>
</div>

+ 2
- 37
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/Sidebar-test.tsx.snap View File

@@ -18,6 +18,7 @@ Array [

exports[`should render facets for "APP" 1`] = `
Array [
"PeriodFilter",
"TypeFacet",
"SeverityFacet",
"ScopeFacet",
@@ -34,43 +35,6 @@ Array [
]
`;

exports[`should render facets for "DEV" 1`] = `
Array [
"TypeFacet",
"SeverityFacet",
"ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
"withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"ProjectFacet",
"DirectoryFacet",
"FileFacet",
"AssigneeFacet",
]
`;

exports[`should render facets for "DIR" 1`] = `
Array [
"TypeFacet",
"SeverityFacet",
"ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
"withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"FileFacet",
"AssigneeFacet",
"AuthorFacet",
]
`;

exports[`should render facets for "SVW" 1`] = `
Array [
"TypeFacet",
@@ -127,6 +91,7 @@ Array [

exports[`should render facets for project 1`] = `
Array [
"PeriodFilter",
"TypeFacet",
"SeverityFacet",
"ScopeFacet",

+ 1
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -959,6 +959,7 @@ issue.changelog.field.file=File
# ISSUES FACETS
#
#------------------------------------------------------------------------------
issues.facet.period=Period
issues.facet.types=Type
issues.facet.severities=Severity
issues.facet.scopes=Scope

Loading…
Cancel
Save