Browse Source

SONAR-8877 Projects searchbox on projects page (#1747)

tags/6.4-RC1
Grégoire Aubert 7 years ago
parent
commit
421198d449

+ 5
- 0
server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js View File

@@ -27,6 +27,7 @@ import ReliabilityFilter from '../filters/ReliabilityFilter';
import SecurityFilter from '../filters/SecurityFilter';
import MaintainabilityFilter from '../filters/MaintainabilityFilter';
import LanguageFilter from '../filters/LanguageFilter';
import SearchFilter from '../filters/SearchFilter';
import { translate } from '../../../helpers/l10n';

export default class PageSidebar extends React.Component {
@@ -56,6 +57,10 @@ export default class PageSidebar extends React.Component {
)}

<h3>{translate('filters')}</h3>
<SearchFilter
query={this.props.query}
isFavorite={this.props.isFavorite}
organization={this.props.organization}/>
</div>

<QualityGateFilter

+ 3
- 4
server/sonar-web/src/main/js/apps/projects/filters/Filter.js View File

@@ -20,6 +20,7 @@
import React from 'react';
import classNames from 'classnames';
import { Link } from 'react-router';
import { getFilterUrl } from './utils';
import { formatMeasure } from '../../../helpers/measures';
import { translate } from '../../../helpers/l10n';

@@ -37,9 +38,7 @@ export default class Filter extends React.Component {

getFacetValueForOption: React.PropTypes.func,

halfWidth: React.PropTypes.bool,

getFilterUrl: React.PropTypes.func.isRequired
halfWidth: React.PropTypes.bool
};

static defaultProps = {
@@ -64,7 +63,7 @@ export default class Filter extends React.Component {
} else {
urlOption = this.isSelected(option) ? null : option;
}
return this.props.getFilterUrl({ [property]: urlOption });
return getFilterUrl(this.props, { [property]: urlOption });
}

renderHeader () {

+ 1
- 11
server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.js View File

@@ -19,23 +19,13 @@
*/
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import omitBy from 'lodash/omitBy';
import isNil from 'lodash/isNil';
import Filter from './Filter';
import { getProjectsAppFacetByProperty, getProjectsAppMaxFacetValue } from '../../../store/rootReducer';

const mapStateToProps = (state, ownProps) => ({
value: ownProps.query[ownProps.property],
facet: getProjectsAppFacetByProperty(state, ownProps.property),
maxFacetValue: getProjectsAppMaxFacetValue(state),
getFilterUrl: part => {
const basePathName = ownProps.organization ?
`/organizations/${ownProps.organization.key}/projects` :
'/projects';
const pathname = basePathName + (ownProps.isFavorite ? '/favorite' : '');
const query = omitBy({ ...ownProps.query, ...part }, isNil);
return { pathname, query };
}
maxFacetValue: getProjectsAppMaxFacetValue(state)
});

export default connect(mapStateToProps)(withRouter(Filter));

+ 4
- 14
server/sonar-web/src/main/js/apps/projects/filters/LanguageFilterFooter.js View File

@@ -22,8 +22,7 @@ import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import Select from 'react-select';
import difference from 'lodash/difference';
import isNil from 'lodash/isNil';
import omitBy from 'lodash/omitBy';
import { getFilterUrl } from './utils';
import { getProjectsAppFacetByProperty, getLanguages } from '../../../store/rootReducer';
import { translate } from '../../../helpers/l10n';

@@ -35,13 +34,12 @@ class LanguageFilterFooter extends React.Component {
organization: React.PropTypes.object,
languages: React.PropTypes.object,
value: React.PropTypes.any,
facet: React.PropTypes.object,
getFilterUrl: React.PropTypes.func.isRequired
facet: React.PropTypes.object
}

handleLanguageChange = ({ value }) => {
const urlOptions = (this.props.value || []).concat(value).join(',');
const path = this.props.getFilterUrl({ [this.props.property]: urlOptions });
const path = getFilterUrl(this.props, { [this.props.property]: urlOptions });
this.props.router.push(path);
}

@@ -70,15 +68,7 @@ class LanguageFilterFooter extends React.Component {
const mapStateToProps = (state, ownProps) => ({
languages: getLanguages(state),
value: ownProps.query[ownProps.property],
facet: getProjectsAppFacetByProperty(state, ownProps.property),
getFilterUrl: part => {
const basePathName = ownProps.organization ?
`/organizations/${ownProps.organization.key}/projects` :
'/projects';
const pathname = basePathName + (ownProps.isFavorite ? '/favorite' : '');
const query = omitBy({ ...ownProps.query, ...part }, isNil);
return { pathname, query };
}
facet: getProjectsAppFacetByProperty(state, ownProps.property)
});

export default connect(mapStateToProps)(withRouter(LanguageFilterFooter));

+ 86
- 0
server/sonar-web/src/main/js/apps/projects/filters/SearchFilter.js View File

@@ -0,0 +1,86 @@
/*
* 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 { withRouter } from 'react-router';
import classNames from 'classnames';
import debounce from 'lodash/debounce';
import { getFilterUrl } from './utils';
import { translate, translateWithParameters } from '../../../helpers/l10n';

class SearchFilter extends React.Component {
static propTypes = {
query: React.PropTypes.object.isRequired,
router: React.PropTypes.object.isRequired,
isFavorite: React.PropTypes.bool,
organization: React.PropTypes.object
}

constructor (props) {
super(props);
this.state = {
userQuery: props.query.search
};
this.handleSearch = debounce(this.handleSearch.bind(this), 250);
}

componentWillReceiveProps (nextProps) {
if (this.props.query.search === this.state.userQuery && nextProps.query.search !== this.props.query.search) {
this.setState({
userQuery: nextProps.query.search || ''
});
}
}

handleSearch (userQuery) {
const path = getFilterUrl(this.props, { search: userQuery || null });
this.props.router.push(path);
}

handleQueryChange (userQuery) {
this.setState({ userQuery });
if (!userQuery || userQuery.length >= 2) {
this.handleSearch(userQuery);
}
}

render () {
const { userQuery } = this.state;
const inputClassName = classNames('input-super-large', {
'touched': userQuery && userQuery.length < 2
});

return (
<div className="projects-facet-search" data-key="search">
<input
type="search"
value={userQuery || ''}
className={inputClassName}
placeholder={translate('projects.search')}
onChange={event => this.handleQueryChange(event.target.value)}
autoComplete="off"/>
<span className="note spacer-left">
{translateWithParameters('select2.tooShort', 2)}
</span>
</div>
);
}
}

export default withRouter(SearchFilter);

+ 30
- 0
server/sonar-web/src/main/js/apps/projects/filters/utils.js View File

@@ -0,0 +1,30 @@
/*
* 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 omitBy from 'lodash/omitBy';
import isNil from 'lodash/isNil';

export const getFilterUrl = (ownProps, part) => {
const basePathName = ownProps.organization ?
`/organizations/${ownProps.organization.key}/projects` :
'/projects';
const pathname = basePathName + (ownProps.isFavorite ? '/favorite' : '');
const query = omitBy({ ...ownProps.query, ...part }, isNil);
return { pathname, query };
};

+ 6
- 1
server/sonar-web/src/main/js/apps/projects/store/utils.js View File

@@ -54,7 +54,8 @@ export const parseUrlQuery = urlQuery => ({
'coverage': getAsNumericRating(urlQuery['coverage']),
'duplications': getAsNumericRating(urlQuery['duplications']),
'size': getAsNumericRating(urlQuery['size']),
'language': getAsArray(urlQuery['language'], getAsString)
'language': getAsArray(urlQuery['language'], getAsString),
'search': getAsString(urlQuery['search'])
});

const convertIssuesRating = (metric, rating) => {
@@ -159,6 +160,10 @@ export const convertToFilter = (query, isFavorite) => {
}
}

if (query['search'] != null) {
conditions.push(`query = "${query['search']}"`);
}

return conditions.join(' and ');
};


+ 18
- 0
server/sonar-web/src/main/js/apps/projects/styles.css View File

@@ -112,6 +112,24 @@
border-bottom: 1px solid #e6e6e6;
}

.projects-facet-search {
position: relative;
padding-top: 10px;
padding-bottom: 10px;
}

.projects-facet-search .note {
position: absolute;
opacity: 0;
left: 0;
bottom: -7px;
transition: opacity 0.3s ease;
}

.projects-facet-search input.touched ~ .note {
opacity: 1;
}

.projects-facets-reset {
float: right;
}

+ 1
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -823,6 +823,7 @@ projects.no_favorite_projects=You don't have any favorite projects yet.
projects.no_favorite_projects.engagement=Discover and mark as favorites projects you are interested in to have a quick access to them.
projects.explore_projects=Explore Projects
projects.not_analyzed=Project is not analyzed yet.
projects.search=Search by project name or key


#------------------------------------------------------------------------------

Loading…
Cancel
Save