@@ -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> | |||
); | |||
} |
@@ -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> | |||
); | |||
} |
@@ -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} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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(); | |||
}); |
@@ -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> |
@@ -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", |
@@ -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 |