@Test | @Test | ||||
public void should_sort_by_facet() { | public void should_sort_by_facet() { | ||||
ProjectsPage page = nav.openProjects(); | ProjectsPage page = nav.openProjects(); | ||||
page.getFacetByProperty("duplications") | |||||
.sortListDesc(); | |||||
page.sortProjects("Duplications"); | |||||
page.getProjectByIdx(0).shouldHaveMeasure("duplicated_lines_density", "63.7%"); | page.getProjectByIdx(0).shouldHaveMeasure("duplicated_lines_density", "63.7%"); | ||||
page.getFacetByProperty("duplications") | |||||
.sortListAsc(); | |||||
page.invertSorting(); | |||||
page.getProjectByIdx(0).shouldHaveMeasure("duplicated_lines_density", "0.0%"); | page.getProjectByIdx(0).shouldHaveMeasure("duplicated_lines_density", "0.0%"); | ||||
} | } | ||||
selectInput.pressEnter(); | selectInput.pressEnter(); | ||||
return this; | return this; | ||||
} | } | ||||
private SelenideElement getSortingButton(String selector) { | |||||
ElementsCollection buttons = this.elt.$$(".projects-facet-sort a"); | |||||
return buttons.find(new Condition("AttributeMatch") { | |||||
@Override | |||||
public boolean apply(WebElement webElement) { | |||||
return webElement.getAttribute("href").matches(".*sort=" + selector + ".*"); | |||||
} | |||||
}); | |||||
} | |||||
public FacetItem sortListDesc() { | |||||
this.getSortingButton("-").click(); | |||||
return this; | |||||
} | |||||
public FacetItem sortListAsc() { | |||||
this.getSortingButton("[a-zA-Z ]").click(); | |||||
return this; | |||||
} | |||||
} | } |
searchInput.setValue("").sendKeys(search); | searchInput.setValue("").sendKeys(search); | ||||
return this; | return this; | ||||
} | } | ||||
public ProjectsPage sortProjects(String sort) { | |||||
SelenideElement topbar = $(".projects-topbar-actions").should(Condition.exist); | |||||
if (!topbar.has(Condition.hasClass("open"))){ | |||||
$(".js-projects-topbar-open").click(); | |||||
} | |||||
topbar.should(Condition.hasClass("open")); | |||||
SelenideElement sortSelect = topbar.$(".js-projects-sorting-select"); | |||||
sortSelect.$(".Select-value").should(Condition.exist).click(); | |||||
sortSelect.$(".Select-option[title='" + sort + "']").should(Condition.exist).click(); | |||||
return this; | |||||
} | |||||
public ProjectsPage invertSorting() { | |||||
SelenideElement topbar = $(".projects-topbar-actions").should(Condition.exist); | |||||
if (!topbar.has(Condition.hasClass("open"))){ | |||||
$(".js-projects-topbar-open").click(); | |||||
} | |||||
topbar.should(Condition.hasClass("open")); | |||||
topbar.$(".js-projects-sorting-select a.button-icon").should(Condition.exist).click(); | |||||
return this; | |||||
} | |||||
} | } |
import React from 'react'; | import React from 'react'; | ||||
import Helmet from 'react-helmet'; | import Helmet from 'react-helmet'; | ||||
import PageHeaderContainer from './PageHeaderContainer'; | import PageHeaderContainer from './PageHeaderContainer'; | ||||
import ProjectOptionBar from './ProjectOptionBar'; | |||||
import ProjectsOptionBar from './ProjectsOptionBar'; | |||||
import ProjectsListContainer from './ProjectsListContainer'; | import ProjectsListContainer from './ProjectsListContainer'; | ||||
import ProjectsListFooterContainer from './ProjectsListFooterContainer'; | import ProjectsListFooterContainer from './ProjectsListFooterContainer'; | ||||
import PageSidebar from './PageSidebar'; | import PageSidebar from './PageSidebar'; | ||||
import VisualizationsContainer from '../visualizations/VisualizationsContainer'; | import VisualizationsContainer from '../visualizations/VisualizationsContainer'; | ||||
import { parseUrlQuery } from '../store/utils'; | import { parseUrlQuery } from '../store/utils'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import { SORTING_SWITCH, parseSorting } from '../utils'; | |||||
import '../styles.css'; | import '../styles.css'; | ||||
type Props = { | type Props = { | ||||
handleOptionBarToggle = (open: boolean) => this.setState({ optionBarOpen: open }); | handleOptionBarToggle = (open: boolean) => this.setState({ optionBarOpen: open }); | ||||
handlePerspectiveChange = ({ view, visualization }: { view: string, visualization?: string }) => { | handlePerspectiveChange = ({ view, visualization }: { view: string, visualization?: string }) => { | ||||
this.props.router.push({ | |||||
pathname: this.props.location.pathname, | |||||
query: { | |||||
...this.props.location.query, | |||||
view: view === 'overall' ? undefined : view, | |||||
visualization | |||||
const query: { view: ?string, visualization: ?string, sort?: ?string } = { | |||||
view: view === 'overall' ? undefined : view, | |||||
visualization | |||||
}; | |||||
if (this.state.query.view === 'leak' || view === 'leak') { | |||||
if (this.state.query.sort) { | |||||
const sort = parseSorting(this.state.query.sort); | |||||
if (SORTING_SWITCH[sort.sortValue]) { | |||||
query.sort = (sort.sortDesc ? '-' : '') + SORTING_SWITCH[sort.sortValue]; | |||||
} | |||||
} | } | ||||
}); | |||||
this.props.router.push({ pathname: this.props.location.pathname, query }); | |||||
} else { | |||||
this.updateLocationQuery(query); | |||||
} | |||||
}; | |||||
handleSortChange = (sort: string, desc: boolean) => { | |||||
if (sort === 'name' && !desc) { | |||||
this.updateLocationQuery({ sort: undefined }); | |||||
} else { | |||||
this.updateLocationQuery({ sort: (desc ? '-' : '') + sort }); | |||||
} | |||||
}; | }; | ||||
handleQueryChange() { | handleQueryChange() { | ||||
this.props.fetchProjects(query, this.props.isFavorite, this.props.organization); | this.props.fetchProjects(query, this.props.isFavorite, this.props.organization); | ||||
} | } | ||||
updateLocationQuery = (newQuery: { [string]: ?string }) => { | |||||
this.props.router.push({ | |||||
pathname: this.props.location.pathname, | |||||
query: { | |||||
...this.props.location.query, | |||||
...newQuery | |||||
} | |||||
}); | |||||
}; | |||||
render() { | render() { | ||||
const { query, optionBarOpen } = this.state; | const { query, optionBarOpen } = this.state; | ||||
const isFiltered = Object.keys(query).some(key => query[key] != null); | const isFiltered = Object.keys(query).some(key => query[key] != null); | ||||
const view = query.view || 'overall'; | const view = query.view || 'overall'; | ||||
const visualization = query.visualization || 'risk'; | const visualization = query.visualization || 'risk'; | ||||
const selectedSort = query.sort || 'name'; | |||||
const top = (this.props.organization ? 95 : 30) + (optionBarOpen ? 45 : 0); | const top = (this.props.organization ? 95 : 30) + (optionBarOpen ? 45 : 0); | ||||
<div> | <div> | ||||
<Helmet title={translate('projects.page')} /> | <Helmet title={translate('projects.page')} /> | ||||
<ProjectOptionBar | |||||
<ProjectsOptionBar | |||||
onPerspectiveChange={this.handlePerspectiveChange} | onPerspectiveChange={this.handlePerspectiveChange} | ||||
onSortChange={this.handleSortChange} | |||||
onToggleOptionBar={this.handleOptionBarToggle} | onToggleOptionBar={this.handleOptionBarToggle} | ||||
open={optionBarOpen} | open={optionBarOpen} | ||||
selectedSort={selectedSort} | |||||
view={view} | view={view} | ||||
visualization={visualization} | visualization={visualization} | ||||
/> | /> |
export default function PageHeader(props: Props) { | export default function PageHeader(props: Props) { | ||||
return ( | return ( | ||||
<header className="page-header"> | <header className="page-header"> | ||||
<div className="page-actions projects-page-actions"> | |||||
<div className="text-right spacer-bottom"> | |||||
<a className="button" href="#" onClick={props.onOpenOptionBar}> | |||||
<div className="page-actions projects-page-actions text-right"> | |||||
<div className="spacer-bottom"> | |||||
<a className="button js-projects-topbar-open" href="#" onClick={props.onOpenOptionBar}> | |||||
{translate('projects.view_settings')} | {translate('projects.view_settings')} | ||||
</a> | </a> | ||||
</div> | </div> |
import NewMaintainabilityFilter from '../filters/NewMaintainabilityFilter'; | import NewMaintainabilityFilter from '../filters/NewMaintainabilityFilter'; | ||||
import NewReliabilityFilter from '../filters/NewReliabilityFilter'; | import NewReliabilityFilter from '../filters/NewReliabilityFilter'; | ||||
import NewSecurityFilter from '../filters/NewSecurityFilter'; | import NewSecurityFilter from '../filters/NewSecurityFilter'; | ||||
import NewSizeFilter from '../filters/NewSizeFilter'; | |||||
import NewLinesFilter from '../filters/NewLinesFilter'; | |||||
import QualityGateFilter from '../filters/QualityGateFilter'; | import QualityGateFilter from '../filters/QualityGateFilter'; | ||||
import ReliabilityFilter from '../filters/ReliabilityFilter'; | import ReliabilityFilter from '../filters/ReliabilityFilter'; | ||||
import SecurityFilter from '../filters/SecurityFilter'; | import SecurityFilter from '../filters/SecurityFilter'; | ||||
<NewMaintainabilityFilter key="new_maintainability" {...facetProps} />, | <NewMaintainabilityFilter key="new_maintainability" {...facetProps} />, | ||||
<NewCoverageFilter key="new_coverage" {...facetProps} />, | <NewCoverageFilter key="new_coverage" {...facetProps} />, | ||||
<NewDuplicationsFilter key="new_duplications" {...facetProps} />, | <NewDuplicationsFilter key="new_duplications" {...facetProps} />, | ||||
<NewSizeFilter key="new_size" {...facetProps} /> | |||||
<NewLinesFilter key="new_size" {...facetProps} /> | |||||
]} | ]} | ||||
<LanguagesFilterContainer {...facetProps} /> | <LanguagesFilterContainer {...facetProps} /> | ||||
<TagsFilterContainer {...facetProps} /> | <TagsFilterContainer {...facetProps} /> |
export type Option = { label: string, type: string, value: string }; | export type Option = { label: string, type: string, value: string }; | ||||
type Props = { | type Props = { | ||||
className?: string, | |||||
onChange: ({ view: string, visualization?: string }) => void, | onChange: ({ view: string, visualization?: string }) => void, | ||||
view: string, | view: string, | ||||
visualization?: string | visualization?: string | ||||
const { view, visualization } = this.props; | const { view, visualization } = this.props; | ||||
const perspective = view === 'visualizations' ? visualization : view; | const perspective = view === 'visualizations' ? visualization : view; | ||||
return ( | return ( | ||||
<div> | |||||
<div className={this.props.className}> | |||||
<label>{translate('projects.perspective')}:</label> | <label>{translate('projects.perspective')}:</label> | ||||
<Select | <Select | ||||
className="little-spacer-left input-medium" | className="little-spacer-left input-medium" |
import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
import CloseIcon from '../../../components/icons-components/CloseIcon'; | import CloseIcon from '../../../components/icons-components/CloseIcon'; | ||||
import PerspectiveSelect from './PerspectiveSelect'; | import PerspectiveSelect from './PerspectiveSelect'; | ||||
import ProjectsSortingSelect from './ProjectsSortingSelect'; | |||||
type Props = { | type Props = { | ||||
onPerspectiveChange: ({ view: string, visualization?: string }) => void, | onPerspectiveChange: ({ view: string, visualization?: string }) => void, | ||||
onSortChange: (sort: string, desc: boolean) => void, | |||||
onToggleOptionBar: boolean => void, | onToggleOptionBar: boolean => void, | ||||
open: boolean, | open: boolean, | ||||
selectedSort: string, | |||||
view: string, | view: string, | ||||
visualization?: string | visualization?: string | ||||
}; | }; | ||||
export default class ProjectOptionBar extends React.PureComponent { | |||||
export default class ProjectsOptionBar extends React.PureComponent { | |||||
props: Props; | props: Props; | ||||
closeBar = (evt: Event & { currentTarget: HTMLElement }) => { | closeBar = (evt: Event & { currentTarget: HTMLElement }) => { | ||||
return ( | return ( | ||||
<div className="projects-topbar"> | <div className="projects-topbar"> | ||||
<div className={classNames('projects-topbar-actions', { open })}> | <div className={classNames('projects-topbar-actions', { open })}> | ||||
<a | |||||
className="projects-topbar-button projects-topbar-button-close" | |||||
href="#" | |||||
onClick={this.closeBar}> | |||||
<a className="projects-topbar-button button-icon" href="#" onClick={this.closeBar}> | |||||
<CloseIcon /> | <CloseIcon /> | ||||
</a> | </a> | ||||
<div className="projects-topbar-actions-inner"> | <div className="projects-topbar-actions-inner"> | ||||
<PerspectiveSelect | <PerspectiveSelect | ||||
className="projects-topbar-item js-projects-perspective-select" | |||||
onChange={this.props.onPerspectiveChange} | onChange={this.props.onPerspectiveChange} | ||||
view={this.props.view} | view={this.props.view} | ||||
visualization={this.props.visualization} | visualization={this.props.visualization} | ||||
/> | /> | ||||
<ProjectsSortingSelect | |||||
className="projects-topbar-item js-projects-sorting-select" | |||||
onChange={this.props.onSortChange} | |||||
selectedSort={this.props.selectedSort} | |||||
view={this.props.view} | |||||
/> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 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. | |||||
*/ | |||||
// @flow | |||||
import React from 'react'; | |||||
import Select from 'react-select'; | |||||
import ProjectsSortingSelectOption from './ProjectsSortingSelectOption'; | |||||
import SortAscIcon from '../../../components/icons-components/SortAscIcon'; | |||||
import SortDescIcon from '../../../components/icons-components/SortDescIcon'; | |||||
import Tooltip from '../../../components/controls/Tooltip'; | |||||
import { translate } from '../../../helpers/l10n'; | |||||
import { SORTING_METRICS, SORTING_LEAK_METRICS, parseSorting } from '../utils'; | |||||
export type Option = { label: string, value: string, complement?: string, short?: string }; | |||||
type Props = { | |||||
className?: string, | |||||
onChange: (sort: string, desc: boolean) => void, | |||||
selectedSort: string, | |||||
view: string | |||||
}; | |||||
type State = { | |||||
sortValue: string, | |||||
sortDesc: boolean | |||||
}; | |||||
export default class ProjectsSortingSelect extends React.PureComponent { | |||||
props: Props; | |||||
state: State; | |||||
constructor(props: Props) { | |||||
super(props); | |||||
this.state = parseSorting(props.selectedSort); | |||||
} | |||||
componentDidUpdate(prevProps: Props) { | |||||
if (prevProps.selectedSort !== this.props.selectedSort) { | |||||
this.setState(parseSorting(this.props.selectedSort)); | |||||
} | |||||
} | |||||
getOptions = () => { | |||||
const sortMetrics = this.props.view === 'leak' ? SORTING_LEAK_METRICS : SORTING_METRICS; | |||||
return sortMetrics.map((opt: { value: string, complement?: string }) => ({ | |||||
value: opt.value, | |||||
label: translate('projects.sorting', opt.value), | |||||
complement: opt.complement && translate('projects.sorting', opt.complement), | |||||
short: opt.complement && translate('projects.sorting', opt.value, 'short') | |||||
})); | |||||
}; | |||||
handleDescToggle = (evt: Event & { currentTarget: HTMLElement }) => { | |||||
evt.preventDefault(); | |||||
evt.currentTarget.blur(); | |||||
this.props.onChange(this.state.sortValue, !this.state.sortDesc); | |||||
}; | |||||
handleSortChange = (option: Option) => this.props.onChange(option.value, this.state.sortDesc); | |||||
render() { | |||||
const { sortDesc } = this.state; | |||||
return ( | |||||
<div className={this.props.className}> | |||||
<label>{translate('projects.sort_by')}:</label> | |||||
<Select | |||||
className="little-spacer-left input-large" | |||||
clearable={false} | |||||
onChange={this.handleSortChange} | |||||
options={this.getOptions()} | |||||
optionComponent={ProjectsSortingSelectOption} | |||||
searchable={false} | |||||
value={this.state.sortValue} | |||||
/> | |||||
<Tooltip | |||||
overlay={ | |||||
sortDesc ? translate('projects.sort_descending') : translate('projects.sort_ascending') | |||||
}> | |||||
<a className="spacer-left button-icon" href="#" onClick={this.handleDescToggle}> | |||||
{sortDesc | |||||
? <SortDescIcon className="little-spacer-top" /> | |||||
: <SortAscIcon className="little-spacer-top" />} | |||||
</a> | |||||
</Tooltip> | |||||
</div> | |||||
); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 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. | |||||
*/ | |||||
//@flow | |||||
import React from 'react'; | |||||
import type { Option } from './ProjectsSortingSelect'; | |||||
type Props = { | |||||
option: Option, | |||||
children?: Element | Text, | |||||
className?: string, | |||||
isFocused?: boolean, | |||||
onFocus: (Option, MouseEvent) => void, | |||||
onSelect: (Option, MouseEvent) => void | |||||
}; | |||||
export default class ProjectsSortingSelectOption extends React.PureComponent { | |||||
props: Props; | |||||
handleMouseDown = (event: MouseEvent) => { | |||||
event.preventDefault(); | |||||
event.stopPropagation(); | |||||
this.props.onSelect(this.props.option, event); | |||||
}; | |||||
handleMouseEnter = (event: MouseEvent) => { | |||||
this.props.onFocus(this.props.option, event); | |||||
}; | |||||
handleMouseMove = (event: MouseEvent) => { | |||||
if (this.props.isFocused) { | |||||
return; | |||||
} | |||||
this.props.onFocus(this.props.option, event); | |||||
}; | |||||
render() { | |||||
const { option } = this.props; | |||||
return ( | |||||
<div | |||||
className={this.props.className} | |||||
onMouseDown={this.handleMouseDown} | |||||
onMouseEnter={this.handleMouseEnter} | |||||
onMouseMove={this.handleMouseMove} | |||||
title={option.label}> | |||||
{option.short ? option.short : this.props.children} | |||||
{option.complement && | |||||
<div className="pull-right text-muted-2"> | |||||
{option.complement} | |||||
</div>} | |||||
</div> | |||||
); | |||||
} | |||||
} |
*/ | */ | ||||
import React from 'react'; | import React from 'react'; | ||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import ProjectOptionBar from '../ProjectOptionBar'; | |||||
import ProjectsOptionBar from '../ProjectsOptionBar'; | |||||
import { click } from '../../../../helpers/testUtils'; | import { click } from '../../../../helpers/testUtils'; | ||||
it('should render option bar closed', () => { | it('should render option bar closed', () => { | ||||
expect(shallow(<ProjectOptionBar open={false} view="overall" />)).toMatchSnapshot(); | |||||
expect(shallow(<ProjectsOptionBar open={false} view="overall" />)).toMatchSnapshot(); | |||||
}); | }); | ||||
it('should render option bar open', () => { | it('should render option bar open', () => { | ||||
expect( | expect( | ||||
shallow(<ProjectOptionBar open={true} view="visualizations" visualization="coverage" />) | |||||
shallow(<ProjectsOptionBar open={true} view="leak" visualization="risk" />) | |||||
).toMatchSnapshot(); | |||||
}); | |||||
it.skip('should not render sorting options for visualizations', () => { | |||||
expect( | |||||
shallow(<ProjectsOptionBar open={true} view="visualizations" visualization="coverage" />) | |||||
).toMatchSnapshot(); | ).toMatchSnapshot(); | ||||
}); | }); | ||||
it('should call close method correctly', () => { | it('should call close method correctly', () => { | ||||
const toggle = jest.fn(); | const toggle = jest.fn(); | ||||
const wrapper = shallow(<ProjectOptionBar open={true} view="leak" onToggleOptionBar={toggle} />); | |||||
click(wrapper.find('a.projects-topbar-button-close')); | |||||
const wrapper = shallow(<ProjectsOptionBar open={true} view="leak" onToggleOptionBar={toggle} />); | |||||
click(wrapper.find('a.projects-topbar-button')); | |||||
expect(toggle.mock.calls).toMatchSnapshot(); | expect(toggle.mock.calls).toMatchSnapshot(); | ||||
}); | }); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 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 React from 'react'; | |||||
import { shallow } from 'enzyme'; | |||||
import ProjectsSortingSelect from '../ProjectsSortingSelect'; | |||||
it('should render correctly for overall view', () => { | |||||
expect(shallow(<ProjectsSortingSelect selectedSort="name" view="overall" />)).toMatchSnapshot(); | |||||
}); | |||||
it('should render correctly for leak view', () => { | |||||
expect( | |||||
shallow(<ProjectsSortingSelect selectedSort="new_coverage" view="leak" />) | |||||
).toMatchSnapshot(); | |||||
}); | |||||
it('should handle the descending sort direction', () => { | |||||
expect( | |||||
shallow(<ProjectsSortingSelect selectedSort="-vulnerability" view="overall" />) | |||||
).toMatchSnapshot(); | |||||
}); |
} | } | ||||
} | } | ||||
/> | /> | ||||
<NewSizeFilter | |||||
<NewLinesFilter | |||||
isFavorite={false} | isFavorite={false} | ||||
property="new_lines" | property="new_lines" | ||||
query={ | query={ |
] | ] | ||||
`; | `; | ||||
exports[`should not render sorting options for visualizations 1`] = ` | |||||
<div | |||||
className="projects-topbar" | |||||
> | |||||
<div | |||||
className="projects-topbar-actions open" | |||||
> | |||||
<a | |||||
className="projects-topbar-button button-icon" | |||||
href="#" | |||||
onClick={[Function]} | |||||
> | |||||
<CloseIcon /> | |||||
</a> | |||||
<div | |||||
className="projects-topbar-actions-inner" | |||||
> | |||||
<PerspectiveSelect | |||||
className="projects-topbar-item" | |||||
view="visualizations" | |||||
visualization="coverage" | |||||
/> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
`; | |||||
exports[`should render option bar closed 1`] = ` | exports[`should render option bar closed 1`] = ` | ||||
<div | <div | ||||
className="projects-topbar" | className="projects-topbar" | ||||
className="projects-topbar-actions" | className="projects-topbar-actions" | ||||
> | > | ||||
<a | <a | ||||
className="projects-topbar-button projects-topbar-button-close" | |||||
className="projects-topbar-button button-icon" | |||||
href="#" | href="#" | ||||
onClick={[Function]} | onClick={[Function]} | ||||
> | > | ||||
className="projects-topbar-actions-inner" | className="projects-topbar-actions-inner" | ||||
> | > | ||||
<PerspectiveSelect | <PerspectiveSelect | ||||
className="projects-topbar-item js-projects-perspective-select" | |||||
view="overall" | |||||
/> | |||||
<ProjectsSortingSelect | |||||
className="projects-topbar-item js-projects-sorting-select" | |||||
view="overall" | view="overall" | ||||
/> | /> | ||||
</div> | </div> | ||||
className="projects-topbar-actions open" | className="projects-topbar-actions open" | ||||
> | > | ||||
<a | <a | ||||
className="projects-topbar-button projects-topbar-button-close" | |||||
className="projects-topbar-button button-icon" | |||||
href="#" | href="#" | ||||
onClick={[Function]} | onClick={[Function]} | ||||
> | > | ||||
className="projects-topbar-actions-inner" | className="projects-topbar-actions-inner" | ||||
> | > | ||||
<PerspectiveSelect | <PerspectiveSelect | ||||
view="visualizations" | |||||
visualization="coverage" | |||||
className="projects-topbar-item js-projects-perspective-select" | |||||
view="leak" | |||||
visualization="risk" | |||||
/> | |||||
<ProjectsSortingSelect | |||||
className="projects-topbar-item js-projects-sorting-select" | |||||
view="leak" | |||||
/> | /> | ||||
</div> | </div> | ||||
</div> | </div> |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`should handle the descending sort direction 1`] = ` | |||||
<div> | |||||
<label> | |||||
projects.sort_by | |||||
: | |||||
</label> | |||||
<Select | |||||
addLabelText="Add \\"{label}\\"?" | |||||
arrowRenderer={[Function]} | |||||
autosize={true} | |||||
backspaceRemoves={true} | |||||
backspaceToRemoveMessage="Press backspace to remove {label}" | |||||
className="little-spacer-left input-large" | |||||
clearAllText="Clear all" | |||||
clearValueText="Clear value" | |||||
clearable={false} | |||||
delimiter="," | |||||
disabled={false} | |||||
escapeClearsValue={true} | |||||
filterOptions={[Function]} | |||||
ignoreAccents={true} | |||||
ignoreCase={true} | |||||
inputProps={Object {}} | |||||
isLoading={false} | |||||
joinValues={false} | |||||
labelKey="label" | |||||
matchPos="any" | |||||
matchProp="any" | |||||
menuBuffer={0} | |||||
menuRenderer={[Function]} | |||||
multi={false} | |||||
noResultsText="No results found" | |||||
onBlurResetsInput={true} | |||||
onChange={[Function]} | |||||
onCloseResetsInput={true} | |||||
openAfterFocus={false} | |||||
optionComponent={[Function]} | |||||
options={ | |||||
Array [ | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.name", | |||||
"short": undefined, | |||||
"value": "name", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.analysis_date", | |||||
"short": undefined, | |||||
"value": "analysis_date", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.reliability", | |||||
"short": undefined, | |||||
"value": "reliability", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.security", | |||||
"short": undefined, | |||||
"value": "security", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.maintainability", | |||||
"short": undefined, | |||||
"value": "maintainability", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.coverage", | |||||
"short": undefined, | |||||
"value": "coverage", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.duplications", | |||||
"short": undefined, | |||||
"value": "duplications", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.size", | |||||
"short": undefined, | |||||
"value": "size", | |||||
}, | |||||
] | |||||
} | |||||
pageSize={5} | |||||
placeholder="Select..." | |||||
required={false} | |||||
scrollMenuIntoView={true} | |||||
searchable={false} | |||||
simpleValue={false} | |||||
tabSelectsValue={true} | |||||
value="vulnerability" | |||||
valueComponent={[Function]} | |||||
valueKey="value" | |||||
/> | |||||
<Tooltip | |||||
overlay="projects.sort_descending" | |||||
placement="bottom" | |||||
> | |||||
<a | |||||
className="spacer-left button-icon" | |||||
href="#" | |||||
onClick={[Function]} | |||||
> | |||||
<SortDescIcon | |||||
className="little-spacer-top" | |||||
/> | |||||
</a> | |||||
</Tooltip> | |||||
</div> | |||||
`; | |||||
exports[`should render correctly for leak view 1`] = ` | |||||
<div> | |||||
<label> | |||||
projects.sort_by | |||||
: | |||||
</label> | |||||
<Select | |||||
addLabelText="Add \\"{label}\\"?" | |||||
arrowRenderer={[Function]} | |||||
autosize={true} | |||||
backspaceRemoves={true} | |||||
backspaceToRemoveMessage="Press backspace to remove {label}" | |||||
className="little-spacer-left input-large" | |||||
clearAllText="Clear all" | |||||
clearValueText="Clear value" | |||||
clearable={false} | |||||
delimiter="," | |||||
disabled={false} | |||||
escapeClearsValue={true} | |||||
filterOptions={[Function]} | |||||
ignoreAccents={true} | |||||
ignoreCase={true} | |||||
inputProps={Object {}} | |||||
isLoading={false} | |||||
joinValues={false} | |||||
labelKey="label" | |||||
matchPos="any" | |||||
matchProp="any" | |||||
menuBuffer={0} | |||||
menuRenderer={[Function]} | |||||
multi={false} | |||||
noResultsText="No results found" | |||||
onBlurResetsInput={true} | |||||
onChange={[Function]} | |||||
onCloseResetsInput={true} | |||||
openAfterFocus={false} | |||||
optionComponent={[Function]} | |||||
options={ | |||||
Array [ | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.name", | |||||
"short": undefined, | |||||
"value": "name", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.analysis_date", | |||||
"short": undefined, | |||||
"value": "analysis_date", | |||||
}, | |||||
Object { | |||||
"complement": "projects.sorting.on_new_code", | |||||
"label": "projects.sorting.new_reliability", | |||||
"short": "projects.sorting.new_reliability.short", | |||||
"value": "new_reliability", | |||||
}, | |||||
Object { | |||||
"complement": "projects.sorting.on_new_code", | |||||
"label": "projects.sorting.new_security", | |||||
"short": "projects.sorting.new_security.short", | |||||
"value": "new_security", | |||||
}, | |||||
Object { | |||||
"complement": "projects.sorting.on_new_code", | |||||
"label": "projects.sorting.new_maintainability", | |||||
"short": "projects.sorting.new_maintainability.short", | |||||
"value": "new_maintainability", | |||||
}, | |||||
Object { | |||||
"complement": "projects.sorting.on_new_code", | |||||
"label": "projects.sorting.new_coverage", | |||||
"short": "projects.sorting.new_coverage.short", | |||||
"value": "new_coverage", | |||||
}, | |||||
Object { | |||||
"complement": "projects.sorting.on_new_lines", | |||||
"label": "projects.sorting.new_duplications", | |||||
"short": "projects.sorting.new_duplications.short", | |||||
"value": "new_duplications", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.new_lines", | |||||
"short": undefined, | |||||
"value": "new_lines", | |||||
}, | |||||
] | |||||
} | |||||
pageSize={5} | |||||
placeholder="Select..." | |||||
required={false} | |||||
scrollMenuIntoView={true} | |||||
searchable={false} | |||||
simpleValue={false} | |||||
tabSelectsValue={true} | |||||
value="new_coverage" | |||||
valueComponent={[Function]} | |||||
valueKey="value" | |||||
/> | |||||
<Tooltip | |||||
overlay="projects.sort_ascending" | |||||
placement="bottom" | |||||
> | |||||
<a | |||||
className="spacer-left button-icon" | |||||
href="#" | |||||
onClick={[Function]} | |||||
> | |||||
<SortAscIcon | |||||
className="little-spacer-top" | |||||
/> | |||||
</a> | |||||
</Tooltip> | |||||
</div> | |||||
`; | |||||
exports[`should render correctly for overall view 1`] = ` | |||||
<div> | |||||
<label> | |||||
projects.sort_by | |||||
: | |||||
</label> | |||||
<Select | |||||
addLabelText="Add \\"{label}\\"?" | |||||
arrowRenderer={[Function]} | |||||
autosize={true} | |||||
backspaceRemoves={true} | |||||
backspaceToRemoveMessage="Press backspace to remove {label}" | |||||
className="little-spacer-left input-large" | |||||
clearAllText="Clear all" | |||||
clearValueText="Clear value" | |||||
clearable={false} | |||||
delimiter="," | |||||
disabled={false} | |||||
escapeClearsValue={true} | |||||
filterOptions={[Function]} | |||||
ignoreAccents={true} | |||||
ignoreCase={true} | |||||
inputProps={Object {}} | |||||
isLoading={false} | |||||
joinValues={false} | |||||
labelKey="label" | |||||
matchPos="any" | |||||
matchProp="any" | |||||
menuBuffer={0} | |||||
menuRenderer={[Function]} | |||||
multi={false} | |||||
noResultsText="No results found" | |||||
onBlurResetsInput={true} | |||||
onChange={[Function]} | |||||
onCloseResetsInput={true} | |||||
openAfterFocus={false} | |||||
optionComponent={[Function]} | |||||
options={ | |||||
Array [ | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.name", | |||||
"short": undefined, | |||||
"value": "name", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.analysis_date", | |||||
"short": undefined, | |||||
"value": "analysis_date", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.reliability", | |||||
"short": undefined, | |||||
"value": "reliability", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.security", | |||||
"short": undefined, | |||||
"value": "security", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.maintainability", | |||||
"short": undefined, | |||||
"value": "maintainability", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.coverage", | |||||
"short": undefined, | |||||
"value": "coverage", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.duplications", | |||||
"short": undefined, | |||||
"value": "duplications", | |||||
}, | |||||
Object { | |||||
"complement": undefined, | |||||
"label": "projects.sorting.size", | |||||
"short": undefined, | |||||
"value": "size", | |||||
}, | |||||
] | |||||
} | |||||
pageSize={5} | |||||
placeholder="Select..." | |||||
required={false} | |||||
scrollMenuIntoView={true} | |||||
searchable={false} | |||||
simpleValue={false} | |||||
tabSelectsValue={true} | |||||
value="name" | |||||
valueComponent={[Function]} | |||||
valueKey="value" | |||||
/> | |||||
<Tooltip | |||||
overlay="projects.sort_ascending" | |||||
placement="bottom" | |||||
> | |||||
<a | |||||
className="spacer-left button-icon" | |||||
href="#" | |||||
onClick={[Function]} | |||||
> | |||||
<SortAscIcon | |||||
className="little-spacer-top" | |||||
/> | |||||
</a> | |||||
</Tooltip> | |||||
</div> | |||||
`; |
import React from 'react'; | import React from 'react'; | ||||
import FilterContainer from './FilterContainer'; | import FilterContainer from './FilterContainer'; | ||||
import FilterHeader from './FilterHeader'; | import FilterHeader from './FilterHeader'; | ||||
import SortingFilter from './SortingFilter'; | |||||
import CoverageRating from '../../../components/ui/CoverageRating'; | import CoverageRating from '../../../components/ui/CoverageRating'; | ||||
import { getCoverageRatingLabel, getCoverageRatingAverageValue } from '../../../helpers/ratings'; | import { getCoverageRatingLabel, getCoverageRatingAverageValue } from '../../../helpers/ratings'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
organization={this.props.organization} | organization={this.props.organization} | ||||
getFacetValueForOption={this.getFacetValueForOption} | getFacetValueForOption={this.getFacetValueForOption} | ||||
highlightUnder={1} | highlightUnder={1} | ||||
header={ | |||||
<FilterHeader name={translate('metric_domain.Coverage')}> | |||||
<SortingFilter | |||||
property={this.props.property} | |||||
query={this.props.query} | |||||
isFavorite={this.props.isFavorite} | |||||
organization={this.props.organization} | |||||
sortDesc="right" | |||||
/> | |||||
</FilterHeader> | |||||
} | |||||
header={<FilterHeader name={translate('metric_domain.Coverage')} />} | |||||
/> | /> | ||||
); | ); | ||||
} | } |
import React from 'react'; | import React from 'react'; | ||||
import FilterContainer from './FilterContainer'; | import FilterContainer from './FilterContainer'; | ||||
import FilterHeader from './FilterHeader'; | import FilterHeader from './FilterHeader'; | ||||
import SortingFilter from './SortingFilter'; | |||||
import DuplicationsRating from '../../../components/ui/DuplicationsRating'; | import DuplicationsRating from '../../../components/ui/DuplicationsRating'; | ||||
import { | import { | ||||
getDuplicationsRatingLabel, | getDuplicationsRatingLabel, | ||||
organization={this.props.organization} | organization={this.props.organization} | ||||
getFacetValueForOption={this.getFacetValueForOption} | getFacetValueForOption={this.getFacetValueForOption} | ||||
highlightUnder={1} | highlightUnder={1} | ||||
header={ | |||||
<FilterHeader name={translate('metric_domain.Duplications')}> | |||||
<SortingFilter | |||||
property={this.props.property} | |||||
query={this.props.query} | |||||
isFavorite={this.props.isFavorite} | |||||
organization={this.props.organization} | |||||
/> | |||||
</FilterHeader> | |||||
} | |||||
header={<FilterHeader name={translate('metric_domain.Duplications')} />} | |||||
/> | /> | ||||
); | ); | ||||
} | } |
import React from 'react'; | import React from 'react'; | ||||
import FilterContainer from './FilterContainer'; | import FilterContainer from './FilterContainer'; | ||||
import FilterHeader from './FilterHeader'; | import FilterHeader from './FilterHeader'; | ||||
import SortingFilter from './SortingFilter'; | |||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import { getSizeRatingLabel } from '../../../helpers/ratings'; | import { getSizeRatingLabel } from '../../../helpers/ratings'; | ||||
export default class NewSizeFilter extends React.PureComponent { | |||||
export default class NewLinesFilter extends React.PureComponent { | |||||
static propTypes = { | static propTypes = { | ||||
className: React.PropTypes.string, | className: React.PropTypes.string, | ||||
query: React.PropTypes.object.isRequired, | query: React.PropTypes.object.isRequired, | ||||
organization={this.props.organization} | organization={this.props.organization} | ||||
getFacetValueForOption={this.getFacetValueForOption} | getFacetValueForOption={this.getFacetValueForOption} | ||||
highlightUnder={1} | highlightUnder={1} | ||||
header={ | |||||
<FilterHeader name={translate('metric_domain.new_size')}> | |||||
<SortingFilter | |||||
property={this.props.property} | |||||
query={this.props.query} | |||||
isFavorite={this.props.isFavorite} | |||||
organization={this.props.organization} | |||||
leftText={translate('biggest')} | |||||
rightText={translate('smallest')} | |||||
/> | |||||
</FilterHeader> | |||||
} | |||||
header={<FilterHeader name={translate('projects.facets.new_lines')} />} | |||||
/> | /> | ||||
); | ); | ||||
} | } |
import React from 'react'; | import React from 'react'; | ||||
import FilterContainer from './FilterContainer'; | import FilterContainer from './FilterContainer'; | ||||
import FilterHeader from './FilterHeader'; | import FilterHeader from './FilterHeader'; | ||||
import SortingFilter from './SortingFilter'; | |||||
import SizeRating from '../../../components/ui/SizeRating'; | import SizeRating from '../../../components/ui/SizeRating'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import { getSizeRatingLabel, getSizeRatingAverageValue } from '../../../helpers/ratings'; | import { getSizeRatingLabel, getSizeRatingAverageValue } from '../../../helpers/ratings'; | ||||
organization={this.props.organization} | organization={this.props.organization} | ||||
getFacetValueForOption={this.getFacetValueForOption} | getFacetValueForOption={this.getFacetValueForOption} | ||||
highlightUnder={1} | highlightUnder={1} | ||||
header={ | |||||
<FilterHeader name={translate('metric_domain.Size')}> | |||||
<SortingFilter | |||||
property={this.props.property} | |||||
query={this.props.query} | |||||
isFavorite={this.props.isFavorite} | |||||
organization={this.props.organization} | |||||
leftText={translate('biggest')} | |||||
rightText={translate('smallest')} | |||||
/> | |||||
</FilterHeader> | |||||
} | |||||
header={<FilterHeader name={translate('metric_domain.Size')} />} | |||||
/> | /> | ||||
); | ); | ||||
} | } |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 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 React from 'react'; | |||||
import classNames from 'classnames'; | |||||
import { Link } from 'react-router'; | |||||
import { getFilterUrl } from './utils'; | |||||
import { translate } from '../../../helpers/l10n'; | |||||
export default class SortingFilter extends React.PureComponent { | |||||
static propTypes = { | |||||
property: React.PropTypes.string.isRequired, | |||||
query: React.PropTypes.object.isRequired, | |||||
isFavorite: React.PropTypes.bool, | |||||
organization: React.PropTypes.object, | |||||
sortDesc: React.PropTypes.oneOf(['left', 'right']), | |||||
leftText: React.PropTypes.string, | |||||
rightText: React.PropTypes.string | |||||
}; | |||||
static defaultProps = { | |||||
sortDesc: 'left' | |||||
}; | |||||
isSortActive(side) { | |||||
const { sort } = this.props.query; | |||||
if (sort && sort[0] === '-') { | |||||
return sort.substr(1) === this.props.property && side === this.props.sortDesc; | |||||
} else { | |||||
return sort === this.props.property && side !== this.props.sortDesc; | |||||
} | |||||
} | |||||
getLinkClass(side) { | |||||
return classNames('button button-small button-grey', { | |||||
'button-active': this.isSortActive(side) | |||||
}); | |||||
} | |||||
getLinkPath(side) { | |||||
if (this.isSortActive(side)) { | |||||
return getFilterUrl(this.props, { sort: null }); | |||||
} | |||||
return getFilterUrl(this.props, { | |||||
sort: (this.props.sortDesc === side ? '-' : '') + this.props.property | |||||
}); | |||||
} | |||||
blurLink(event) { | |||||
event.target.blur(); | |||||
} | |||||
render() { | |||||
const { leftText, rightText } = this.props; | |||||
return ( | |||||
<div className="projects-facet-sort"> | |||||
<span>{translate('projects.sort_list')}</span> | |||||
<div className="spacer-left button-group"> | |||||
<Link | |||||
onClick={this.blurLink} | |||||
className={this.getLinkClass('left')} | |||||
to={this.getLinkPath('left')}> | |||||
{leftText || translate('worst')} | |||||
</Link> | |||||
<Link | |||||
onClick={this.blurLink} | |||||
className={this.getLinkClass('right')} | |||||
to={this.getLinkPath('right')}> | |||||
{rightText || translate('best')} | |||||
</Link> | |||||
</div> | |||||
</div> | |||||
); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 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 React from 'react'; | |||||
import { shallow } from 'enzyme'; | |||||
import SortingFilter from '../SortingFilter'; | |||||
it('should render with default parameters and empty query', () => { | |||||
const wrapper = shallow(<SortingFilter property="foo" query={{}} />); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
const sortingFilter = wrapper.instance(); | |||||
expect(sortingFilter.isSortActive('left')).toBeFalsy(); | |||||
expect(sortingFilter.isSortActive('right')).toBeFalsy(); | |||||
}); | |||||
it('should render with custom parameters', () => { | |||||
const wrapper = shallow( | |||||
<SortingFilter property="foo" query={{}} sortDesc="right" leftText="worst" rightText="best" /> | |||||
); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
}); | |||||
it('should render correctly with matching query', () => { | |||||
const wrapper = shallow( | |||||
<SortingFilter property="foo" query={{ sort: '-foo', languages: 'php,cpp' }} sortDesc="right" /> | |||||
); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
const sortingFilter = wrapper.instance(); | |||||
expect(sortingFilter.isSortActive('left')).toBeFalsy(); | |||||
expect(sortingFilter.isSortActive('right')).toBeTruthy(); | |||||
}); | |||||
it('should render correctly with no matching query', () => { | |||||
const wrapper = shallow(<SortingFilter property="foo" query={{ sort: 'bar' }} />); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
const sortingFilter = wrapper.instance(); | |||||
expect(sortingFilter.isSortActive('left')).toBeFalsy(); | |||||
expect(sortingFilter.isSortActive('right')).toBeFalsy(); | |||||
}); |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`should render correctly with matching query 1`] = ` | |||||
<div | |||||
className="projects-facet-sort" | |||||
> | |||||
<span> | |||||
projects.sort_list | |||||
</span> | |||||
<div | |||||
className="spacer-left button-group" | |||||
> | |||||
<Link | |||||
className="button button-small button-grey" | |||||
onClick={[Function]} | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/projects", | |||||
"query": Object { | |||||
"languages": "php,cpp", | |||||
"sort": "foo", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
worst | |||||
</Link> | |||||
<Link | |||||
className="button button-small button-grey button-active" | |||||
onClick={[Function]} | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/projects", | |||||
"query": Object { | |||||
"languages": "php,cpp", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
best | |||||
</Link> | |||||
</div> | |||||
</div> | |||||
`; | |||||
exports[`should render correctly with no matching query 1`] = ` | |||||
<div | |||||
className="projects-facet-sort" | |||||
> | |||||
<span> | |||||
projects.sort_list | |||||
</span> | |||||
<div | |||||
className="spacer-left button-group" | |||||
> | |||||
<Link | |||||
className="button button-small button-grey" | |||||
onClick={[Function]} | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/projects", | |||||
"query": Object { | |||||
"sort": "-foo", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
worst | |||||
</Link> | |||||
<Link | |||||
className="button button-small button-grey" | |||||
onClick={[Function]} | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/projects", | |||||
"query": Object { | |||||
"sort": "foo", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
best | |||||
</Link> | |||||
</div> | |||||
</div> | |||||
`; | |||||
exports[`should render with custom parameters 1`] = ` | |||||
<div | |||||
className="projects-facet-sort" | |||||
> | |||||
<span> | |||||
projects.sort_list | |||||
</span> | |||||
<div | |||||
className="spacer-left button-group" | |||||
> | |||||
<Link | |||||
className="button button-small button-grey" | |||||
onClick={[Function]} | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/projects", | |||||
"query": Object { | |||||
"sort": "foo", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
worst | |||||
</Link> | |||||
<Link | |||||
className="button button-small button-grey" | |||||
onClick={[Function]} | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/projects", | |||||
"query": Object { | |||||
"sort": "-foo", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
best | |||||
</Link> | |||||
</div> | |||||
</div> | |||||
`; | |||||
exports[`should render with default parameters and empty query 1`] = ` | |||||
<div | |||||
className="projects-facet-sort" | |||||
> | |||||
<span> | |||||
projects.sort_list | |||||
</span> | |||||
<div | |||||
className="spacer-left button-group" | |||||
> | |||||
<Link | |||||
className="button button-small button-grey" | |||||
onClick={[Function]} | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/projects", | |||||
"query": Object { | |||||
"sort": "-foo", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
worst | |||||
</Link> | |||||
<Link | |||||
className="button button-small button-grey" | |||||
onClick={[Function]} | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/projects", | |||||
"query": Object { | |||||
"sort": "foo", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
best | |||||
</Link> | |||||
</div> | |||||
</div> | |||||
`; |
border-bottom: 1px solid #e6e6e6; | border-bottom: 1px solid #e6e6e6; | ||||
} | } | ||||
.projects-topbar-item { | |||||
padding: 0 24px; | |||||
} | |||||
.projects-topbar-button { | .projects-topbar-button { | ||||
box-sizing: border-box; | box-sizing: border-box; | ||||
float: right; | float: right; | ||||
padding: 11px; | padding: 11px; | ||||
padding-top: 15px; | |||||
border: none; | border: none; | ||||
border-left: 1px solid #e6e6e6; | |||||
border-bottom: 1px solid #e6e6e6; | |||||
width: 46px; | width: 46px; | ||||
height: 46px; | height: 46px; | ||||
color: #777; | |||||
text-align: center; | text-align: center; | ||||
text-decoration: none; | text-decoration: none; | ||||
cursor: pointer; | cursor: pointer; | ||||
} | } | ||||
.projects-topbar-button-close { | |||||
padding-top: 15px; | |||||
border-left: 1px solid #e6e6e6; | |||||
border-bottom: 1px solid #e6e6e6; | |||||
} | |||||
.projects-topbar-button:hover, .project-topbar-button:focus, .project-topbar-button:active { | |||||
color: #444; | |||||
outline: none; | |||||
} | |||||
.projects-sidebar { | .projects-sidebar { | ||||
width: 260px; | width: 260px; | ||||
} | } |
export const saveFavorite = () => save(LOCALSTORAGE_FAVORITE); | export const saveFavorite = () => save(LOCALSTORAGE_FAVORITE); | ||||
export const SORTING_METRICS = [ | |||||
{ value: 'name' }, | |||||
{ value: 'reliability' }, | |||||
{ value: 'security' }, | |||||
{ value: 'maintainability' }, | |||||
{ value: 'coverage' }, | |||||
{ value: 'duplications' }, | |||||
{ value: 'size' } | |||||
]; | |||||
export const SORTING_LEAK_METRICS = [ | |||||
{ value: 'name' }, | |||||
{ value: 'new_reliability', complement: 'on_new_code' }, | |||||
{ value: 'new_security', complement: 'on_new_code' }, | |||||
{ value: 'new_maintainability', complement: 'on_new_code' }, | |||||
{ value: 'new_coverage', complement: 'on_new_code' }, | |||||
{ value: 'new_duplications', complement: 'on_new_lines' }, | |||||
{ value: 'new_lines' } | |||||
]; | |||||
export const SORTING_SWITCH = { | |||||
name: 'name', | |||||
reliability: 'new_reliability', | |||||
security: 'new_security', | |||||
maintainability: 'new_maintainability', | |||||
coverage: 'new_coverage', | |||||
duplications: 'new_duplications', | |||||
size: 'new_lines', | |||||
new_reliability: 'reliability', | |||||
new_security: 'security', | |||||
new_maintainability: 'maintainability', | |||||
new_coverage: 'coverage', | |||||
new_duplications: 'duplications', | |||||
new_lines: 'size' | |||||
}; | |||||
export const VIEWS = ['overall', 'leak']; | export const VIEWS = ['overall', 'leak']; | ||||
export const VISUALIZATIONS = [ | export const VISUALIZATIONS = [ | ||||
export const localizeSorting = (sort?: string) => { | export const localizeSorting = (sort?: string) => { | ||||
return translate('projects.sort', sort || 'name'); | return translate('projects.sort', sort || 'name'); | ||||
}; | }; | ||||
export const parseSorting = (sort: string): { sortValue: string, sortDesc: boolean } => { | |||||
const desc = sort[0] === '-'; | |||||
return { sortValue: desc ? sort.substr(1) : sort, sortDesc: desc }; | |||||
}; |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 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. | |||||
*/ | |||||
// @flow | |||||
import React from 'react'; | |||||
type Props = { className?: string, size?: number }; | |||||
export default function SortAscIcon({ className, size = 16 }: Props) { | |||||
/* eslint-disable max-len */ | |||||
return ( | |||||
<svg | |||||
className={className} | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
viewBox="0 0 16 16" | |||||
width={size} | |||||
height={size}> | |||||
<path | |||||
style={{ fill: 'currentColor' }} | |||||
d="M6.571 12.857q0 0.107-0.089 0.214l-2.848 2.848q-0.089 0.080-0.205 0.080-0.107 0-0.205-0.080l-2.857-2.857q-0.134-0.143-0.063-0.313 0.071-0.179 0.268-0.179h1.714v-12.286q0-0.125 0.080-0.205t0.205-0.080h1.714q0.125 0 0.205 0.080t0.080 0.205v12.286h1.714q0.125 0 0.205 0.080t0.080 0.205zM16 14v1.714q0 0.125-0.080 0.205t-0.205 0.080h-7.429q-0.125 0-0.205-0.080t-0.080-0.205v-1.714q0-0.125 0.080-0.205t0.205-0.080h7.429q0.125 0 0.205 0.080t0.080 0.205zM14.286 9.429v1.714q0 0.125-0.080 0.205t-0.205 0.080h-5.714q-0.125 0-0.205-0.080t-0.080-0.205v-1.714q0-0.125 0.080-0.205t0.205-0.080h5.714q0.125 0 0.205 0.080t0.080 0.205zM12.571 4.857v1.714q0 0.125-0.080 0.205t-0.205 0.080h-4q-0.125 0-0.205-0.080t-0.080-0.205v-1.714q0-0.125 0.080-0.205t0.205-0.080h4q0.125 0 0.205 0.080t0.080 0.205zM10.857 0.286v1.714q0 0.125-0.080 0.205t-0.205 0.080h-2.286q-0.125 0-0.205-0.080t-0.080-0.205v-1.714q0-0.125 0.080-0.205t0.205-0.080h2.286q0.125 0 0.205 0.080t0.080 0.205z" | |||||
/> | |||||
</svg> | |||||
); | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 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. | |||||
*/ | |||||
// @flow | |||||
import React from 'react'; | |||||
type Props = { className?: string, size?: number }; | |||||
export default function SortDescIcon({ className, size = 16 }: Props) { | |||||
/* eslint-disable max-len */ | |||||
return ( | |||||
<svg | |||||
className={className} | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
viewBox="0 0 16 16" | |||||
width={size} | |||||
height={size}> | |||||
<path | |||||
style={{ fill: 'currentColor' }} | |||||
d="M10.857 14v1.714q0 0.125-0.080 0.205t-0.205 0.080h-2.286q-0.125 0-0.205-0.080t-0.080-0.205v-1.714q0-0.125 0.080-0.205t0.205-0.080h2.286q0.125 0 0.205 0.080t0.080 0.205zM6.571 12.857q0 0.107-0.089 0.214l-2.848 2.848q-0.089 0.080-0.205 0.080-0.107 0-0.205-0.080l-2.857-2.857q-0.134-0.143-0.063-0.313 0.071-0.179 0.268-0.179h1.714v-12.286q0-0.125 0.080-0.205t0.205-0.080h1.714q0.125 0 0.205 0.080t0.080 0.205v12.286h1.714q0.125 0 0.205 0.080t0.080 0.205zM12.571 9.429v1.714q0 0.125-0.080 0.205t-0.205 0.080h-4q-0.125 0-0.205-0.080t-0.080-0.205v-1.714q0-0.125 0.080-0.205t0.205-0.080h4q0.125 0 0.205 0.080t0.080 0.205zM14.286 4.857v1.714q0 0.125-0.080 0.205t-0.205 0.080h-5.714q-0.125 0-0.205-0.080t-0.080-0.205v-1.714q0-0.125 0.080-0.205t0.205-0.080h5.714q0.125 0 0.205 0.080t0.080 0.205zM16 0.286v1.714q0 0.125-0.080 0.205t-0.205 0.080h-7.429q-0.125 0-0.205-0.080t-0.080-0.205v-1.714q0-0.125 0.080-0.205t0.205-0.080h7.429q0.125 0 0.205 0.080t0.080 0.205z" | |||||
/> | |||||
</svg> | |||||
); | |||||
} |
} | } | ||||
} | } | ||||
.button-icon { | |||||
border: none; | |||||
color: @secondFontColor; | |||||
} | |||||
.button-small { | .button-small { | ||||
height: 20px; | height: 20px; | ||||
line-height: 18px; | line-height: 18px; |
projects.search=Search by project name or key | projects.search=Search by project name or key | ||||
projects.sort_list=Sort list by | projects.sort_list=Sort list by | ||||
projects.perspective=Perspective | projects.perspective=Perspective | ||||
projects.sort_by=Sort by | |||||
projects.sort_ascending=Result sorted in ascending order | |||||
projects.sort_descending=Result sorted in descending order | |||||
projects.sorting.name=Name (default) | |||||
projects.sorting.reliability=Reliability | |||||
projects.sorting.security=Security | |||||
projects.sorting.maintainability=Maintainability | |||||
projects.sorting.coverage=Coverage | |||||
projects.sorting.duplications=Duplications | |||||
projects.sorting.size=Size | |||||
projects.sorting.new_reliability=Reliability on New Code | |||||
projects.sorting.new_security=Security on New Code | |||||
projects.sorting.new_maintainability=Maintainability on New Code | |||||
projects.sorting.new_coverage=Coverage on New Code | |||||
projects.sorting.new_duplications=Duplications on New Lines | |||||
projects.sorting.new_reliability.short=Reliability | |||||
projects.sorting.new_security.short=Security | |||||
projects.sorting.new_maintainability.short=Maintainability | |||||
projects.sorting.new_coverage.short=Coverage | |||||
projects.sorting.new_duplications.short=Duplications | |||||
projects.sorting.new_lines=New Lines | |||||
projects.sorting.on_new_code=on New Code | |||||
projects.sorting.on_new_lines=on New Lines | |||||
projects.view_settings=Settings | projects.view_settings=Settings | ||||
projects.view.overall=Overall Status | projects.view.overall=Overall Status | ||||
projects.view.leak=Leak | projects.view.leak=Leak | ||||
projects.limited_set_of_projects=Displayed project set limited to the top {0} projects based on current sort: {1}. | projects.limited_set_of_projects=Displayed project set limited to the top {0} projects based on current sort: {1}. | ||||
projects.facets.quality_gate=Quality Gate | projects.facets.quality_gate=Quality Gate | ||||
projects.facets.languages=Languages | projects.facets.languages=Languages | ||||
projects.facets.new_lines=New Lines | |||||
projects.facets.tags=Tags | projects.facets.tags=Tags | ||||
projects.sort.name=by name | projects.sort.name=by name | ||||
projects.sort.reliability=by reliability (best first) | projects.sort.reliability=by reliability (best first) | ||||
#------------------------------------------------------------------------------ | #------------------------------------------------------------------------------ | ||||
metric_domain.Size=Size | metric_domain.Size=Size | ||||
metric_domain.new_size=New Lines | |||||
metric_domain.Tests=Tests | metric_domain.Tests=Tests | ||||
metric_domain.Integration Tests=Integration Tests | metric_domain.Integration Tests=Integration Tests | ||||
metric_domain.Complexity=Complexity | metric_domain.Complexity=Complexity |