@@ -38,7 +38,8 @@ type Props = { | |||
optionBarOpen: boolean, | |||
optionBarToggle: (open: boolean) => void, | |||
organization?: { key: string }, | |||
router: { push: ({ pathname: string, query?: {} }) => void } | |||
router: { push: ({ pathname: string, query?: {} }) => void }, | |||
user?: { isLoggedIn: boolean } | |||
}; | |||
type State = { | |||
@@ -132,6 +133,7 @@ export default class AllProjects extends React.PureComponent { | |||
onToggleOptionBar={this.props.optionBarToggle} | |||
open={optionBarOpen} | |||
selectedSort={selectedSort} | |||
user={this.props.user} | |||
view={view} | |||
visualization={visualization} | |||
/> |
@@ -33,6 +33,7 @@ type Props = { | |||
projects: Array<*>, | |||
projectsAppState: { loading: boolean, total?: number }, | |||
selectedSort: string, | |||
user?: { isLoggedIn: boolean }, | |||
view: string, | |||
visualization?: string | |||
}; | |||
@@ -47,18 +48,19 @@ export default class ProjectsOptionBar extends React.PureComponent { | |||
}; | |||
renderSortingSelect() { | |||
const { projectsAppState, projects, view } = this.props; | |||
const { projectsAppState, projects, user, view } = this.props; | |||
const limitReached = | |||
projects != null && | |||
projectsAppState.total != null && | |||
projects.length < projectsAppState.total; | |||
const defaultOption = user && user.isLoggedIn ? 'name' : 'analysis_date'; | |||
if (view === 'visualizations' && !limitReached) { | |||
return ( | |||
<Tooltip overlay={translate('projects.sort.disabled')}> | |||
<div> | |||
<ProjectsSortingSelect | |||
className="projects-topbar-item js-projects-sorting-select disabled" | |||
defaultOption={defaultOption} | |||
onChange={this.props.onSortChange} | |||
selectedSort={this.props.selectedSort} | |||
view={this.props.view} | |||
@@ -70,6 +72,7 @@ export default class ProjectsOptionBar extends React.PureComponent { | |||
return ( | |||
<ProjectsSortingSelect | |||
className="projects-topbar-item js-projects-sorting-select" | |||
defaultOption={defaultOption} | |||
onChange={this.props.onSortChange} | |||
selectedSort={this.props.selectedSort} | |||
view={this.props.view} |
@@ -19,6 +19,7 @@ | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { sortBy } from 'lodash'; | |||
import Select from 'react-select'; | |||
import ProjectsSortingSelectOption from './ProjectsSortingSelectOption'; | |||
import SortAscIcon from '../../../components/icons-components/SortAscIcon'; | |||
@@ -27,13 +28,14 @@ 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 }; | |||
export type Option = { label: string, value: string, class?: string, short?: string }; | |||
type Props = { | |||
className?: string, | |||
onChange: (sort: string, desc: boolean) => void, | |||
selectedSort: string, | |||
view: string | |||
view: string, | |||
defaultOption: string | |||
}; | |||
type State = { | |||
@@ -58,11 +60,16 @@ export default class ProjectsSortingSelect extends React.PureComponent { | |||
getOptions = () => { | |||
const sortMetrics = this.props.view === 'leak' ? SORTING_LEAK_METRICS : SORTING_METRICS; | |||
return sortMetrics.map((opt: { value: string, complement?: string }) => ({ | |||
return sortBy( | |||
sortMetrics, | |||
opt => (opt.value === this.props.defaultOption ? 0 : 1) | |||
).map((opt: { value: string, class?: 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') | |||
label: translate('projects.sorting', opt.value) + | |||
(opt.value === this.props.defaultOption | |||
? ` (${translate('projects.sorting.default')})` | |||
: ''), | |||
class: opt.class | |||
})); | |||
}; | |||
@@ -84,8 +91,8 @@ export default class ProjectsSortingSelect extends React.PureComponent { | |||
className="little-spacer-left input-large" | |||
clearable={false} | |||
onChange={this.handleSortChange} | |||
options={this.getOptions()} | |||
optionComponent={ProjectsSortingSelectOption} | |||
options={this.getOptions()} | |||
searchable={false} | |||
value={this.state.sortValue} | |||
/> |
@@ -19,6 +19,7 @@ | |||
*/ | |||
//@flow | |||
import React from 'react'; | |||
import classNames from 'classnames'; | |||
import type { Option } from './ProjectsSortingSelect'; | |||
type Props = { | |||
@@ -54,16 +55,12 @@ export default class ProjectsSortingSelectOption extends React.PureComponent { | |||
const { option } = this.props; | |||
return ( | |||
<div | |||
className={this.props.className} | |||
className={classNames(this.props.className, option.class)} | |||
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> | |||
); | |||
} |
@@ -35,6 +35,7 @@ it('should render option bar open', () => { | |||
visualization="risk" | |||
projects={[1, 2, 3]} | |||
projectsAppState={{ total: 3 }} | |||
user={{ isLoggedIn: true }} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
@@ -52,3 +53,26 @@ it('should call close method correctly', () => { | |||
click(wrapper.find('.projects-topbar-button')); | |||
expect(toggle.mock.calls).toMatchSnapshot(); | |||
}); | |||
it('should render switch the default sorting option for anonymous users', () => { | |||
expect( | |||
shallow( | |||
<ProjectsOptionBar | |||
open={true} | |||
view="overall" | |||
visualization="risk" | |||
user={{ isLoggedIn: true }} | |||
/> | |||
).find('ProjectsSortingSelect') | |||
).toMatchSnapshot(); | |||
expect( | |||
shallow( | |||
<ProjectsOptionBar | |||
open={true} | |||
view="leak" | |||
visualization="risk" | |||
user={{ isLoggedIn: false }} | |||
/> | |||
).find('ProjectsSortingSelect') | |||
).toMatchSnapshot(); | |||
}); |
@@ -22,17 +22,27 @@ import { shallow } from 'enzyme'; | |||
import ProjectsSortingSelect from '../ProjectsSortingSelect'; | |||
it('should render correctly for overall view', () => { | |||
expect(shallow(<ProjectsSortingSelect selectedSort="name" view="overall" />)).toMatchSnapshot(); | |||
expect( | |||
shallow(<ProjectsSortingSelect selectedSort="name" view="overall" defaultOption="name" />) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should render correctly for leak view', () => { | |||
expect( | |||
shallow(<ProjectsSortingSelect selectedSort="new_coverage" view="leak" />) | |||
shallow( | |||
<ProjectsSortingSelect | |||
selectedSort="new_coverage" | |||
view="leak" | |||
defaultOption="analysis_date" | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should handle the descending sort direction', () => { | |||
expect( | |||
shallow(<ProjectsSortingSelect selectedSort="-vulnerability" view="overall" />) | |||
shallow( | |||
<ProjectsSortingSelect selectedSort="-vulnerability" view="overall" defaultOption="name" /> | |||
) | |||
).toMatchSnapshot(); | |||
}); |
@@ -39,6 +39,7 @@ exports[`should render disabled sorting options for visualizations 1`] = ` | |||
<div> | |||
<ProjectsSortingSelect | |||
className="projects-topbar-item js-projects-sorting-select disabled" | |||
defaultOption="analysis_date" | |||
view="visualizations" | |||
/> | |||
</div> | |||
@@ -74,6 +75,7 @@ exports[`should render option bar closed 1`] = ` | |||
/> | |||
<ProjectsSortingSelect | |||
className="projects-topbar-item js-projects-sorting-select" | |||
defaultOption="analysis_date" | |||
view="overall" | |||
/> | |||
</div> | |||
@@ -108,6 +110,7 @@ exports[`should render option bar open 1`] = ` | |||
/> | |||
<ProjectsSortingSelect | |||
className="projects-topbar-item js-projects-sorting-select" | |||
defaultOption="name" | |||
view="leak" | |||
/> | |||
</div> | |||
@@ -115,3 +118,19 @@ exports[`should render option bar open 1`] = ` | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render switch the default sorting option for anonymous users 1`] = ` | |||
<ProjectsSortingSelect | |||
className="projects-topbar-item js-projects-sorting-select" | |||
defaultOption="name" | |||
view="overall" | |||
/> | |||
`; | |||
exports[`should render switch the default sorting option for anonymous users 2`] = ` | |||
<ProjectsSortingSelect | |||
className="projects-topbar-item js-projects-sorting-select" | |||
defaultOption="analysis_date" | |||
view="leak" | |||
/> | |||
`; |
@@ -40,51 +40,43 @@ exports[`should handle the descending sort direction 1`] = ` | |||
options={ | |||
Array [ | |||
Object { | |||
"complement": undefined, | |||
"label": "projects.sorting.name", | |||
"short": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.name (projects.sorting.default)", | |||
"value": "name", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.analysis_date", | |||
"short": undefined, | |||
"value": "analysis_date", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.reliability", | |||
"short": undefined, | |||
"value": "reliability", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.security", | |||
"short": undefined, | |||
"value": "security", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.maintainability", | |||
"short": undefined, | |||
"value": "maintainability", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.coverage", | |||
"short": undefined, | |||
"value": "coverage", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.duplications", | |||
"short": undefined, | |||
"value": "duplications", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.size", | |||
"short": undefined, | |||
"value": "size", | |||
}, | |||
] | |||
@@ -157,51 +149,43 @@ exports[`should render correctly for leak view 1`] = ` | |||
options={ | |||
Array [ | |||
Object { | |||
"complement": undefined, | |||
"label": "projects.sorting.name", | |||
"short": undefined, | |||
"value": "name", | |||
"class": undefined, | |||
"label": "projects.sorting.analysis_date (projects.sorting.default)", | |||
"value": "analysis_date", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"label": "projects.sorting.analysis_date", | |||
"short": undefined, | |||
"value": "analysis_date", | |||
"class": undefined, | |||
"label": "projects.sorting.name", | |||
"value": "name", | |||
}, | |||
Object { | |||
"complement": "projects.sorting.on_new_code", | |||
"class": "projects-leak-sorting-option", | |||
"label": "projects.sorting.new_reliability", | |||
"short": "projects.sorting.new_reliability.short", | |||
"value": "new_reliability", | |||
}, | |||
Object { | |||
"complement": "projects.sorting.on_new_code", | |||
"class": "projects-leak-sorting-option", | |||
"label": "projects.sorting.new_security", | |||
"short": "projects.sorting.new_security.short", | |||
"value": "new_security", | |||
}, | |||
Object { | |||
"complement": "projects.sorting.on_new_code", | |||
"class": "projects-leak-sorting-option", | |||
"label": "projects.sorting.new_maintainability", | |||
"short": "projects.sorting.new_maintainability.short", | |||
"value": "new_maintainability", | |||
}, | |||
Object { | |||
"complement": "projects.sorting.on_new_code", | |||
"class": "projects-leak-sorting-option", | |||
"label": "projects.sorting.new_coverage", | |||
"short": "projects.sorting.new_coverage.short", | |||
"value": "new_coverage", | |||
}, | |||
Object { | |||
"complement": "projects.sorting.on_new_lines", | |||
"class": "projects-leak-sorting-option", | |||
"label": "projects.sorting.new_duplications", | |||
"short": "projects.sorting.new_duplications.short", | |||
"value": "new_duplications", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"class": "projects-leak-sorting-option", | |||
"label": "projects.sorting.new_lines", | |||
"short": undefined, | |||
"value": "new_lines", | |||
}, | |||
] | |||
@@ -274,51 +258,43 @@ exports[`should render correctly for overall view 1`] = ` | |||
options={ | |||
Array [ | |||
Object { | |||
"complement": undefined, | |||
"label": "projects.sorting.name", | |||
"short": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.name (projects.sorting.default)", | |||
"value": "name", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.analysis_date", | |||
"short": undefined, | |||
"value": "analysis_date", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.reliability", | |||
"short": undefined, | |||
"value": "reliability", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.security", | |||
"short": undefined, | |||
"value": "security", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.maintainability", | |||
"short": undefined, | |||
"value": "maintainability", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.coverage", | |||
"short": undefined, | |||
"value": "coverage", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.duplications", | |||
"short": undefined, | |||
"value": "duplications", | |||
}, | |||
Object { | |||
"complement": undefined, | |||
"class": undefined, | |||
"label": "projects.sorting.size", | |||
"short": undefined, | |||
"value": "size", | |||
}, | |||
] |
@@ -112,6 +112,14 @@ | |||
border: 1px solid #eae3c7; | |||
} | |||
.projects-leak-sorting-option { | |||
background-color: #fbf3d5; | |||
margin-bottom: 2px; | |||
} | |||
.projects-leak-sorting-option.is-focused { | |||
background-color: #eae3c7; | |||
} | |||
.project-card-measures .project-card-measure { | |||
width: 120px; |
@@ -61,12 +61,12 @@ export const SORTING_METRICS = [ | |||
export const SORTING_LEAK_METRICS = [ | |||
{ value: 'name' }, | |||
{ value: 'analysis_date' }, | |||
{ 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' } | |||
{ value: 'new_reliability', class: 'projects-leak-sorting-option' }, | |||
{ value: 'new_security', class: 'projects-leak-sorting-option' }, | |||
{ value: 'new_maintainability', class: 'projects-leak-sorting-option' }, | |||
{ value: 'new_coverage', class: 'projects-leak-sorting-option' }, | |||
{ value: 'new_duplications', class: 'projects-leak-sorting-option' }, | |||
{ value: 'new_lines', class: 'projects-leak-sorting-option' } | |||
]; | |||
export const SORTING_SWITCH = { |
@@ -869,7 +869,8 @@ 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.default=default | |||
projects.sorting.name=Name | |||
projects.sorting.analysis_date=Last analysis date | |||
projects.sorting.reliability=Reliability | |||
projects.sorting.security=Security | |||
@@ -877,16 +878,11 @@ 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_reliability=Reliability | |||
projects.sorting.new_security=Security | |||
projects.sorting.new_maintainability=Maintainability | |||
projects.sorting.new_coverage=Coverage | |||
projects.sorting.new_duplications=Duplications | |||
projects.sorting.new_lines=New Lines | |||
projects.sorting.on_new_code=on New Code | |||
projects.sorting.on_new_lines=on New Lines |