aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-05-29 12:18:58 +0200
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-06-09 08:26:48 +0200
commit8a173aa3175c846291b8b56f2cfd84ab0e782627 (patch)
treec3fafd7d782521c25ccd93dcd5816103f3f5145c /server/sonar-web
parenteb029461e77fbb1f7a9dde462512d88fc65d46b4 (diff)
downloadsonarqube-8a173aa3175c846291b8b56f2cfd84ab0e782627.tar.gz
sonarqube-8a173aa3175c846291b8b56f2cfd84ab0e782627.zip
SONAR-9254 Move projects facets sorting to the topbar of the projects page
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/AllProjects.js48
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/PageHeader.js6
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.js3
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectsOptionBar.js (renamed from server/sonar-web/src/main/js/apps/projects/components/ProjectOptionBar.js)17
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelect.js105
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelectOption.js70
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectOptionBar-test.js16
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.js38
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.js.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectOptionBar-test.js.snap45
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelect-test.js.snap352
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.js13
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.js12
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.js (renamed from server/sonar-web/src/main/js/apps/projects/filters/NewSizeFilter.js)16
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js14
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/SortingFilter.js92
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/__tests__/SortingFilter-test.js55
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SortingFilter-test.js.snap186
-rw-r--r--server/sonar-web/src/main/js/apps/projects/styles.css19
-rw-r--r--server/sonar-web/src/main/js/apps/projects/utils.js41
-rw-r--r--server/sonar-web/src/main/js/components/icons-components/SortAscIcon.js40
-rw-r--r--server/sonar-web/src/main/js/components/icons-components/SortDescIcon.js40
-rw-r--r--server/sonar-web/src/main/less/init/forms.less5
24 files changed, 814 insertions, 425 deletions
diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
index 1c64c117f1f..8b172222b75 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
@@ -21,13 +21,14 @@
import React from 'react';
import Helmet from 'react-helmet';
import PageHeaderContainer from './PageHeaderContainer';
-import ProjectOptionBar from './ProjectOptionBar';
+import ProjectsOptionBar from './ProjectsOptionBar';
import ProjectsListContainer from './ProjectsListContainer';
import ProjectsListFooterContainer from './ProjectsListFooterContainer';
import PageSidebar from './PageSidebar';
import VisualizationsContainer from '../visualizations/VisualizationsContainer';
import { parseUrlQuery } from '../store/utils';
import { translate } from '../../../helpers/l10n';
+import { SORTING_SWITCH, parseSorting } from '../utils';
import '../styles.css';
type Props = {
@@ -76,14 +77,30 @@ export default class AllProjects extends React.PureComponent {
handleOptionBarToggle = (open: boolean) => this.setState({ optionBarOpen: open });
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() {
@@ -92,12 +109,23 @@ export default class AllProjects extends React.PureComponent {
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() {
const { query, optionBarOpen } = this.state;
const isFiltered = Object.keys(query).some(key => query[key] != null);
const view = query.view || 'overall';
const visualization = query.visualization || 'risk';
+ const selectedSort = query.sort || 'name';
const top = (this.props.organization ? 95 : 30) + (optionBarOpen ? 45 : 0);
@@ -105,10 +133,12 @@ export default class AllProjects extends React.PureComponent {
<div>
<Helmet title={translate('projects.page')} />
- <ProjectOptionBar
+ <ProjectsOptionBar
onPerspectiveChange={this.handlePerspectiveChange}
+ onSortChange={this.handleSortChange}
onToggleOptionBar={this.handleOptionBarToggle}
open={optionBarOpen}
+ selectedSort={selectedSort}
view={view}
visualization={visualization}
/>
diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js b/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js
index 3b65bfd5d8d..f3866ee3935 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js
@@ -30,9 +30,9 @@ type Props = {
export default function PageHeader(props: Props) {
return (
<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')}
</a>
</div>
diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js b/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js
index 85d9184784d..3e7ca9e4591 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js
@@ -30,7 +30,7 @@ import NewDuplicationsFilter from '../filters/NewDuplicationsFilter';
import NewMaintainabilityFilter from '../filters/NewMaintainabilityFilter';
import NewReliabilityFilter from '../filters/NewReliabilityFilter';
import NewSecurityFilter from '../filters/NewSecurityFilter';
-import NewSizeFilter from '../filters/NewSizeFilter';
+import NewLinesFilter from '../filters/NewLinesFilter';
import QualityGateFilter from '../filters/QualityGateFilter';
import ReliabilityFilter from '../filters/ReliabilityFilter';
import SecurityFilter from '../filters/SecurityFilter';
@@ -101,7 +101,7 @@ export default function PageSidebar({
<NewMaintainabilityFilter key="new_maintainability" {...facetProps} />,
<NewCoverageFilter key="new_coverage" {...facetProps} />,
<NewDuplicationsFilter key="new_duplications" {...facetProps} />,
- <NewSizeFilter key="new_size" {...facetProps} />
+ <NewLinesFilter key="new_size" {...facetProps} />
]}
<LanguagesFilterContainer {...facetProps} />
<TagsFilterContainer {...facetProps} />
diff --git a/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.js b/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.js
index ca31ad7fbe9..2580a34c4b8 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.js
@@ -27,6 +27,7 @@ import { VIEWS, VISUALIZATIONS } from '../utils';
export type Option = { label: string, type: string, value: string };
type Props = {
+ className?: string,
onChange: ({ view: string, visualization?: string }) => void,
view: string,
visualization?: string
@@ -64,7 +65,7 @@ export default class PerspectiveSelect extends React.PureComponent {
const { view, visualization } = this.props;
const perspective = view === 'visualizations' ? visualization : view;
return (
- <div>
+ <div className={this.props.className}>
<label>{translate('projects.perspective')}:</label>
<Select
className="little-spacer-left input-medium"
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectOptionBar.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectsOptionBar.js
index b506baf520d..e48872ff96e 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectOptionBar.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsOptionBar.js
@@ -22,16 +22,19 @@ import React from 'react';
import classNames from 'classnames';
import CloseIcon from '../../../components/icons-components/CloseIcon';
import PerspectiveSelect from './PerspectiveSelect';
+import ProjectsSortingSelect from './ProjectsSortingSelect';
type Props = {
onPerspectiveChange: ({ view: string, visualization?: string }) => void,
+ onSortChange: (sort: string, desc: boolean) => void,
onToggleOptionBar: boolean => void,
open: boolean,
+ selectedSort: string,
view: string,
visualization?: string
};
-export default class ProjectOptionBar extends React.PureComponent {
+export default class ProjectsOptionBar extends React.PureComponent {
props: Props;
closeBar = (evt: Event & { currentTarget: HTMLElement }) => {
@@ -45,18 +48,22 @@ export default class ProjectOptionBar extends React.PureComponent {
return (
<div className="projects-topbar">
<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 />
</a>
<div className="projects-topbar-actions-inner">
<PerspectiveSelect
+ className="projects-topbar-item js-projects-perspective-select"
onChange={this.props.onPerspectiveChange}
view={this.props.view}
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>
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelect.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelect.js
new file mode 100644
index 00000000000..4a5556bd91d
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelect.js
@@ -0,0 +1,105 @@
+/*
+ * 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>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelectOption.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelectOption.js
new file mode 100644
index 00000000000..f3613f1153e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelectOption.js
@@ -0,0 +1,70 @@
+/*
+ * 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>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectOptionBar-test.js b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectOptionBar-test.js
index b8667a28700..338395a6b84 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectOptionBar-test.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectOptionBar-test.js
@@ -19,22 +19,28 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
-import ProjectOptionBar from '../ProjectOptionBar';
+import ProjectsOptionBar from '../ProjectsOptionBar';
import { click } from '../../../../helpers/testUtils';
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', () => {
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();
});
it('should call close method correctly', () => {
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();
});
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.js b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.js
new file mode 100644
index 00000000000..472558d0085
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.js
@@ -0,0 +1,38 @@
+/*
+ * 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();
+});
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.js.snap
index fdbeb7ed11b..5460d9a0e3d 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.js.snap
@@ -97,7 +97,7 @@ exports[`should render \`leak\` view correctly 1`] = `
}
}
/>
- <NewSizeFilter
+ <NewLinesFilter
isFavorite={false}
property="new_lines"
query={
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectOptionBar-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectOptionBar-test.js.snap
index ce43bf57d1f..27dff385bd9 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectOptionBar-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectOptionBar-test.js.snap
@@ -8,6 +8,33 @@ Array [
]
`;
+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`] = `
<div
className="projects-topbar"
@@ -16,7 +43,7 @@ exports[`should render option bar closed 1`] = `
className="projects-topbar-actions"
>
<a
- className="projects-topbar-button projects-topbar-button-close"
+ className="projects-topbar-button button-icon"
href="#"
onClick={[Function]}
>
@@ -26,6 +53,11 @@ exports[`should render option bar closed 1`] = `
className="projects-topbar-actions-inner"
>
<PerspectiveSelect
+ className="projects-topbar-item js-projects-perspective-select"
+ view="overall"
+ />
+ <ProjectsSortingSelect
+ className="projects-topbar-item js-projects-sorting-select"
view="overall"
/>
</div>
@@ -41,7 +73,7 @@ exports[`should render option bar open 1`] = `
className="projects-topbar-actions open"
>
<a
- className="projects-topbar-button projects-topbar-button-close"
+ className="projects-topbar-button button-icon"
href="#"
onClick={[Function]}
>
@@ -51,8 +83,13 @@ exports[`should render option bar open 1`] = `
className="projects-topbar-actions-inner"
>
<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>
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelect-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelect-test.js.snap
new file mode 100644
index 00000000000..c5ef95acbd7
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelect-test.js.snap
@@ -0,0 +1,352 @@
+// 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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.js
index 827e5e482f8..f4a101535e3 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.js
+++ b/server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.js
@@ -20,7 +20,6 @@
import React from 'react';
import FilterContainer from './FilterContainer';
import FilterHeader from './FilterHeader';
-import SortingFilter from './SortingFilter';
import CoverageRating from '../../../components/ui/CoverageRating';
import { getCoverageRatingLabel, getCoverageRatingAverageValue } from '../../../helpers/ratings';
import { translate } from '../../../helpers/l10n';
@@ -70,17 +69,7 @@ export default class CoverageFilter extends React.PureComponent {
organization={this.props.organization}
getFacetValueForOption={this.getFacetValueForOption}
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')} />}
/>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.js
index dcda2e9bccf..609b3388a05 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.js
+++ b/server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.js
@@ -20,7 +20,6 @@
import React from 'react';
import FilterContainer from './FilterContainer';
import FilterHeader from './FilterHeader';
-import SortingFilter from './SortingFilter';
import DuplicationsRating from '../../../components/ui/DuplicationsRating';
import {
getDuplicationsRatingLabel,
@@ -73,16 +72,7 @@ export default class DuplicationsFilter extends React.PureComponent {
organization={this.props.organization}
getFacetValueForOption={this.getFacetValueForOption}
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')} />}
/>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewSizeFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.js
index c672d6c0ce4..3aa2f889cfc 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/NewSizeFilter.js
+++ b/server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.js
@@ -20,11 +20,10 @@
import React from 'react';
import FilterContainer from './FilterContainer';
import FilterHeader from './FilterHeader';
-import SortingFilter from './SortingFilter';
import { translate } from '../../../helpers/l10n';
import { getSizeRatingLabel } from '../../../helpers/ratings';
-export default class NewSizeFilter extends React.PureComponent {
+export default class NewLinesFilter extends React.PureComponent {
static propTypes = {
className: React.PropTypes.string,
query: React.PropTypes.object.isRequired,
@@ -68,18 +67,7 @@ export default class NewSizeFilter extends React.PureComponent {
organization={this.props.organization}
getFacetValueForOption={this.getFacetValueForOption}
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')} />}
/>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js
index aedd2d966cd..ca46efe6c21 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js
+++ b/server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js
@@ -20,7 +20,6 @@
import React from 'react';
import FilterContainer from './FilterContainer';
import FilterHeader from './FilterHeader';
-import SortingFilter from './SortingFilter';
import SizeRating from '../../../components/ui/SizeRating';
import { translate } from '../../../helpers/l10n';
import { getSizeRatingLabel, getSizeRatingAverageValue } from '../../../helpers/ratings';
@@ -72,18 +71,7 @@ export default class SizeFilter extends React.PureComponent {
organization={this.props.organization}
getFacetValueForOption={this.getFacetValueForOption}
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')} />}
/>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SortingFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/SortingFilter.js
deleted file mode 100644
index 61f7dfa1262..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/filters/SortingFilter.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SortingFilter-test.js b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SortingFilter-test.js
deleted file mode 100644
index a73ffcbadf9..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SortingFilter-test.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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();
-});
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SortingFilter-test.js.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SortingFilter-test.js.snap
deleted file mode 100644
index 3854d9fdf16..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SortingFilter-test.js.snap
+++ /dev/null
@@ -1,186 +0,0 @@
-// 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>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projects/styles.css b/server/sonar-web/src/main/js/apps/projects/styles.css
index 6786e39d14b..a6c471bef8c 100644
--- a/server/sonar-web/src/main/js/apps/projects/styles.css
+++ b/server/sonar-web/src/main/js/apps/projects/styles.css
@@ -39,30 +39,25 @@
border-bottom: 1px solid #e6e6e6;
}
+.projects-topbar-item {
+ padding: 0 24px;
+}
+
.projects-topbar-button {
box-sizing: border-box;
float: right;
padding: 11px;
+ padding-top: 15px;
border: none;
+ border-left: 1px solid #e6e6e6;
+ border-bottom: 1px solid #e6e6e6;
width: 46px;
height: 46px;
- color: #777;
text-align: center;
text-decoration: none;
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 {
width: 260px;
}
diff --git a/server/sonar-web/src/main/js/apps/projects/utils.js b/server/sonar-web/src/main/js/apps/projects/utils.js
index a81d6258c14..ab4d157c3a2 100644
--- a/server/sonar-web/src/main/js/apps/projects/utils.js
+++ b/server/sonar-web/src/main/js/apps/projects/utils.js
@@ -47,6 +47,42 @@ export const saveAll = () => save(LOCALSTORAGE_ALL);
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 VISUALIZATIONS = [
@@ -61,3 +97,8 @@ export const VISUALIZATIONS = [
export const localizeSorting = (sort?: string) => {
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 };
+};
diff --git a/server/sonar-web/src/main/js/components/icons-components/SortAscIcon.js b/server/sonar-web/src/main/js/components/icons-components/SortAscIcon.js
new file mode 100644
index 00000000000..afaa0752c4a
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/icons-components/SortAscIcon.js
@@ -0,0 +1,40 @@
+/*
+ * 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>
+ );
+}
diff --git a/server/sonar-web/src/main/js/components/icons-components/SortDescIcon.js b/server/sonar-web/src/main/js/components/icons-components/SortDescIcon.js
new file mode 100644
index 00000000000..75c8e788514
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/icons-components/SortDescIcon.js
@@ -0,0 +1,40 @@
+/*
+ * 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>
+ );
+}
diff --git a/server/sonar-web/src/main/less/init/forms.less b/server/sonar-web/src/main/less/init/forms.less
index 8edf28c49d2..6ef2319bae8 100644
--- a/server/sonar-web/src/main/less/init/forms.less
+++ b/server/sonar-web/src/main/less/init/forms.less
@@ -223,6 +223,11 @@ input[type="submit"].button-grey {
}
}
+.button-icon {
+ border: none;
+ color: @secondFontColor;
+}
+
.button-small {
height: 20px;
line-height: 18px;