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 {
)}
<h3>{translate('filters')}</h3>
+ <SearchFilter
+ query={this.props.query}
+ isFavorite={this.props.isFavorite}
+ organization={this.props.organization}/>
</div>
<QualityGateFilter
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';
getFacetValueForOption: React.PropTypes.func,
- halfWidth: React.PropTypes.bool,
-
- getFilterUrl: React.PropTypes.func.isRequired
+ halfWidth: React.PropTypes.bool
};
static defaultProps = {
} else {
urlOption = this.isSelected(option) ? null : option;
}
- return this.props.getFilterUrl({ [property]: urlOption });
+ return getFilterUrl(this.props, { [property]: urlOption });
}
renderHeader () {
*/
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));
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';
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);
}
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));
--- /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 { 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);
--- /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 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 };
+};
'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) => {
}
}
+ if (query['search'] != null) {
+ conditions.push(`query = "${query['search']}"`);
+ }
+
return conditions.join(' and ');
};
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;
}
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
#------------------------------------------------------------------------------