*/
import React from 'react';
import FilterContainer from './FilterContainer';
+import SortingFilter from './SortingFilter';
import CoverageRating from '../../../components/ui/CoverageRating';
import { getCoverageRatingLabel, getCoverageRatingAverageValue } from '../../../helpers/ratings';
export default class CoverageFilter extends React.Component {
+ static propTypes = {
+ query: React.PropTypes.object.isRequired,
+ isFavorite: React.PropTypes.bool,
+ organization: React.PropTypes.object
+ }
+
+ property = 'coverage';
+
renderOption = (option, selected) => {
return (
<span>
);
};
+ renderSort = () => {
+ return (
+ <SortingFilter
+ property={this.property}
+ query={this.props.query}
+ isFavorite={this.props.isFavorite}
+ organization={this.props.organization}
+ sortDesc="right"/>
+ );
+ }
+
getFacetValueForOption = (facet, option) => {
const map = ['80.0-*', '70.0-80.0', '50.0-70.0', '30.0-50.0', '*-30.0'];
return facet[map[option - 1]];
render () {
return (
<FilterContainer
- property="coverage"
+ property={this.property}
getOptions={() => [1, 2, 3, 4, 5]}
renderName={() => 'Coverage'}
renderOption={this.renderOption}
+ renderSort={this.renderSort}
getFacetValueForOption={this.getFacetValueForOption}
query={this.props.query}
isFavorite={this.props.isFavorite}
*/
import React from 'react';
import FilterContainer from './FilterContainer';
+import SortingFilter from './SortingFilter';
import DuplicationsRating from '../../../components/ui/DuplicationsRating';
import { getDuplicationsRatingLabel, getDuplicationsRatingAverageValue } from '../../../helpers/ratings';
export default class DuplicationsFilter extends React.Component {
+ static propTypes = {
+ query: React.PropTypes.object.isRequired,
+ isFavorite: React.PropTypes.bool,
+ organization: React.PropTypes.object
+ }
+
+ property = 'duplications';
+
renderOption = (option, selected) => {
return (
<span>
);
};
+ renderSort = () => {
+ return (
+ <SortingFilter
+ property={this.property}
+ query={this.props.query}
+ isFavorite={this.props.isFavorite}
+ organization={this.props.organization}/>
+ );
+ }
+
getFacetValueForOption = (facet, option) => {
const map = ['*-3.0', '3.0-5.0', '5.0-10.0', '10.0-20.0', '20.0-*'];
return facet[map[option - 1]];
render () {
return (
<FilterContainer
- property="duplications"
+ property={this.property}
getOptions={() => [1, 2, 3, 4, 5]}
renderName={() => 'Duplications'}
renderOption={this.renderOption}
+ renderSort={this.renderSort}
getFacetValueForOption={this.getFacetValueForOption}
query={this.props.query}
isFavorite={this.props.isFavorite}
renderName: React.PropTypes.func.isRequired,
renderOption: React.PropTypes.func.isRequired,
renderFooter: React.PropTypes.func,
+ renderSort: React.PropTypes.func,
getFacetValueForOption: React.PropTypes.func,
return (
<div className="search-navigator-facet-header projects-facet-header">
{this.props.renderName()}
+ {this.props.renderSort && this.props.renderSort()}
</div>
);
}
*/
import React from 'react';
import FilterContainer from './FilterContainer';
+import SortingFilter from './SortingFilter';
import Rating from '../../../components/ui/Rating';
export default class IssuesFilter extends React.Component {
+ static propTypes = {
+ property: React.PropTypes.string.isRequired,
+ name: React.PropTypes.string.isRequired,
+ query: React.PropTypes.object.isRequired,
+ isFavorite: React.PropTypes.bool,
+ organization: React.PropTypes.object
+ }
+
renderOption = (option, selected) => {
return (
<span>
);
};
+ renderSort = () => {
+ return (
+ <SortingFilter
+ property={this.props.property}
+ query={this.props.query}
+ isFavorite={this.props.isFavorite}
+ organization={this.props.organization}/>
+ );
+ }
+
getFacetValueForOption = (facet, option) => {
return facet[option];
};
getOptions={() => [1, 2, 3, 4, 5]}
renderName={() => this.props.name}
renderOption={this.renderOption}
+ renderSort={this.renderSort}
getFacetValueForOption={this.getFacetValueForOption}
query={this.props.query}
isFavorite={this.props.isFavorite}
*/
import React from 'react';
import FilterContainer from './FilterContainer';
+import SortingFilter from './SortingFilter';
import SizeRating from '../../../components/ui/SizeRating';
+import { translate } from '../../../helpers/l10n';
import { getSizeRatingLabel, getSizeRatingAverageValue } from '../../../helpers/ratings';
export default class SizeFilter extends React.Component {
+ static propTypes = {
+ query: React.PropTypes.object.isRequired,
+ isFavorite: React.PropTypes.bool,
+ organization: React.PropTypes.object
+ }
+
+ property = 'size';
+
renderOption = (option, selected) => {
return (
<span>
);
};
+ renderSort = () => {
+ return (
+ <SortingFilter
+ property={this.property}
+ query={this.props.query}
+ isFavorite={this.props.isFavorite}
+ organization={this.props.organization}
+ leftText={translate('biggest')}
+ rightText={translate('smallest')}/>
+ );
+ }
+
getFacetValueForOption = (facet, option) => {
const map = ['*-1000.0', '1000.0-10000.0', '10000.0-100000.0', '100000.0-500000.0', '500000.0-*'];
return facet[map[option - 1]];
render () {
return (
<FilterContainer
- property="size"
+ property={this.property}
getOptions={() => [1, 2, 3, 4, 5]}
renderName={() => 'Size'}
renderOption={this.renderOption}
+ renderSort={this.renderSort}
getFacetValueForOption={this.getFacetValueForOption}
query={this.props.query}
isFavorite={this.props.isFavorite}
--- /dev/null
+/*
+ * 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.Component {
+ 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',
+ leftText: translate('worst'),
+ rightText: translate('best')
+ };
+
+ 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}</Link>
+ <Link
+ onClick={this.blurLink}
+ className={this.getLinkClass('right')}
+ to={this.getLinkPath('right')}>{rightText}</Link>
+ </div>
+ </div>
+ );
+ }
+}
import { getProjectsAppState } from '../../../store/rootReducer';
import { getMeasuresForProjects } from '../../../api/measures';
import { receiveComponentsMeasures } from '../../../store/measures/actions';
-import { convertToFilter } from './utils';
+import { convertToQueryData } from './utils';
import { receiveFavorites } from '../../../store/favorites/duck';
import { getOrganizations } from '../../../api/organizations';
import { receiveOrganizations } from '../../../store/organizations/duck';
export const fetchProjects = (query, isFavorite, organization) => dispatch => {
dispatch(updateState({ loading: true }));
- const data = { ps: PAGE_SIZE, facets: FACETS.join() };
- const filter = convertToFilter(query, isFavorite);
- if (filter) {
- data.filter = filter;
- }
- if (organization) {
- data.organization = organization.key;
- }
+ const data = convertToQueryData(query, isFavorite, organization, { ps: PAGE_SIZE, facets: FACETS.join() });
return searchProjects(data).then(onReceiveProjects(dispatch), onFail(dispatch));
};
dispatch(updateState({ loading: true }));
const state = getState();
const { pageIndex } = getProjectsAppState(state);
- const data = { ps: PAGE_SIZE, p: pageIndex + 1 };
- const filter = convertToFilter(query, isFavorite);
- if (filter) {
- data.filter = filter;
- }
- if (organization) {
- data.organization = organization.key;
- }
+ const data = convertToQueryData(query, isFavorite, organization, { ps: PAGE_SIZE, p: pageIndex + 1 });
return searchProjects(data).then(onReceiveMoreProjects(dispatch), onFail(dispatch));
};
'duplications': getAsNumericRating(urlQuery['duplications']),
'size': getAsNumericRating(urlQuery['size']),
'languages': getAsArray(urlQuery['languages'], getAsString),
- 'search': getAsString(urlQuery['search'])
+ 'search': getAsString(urlQuery['search']),
+ 'sort': getAsString(urlQuery['sort'])
});
+export const mapMetricToProperty = metricKey => {
+ const map = {
+ 'reliability_rating': 'reliability',
+ 'security_rating': 'security',
+ 'sqale_rating': 'maintainability',
+ 'coverage': 'coverage',
+ 'duplicated_lines_density': 'duplications',
+ 'ncloc': 'size',
+ 'alert_status': 'gate',
+ 'languages': 'languages',
+ 'query': 'search'
+ };
+ return map[metricKey];
+};
+
+export const mapPropertyToMetric = property => {
+ const map = {
+ 'reliability': 'reliability_rating',
+ 'security': 'security_rating',
+ 'maintainability': 'sqale_rating',
+ 'coverage': 'coverage',
+ 'duplications': 'duplicated_lines_density',
+ 'size': 'ncloc',
+ 'gate': 'alert_status',
+ 'languages': 'languages',
+ 'search': 'query'
+ };
+ return map[property];
+};
+
const convertIssuesRating = (metric, rating) => {
if (rating > 1 && rating < 5) {
return `${metric} >= ${rating}`;
const convertCoverage = coverage => {
switch (coverage) {
case 1:
- return 'coverage >= 80';
+ return mapPropertyToMetric('coverage') + ' >= 80';
case 2:
- return 'coverage < 80';
+ return mapPropertyToMetric('coverage') + ' < 80';
case 3:
- return 'coverage < 70';
+ return mapPropertyToMetric('coverage') + ' < 70';
case 4:
- return 'coverage < 50';
+ return mapPropertyToMetric('coverage') + ' < 50';
case 5:
- return 'coverage < 30';
+ return mapPropertyToMetric('coverage') + ' < 30';
default:
return '';
}
const convertDuplications = duplications => {
switch (duplications) {
case 1:
- return 'duplicated_lines_density < 3';
+ return mapPropertyToMetric('duplications') + ' < 3';
case 2:
- return 'duplicated_lines_density >= 3';
+ return mapPropertyToMetric('duplications') + ' >= 3';
case 3:
- return 'duplicated_lines_density >= 5';
+ return mapPropertyToMetric('duplications') + ' >= 5';
case 4:
- return 'duplicated_lines_density >= 10';
+ return mapPropertyToMetric('duplications') + ' >= 10';
case 5:
- return 'duplicated_lines_density >= 20';
+ return mapPropertyToMetric('duplications') + ' >= 20';
default:
return '';
}
const convertSize = size => {
switch (size) {
case 1:
- return 'ncloc < 1000';
+ return mapPropertyToMetric('size') + ' < 1000';
case 2:
- return 'ncloc >= 1000';
+ return mapPropertyToMetric('size') + ' >= 1000';
case 3:
- return 'ncloc >= 10000';
+ return mapPropertyToMetric('size') + ' >= 10000';
case 4:
- return 'ncloc >= 100000';
+ return mapPropertyToMetric('size') + ' >= 100000';
case 5:
- return 'ncloc >= 500000';
+ return mapPropertyToMetric('size') + ' >= 500000';
default:
return '';
}
};
-export const convertToFilter = (query, isFavorite) => {
+const convertToFilter = (query, isFavorite) => {
const conditions = [];
if (isFavorite) {
}
if (query['gate'] != null) {
- conditions.push('alert_status = ' + query['gate']);
+ conditions.push(mapPropertyToMetric('gate') + ' = ' + query['gate']);
}
if (query['coverage'] != null) {
conditions.push(convertSize(query['size']));
}
- if (query['reliability'] != null) {
- conditions.push(convertIssuesRating('reliability_rating', query['reliability']));
- }
-
- if (query['security'] != null) {
- conditions.push(convertIssuesRating('security_rating', query['security']));
- }
-
- if (query['maintainability'] != null) {
- conditions.push(convertIssuesRating('sqale_rating', query['maintainability']));
- }
+ ['reliability', 'security', 'maintainability'].forEach(property => {
+ if (query[property] != null) {
+ conditions.push(convertIssuesRating(mapPropertyToMetric(property), query[property]));
+ }
+ });
const { languages } = query;
if (languages != null) {
if (!Array.isArray(languages) || languages.length < 2) {
- conditions.push('languages = ' + languages);
+ conditions.push(mapPropertyToMetric('languages') + ' = ' + languages);
} else {
- conditions.push(`languages IN (${languages.join(', ')})`);
+ conditions.push(`${mapPropertyToMetric('languages')} IN (${languages.join(', ')})`);
}
}
if (query['search'] != null) {
- conditions.push(`query = "${query['search']}"`);
+ conditions.push(`${mapPropertyToMetric('search')} = "${query['search']}"`);
}
return conditions.join(' and ');
};
-export const mapMetricToProperty = metricKey => {
- const map = {
- 'reliability_rating': 'reliability',
- 'security_rating': 'security',
- 'sqale_rating': 'maintainability',
- 'coverage': 'coverage',
- 'duplicated_lines_density': 'duplications',
- 'ncloc': 'size',
- 'alert_status': 'gate',
- 'languages': 'languages'
- };
- return map[metricKey];
+export const convertToSorting = ({ sort }) => {
+ if (sort && sort[0] === '-') {
+ return { s: mapPropertyToMetric(sort.substr(1)), asc: false };
+ }
+ return { s: mapPropertyToMetric(sort) };
+};
+
+export const convertToQueryData = (query, isFavorite, organization, defaultData = {}) => {
+ const data = { ...defaultData };
+ const filter = convertToFilter(query, isFavorite);
+ const sort = convertToSorting(query);
+
+ if (filter) {
+ data.filter = filter;
+ }
+ if (sort.s) {
+ data.s = sort.s;
+ }
+ if (sort.hasOwnProperty('asc')) {
+ data.asc = sort.asc;
+ }
+ if (organization) {
+ data.organization = organization.key;
+ }
+ return data;
};
transition: none;
}
+.projects-facet-sort {
+ float: right;
+ font-weight: normal;
+ font-size: 11px;
+ color: #777;
+ text-transform: lowercase;
+}
+
+.projects-facet-sort .button-group {
+ margin-top: -3px;
+}
+
+.projects-facet-sort .button-small {
+ padding: 0 6px;
+ font-size: 11px;
+ font-weight: normal;
+}
+
.projects-facets-header {
margin-bottom: 10px;
padding: 10px 0;
}
}
+.button-grey,
+input[type="submit"].button-grey {
+ border-color: @middleGrey;
+ color: @secondFontColor;
+
+ &:hover, &:focus, &.active {
+ background: @middleGrey;
+ color: @white;
+ }
+
+ &.button-active {
+ background: @secondFontColor;
+ border-color: @secondFontColor;
+ color: @white;
+ }
+}
+
.button-clean,
.button-clean:hover,
.button-clean:focus {
back=Back
backup=Backup
backup_verb=Back up
+best=Best
+biggest=Biggest
blocker=Blocker
bold=Bold
branch=Branch
show_verb=Show
x_of_y_shown={0} of {1} shown
size=Size
+smallest=Smallest
status=Status
status_abbreviated=St.
sub_project=Sub-project
views=Views
violations=Violations
with=With
+worst=Worst
projects.explore_projects=Explore Projects
projects.not_analyzed=Project is not analyzed yet.
projects.search=Search by project name or key
+projects.sort_list=Sort list by
#------------------------------------------------------------------------------