Parcourir la source

SONAR-9254 Move projects facets sorting to the topbar of the projects page

tags/6.5-M1
Grégoire Aubert il y a 7 ans
Parent
révision
8a173aa317
28 fichiers modifiés avec 862 ajouts et 450 suppressions
  1. 2
    4
      it/it-tests/src/test/java/it/projectSearch/ProjectsPageTest.java
  2. 0
    20
      it/it-tests/src/test/java/pageobjects/projects/FacetItem.java
  3. 22
    0
      it/it-tests/src/test/java/pageobjects/projects/ProjectsPage.java
  4. 39
    9
      server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
  5. 3
    3
      server/sonar-web/src/main/js/apps/projects/components/PageHeader.js
  6. 2
    2
      server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js
  7. 2
    1
      server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.js
  8. 12
    5
      server/sonar-web/src/main/js/apps/projects/components/ProjectsOptionBar.js
  9. 105
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelect.js
  10. 70
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelectOption.js
  11. 11
    5
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectOptionBar-test.js
  12. 38
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.js
  13. 1
    1
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.js.snap
  14. 41
    4
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectOptionBar-test.js.snap
  15. 352
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelect-test.js.snap
  16. 1
    12
      server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.js
  17. 1
    11
      server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.js
  18. 2
    14
      server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.js
  19. 1
    13
      server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js
  20. 0
    92
      server/sonar-web/src/main/js/apps/projects/filters/SortingFilter.js
  21. 0
    55
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/SortingFilter-test.js
  22. 0
    186
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SortingFilter-test.js.snap
  23. 7
    12
      server/sonar-web/src/main/js/apps/projects/styles.css
  24. 41
    0
      server/sonar-web/src/main/js/apps/projects/utils.js
  25. 40
    0
      server/sonar-web/src/main/js/components/icons-components/SortAscIcon.js
  26. 40
    0
      server/sonar-web/src/main/js/components/icons-components/SortDescIcon.js
  27. 5
    0
      server/sonar-web/src/main/less/init/forms.less
  28. 24
    1
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 2
- 4
it/it-tests/src/test/java/it/projectSearch/ProjectsPageTest.java Voir le fichier

@@ -146,11 +146,9 @@ public class ProjectsPageTest {
@Test
public void should_sort_by_facet() {
ProjectsPage page = nav.openProjects();
page.getFacetByProperty("duplications")
.sortListDesc();
page.sortProjects("Duplications");
page.getProjectByIdx(0).shouldHaveMeasure("duplicated_lines_density", "63.7%");
page.getFacetByProperty("duplications")
.sortListAsc();
page.invertSorting();
page.getProjectByIdx(0).shouldHaveMeasure("duplicated_lines_density", "0.0%");
}


+ 0
- 20
it/it-tests/src/test/java/pageobjects/projects/FacetItem.java Voir le fichier

@@ -48,24 +48,4 @@ public class FacetItem {
selectInput.pressEnter();
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;
}
}

+ 22
- 0
it/it-tests/src/test/java/pageobjects/projects/ProjectsPage.java Voir le fichier

@@ -89,4 +89,26 @@ public class ProjectsPage {
searchInput.setValue("").sendKeys(search);
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;
}
}

+ 39
- 9
server/sonar-web/src/main/js/apps/projects/components/AllProjects.js Voir le fichier

@@ -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}
/>

+ 3
- 3
server/sonar-web/src/main/js/apps/projects/components/PageHeader.js Voir le fichier

@@ -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>

+ 2
- 2
server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js Voir le fichier

@@ -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} />

+ 2
- 1
server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.js Voir le fichier

@@ -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"

server/sonar-web/src/main/js/apps/projects/components/ProjectOptionBar.js → server/sonar-web/src/main/js/apps/projects/components/ProjectsOptionBar.js Voir le fichier

@@ -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>

+ 105
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelect.js Voir le fichier

@@ -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>
);
}
}

+ 70
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelectOption.js Voir le fichier

@@ -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>
);
}
}

+ 11
- 5
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectOptionBar-test.js Voir le fichier

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

+ 38
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.js Voir le fichier

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

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.js.snap Voir le fichier

@@ -97,7 +97,7 @@ exports[`should render \`leak\` view correctly 1`] = `
}
}
/>
<NewSizeFilter
<NewLinesFilter
isFavorite={false}
property="new_lines"
query={

+ 41
- 4
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectOptionBar-test.js.snap Voir le fichier

@@ -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>

+ 352
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelect-test.js.snap Voir le fichier

@@ -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>
`;

+ 1
- 12
server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.js Voir le fichier

@@ -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')} />}
/>
);
}

+ 1
- 11
server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.js Voir le fichier

@@ -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')} />}
/>
);
}

server/sonar-web/src/main/js/apps/projects/filters/NewSizeFilter.js → server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.js Voir le fichier

@@ -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')} />}
/>
);
}

+ 1
- 13
server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js Voir le fichier

@@ -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')} />}
/>
);
}

+ 0
- 92
server/sonar-web/src/main/js/apps/projects/filters/SortingFilter.js Voir le fichier

@@ -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>
);
}
}

+ 0
- 55
server/sonar-web/src/main/js/apps/projects/filters/__tests__/SortingFilter-test.js Voir le fichier

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

+ 0
- 186
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SortingFilter-test.js.snap Voir le fichier

@@ -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>
`;

+ 7
- 12
server/sonar-web/src/main/js/apps/projects/styles.css Voir le fichier

@@ -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;
}

+ 41
- 0
server/sonar-web/src/main/js/apps/projects/utils.js Voir le fichier

@@ -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 };
};

+ 40
- 0
server/sonar-web/src/main/js/components/icons-components/SortAscIcon.js Voir le fichier

@@ -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>
);
}

+ 40
- 0
server/sonar-web/src/main/js/components/icons-components/SortDescIcon.js Voir le fichier

@@ -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>
);
}

+ 5
- 0
server/sonar-web/src/main/less/init/forms.less Voir le fichier

@@ -223,6 +223,11 @@ input[type="submit"].button-grey {
}
}

.button-icon {
border: none;
color: @secondFontColor;
}

.button-small {
height: 20px;
line-height: 18px;

+ 24
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties Voir le fichier

@@ -865,6 +865,29 @@ projects.last_analysis_on_x=Last analysis on {0}
projects.search=Search by project name or key
projects.sort_list=Sort list by
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.overall=Overall Status
projects.view.leak=Leak
@@ -884,6 +907,7 @@ projects.visualization.duplications.description=See duplications' long-term risk
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.languages=Languages
projects.facets.new_lines=New Lines
projects.facets.tags=Tags
projects.sort.name=by name
projects.sort.reliability=by reliability (best first)
@@ -1939,7 +1963,6 @@ severity.INFO.description=Neither a bug nor a quality flaw. Just a finding.
#------------------------------------------------------------------------------

metric_domain.Size=Size
metric_domain.new_size=New Lines
metric_domain.Tests=Tests
metric_domain.Integration Tests=Integration Tests
metric_domain.Complexity=Complexity

Chargement…
Annuler
Enregistrer