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';
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 = [];
if (createdInLast === '1y') {
values.push(translate('issues.facet.createdAt.last_year'));
}
- if (inNewCodePeriod) {
- values.push(translate('issues.new_code'));
- }
return values;
}
}
renderPredefinedPeriods() {
- const { component, createdInLast, inNewCodePeriod } = this.props;
+ const { createdInLast } = this.props;
return (
<div className="spacer-top issues-predefined-periods">
<FacetItem
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>
);
}
--- /dev/null
+/*
+ * 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>
+ );
+}
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,
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';
(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}
--- /dev/null
+/*
+ * 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}
+ />
+ );
+}
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();
});
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>
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>
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>
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>
exports[`should render facets for "APP" 1`] = `
Array [
+ "PeriodFilter",
"TypeFacet",
"SeverityFacet",
"ScopeFacet",
]
`;
-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",
exports[`should render facets for project 1`] = `
Array [
+ "PeriodFilter",
"TypeFacet",
"SeverityFacet",
"ScopeFacet",
# ISSUES FACETS
#
#------------------------------------------------------------------------------
+issues.facet.period=Period
issues.facet.types=Type
issues.facet.severities=Severity
issues.facet.scopes=Scope