From: Grégoire Aubert Date: Thu, 16 Mar 2017 09:37:59 +0000 (+0100) Subject: SONAR-8922 Add tags facet on project page (#1790) X-Git-Tag: 6.4-RC1~757 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=24998ec360f43a7a745d88a5856d5828ba2d11ad;p=sonarqube.git SONAR-8922 Add tags facet on project page (#1790) * Refactor the way filters are rendered * SONAR-8922 Add the tags facet on the projects page * SONAR-8923 Add the tags searchbox --- diff --git a/it/it-tests/src/test/java/it/projectSearch/ProjectsPageTest.java b/it/it-tests/src/test/java/it/projectSearch/ProjectsPageTest.java index 280bd54cb7d..45dceeac2c6 100644 --- a/it/it-tests/src/test/java/it/projectSearch/ProjectsPageTest.java +++ b/it/it-tests/src/test/java/it/projectSearch/ProjectsPageTest.java @@ -26,6 +26,7 @@ import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; +import org.sonarqube.ws.client.PostRequest; import org.sonarqube.ws.client.WsClient; import pageobjects.Navigation; import pageobjects.projects.ProjectsPage; @@ -118,13 +119,30 @@ public class ProjectsPageTest { } @Test - public void should_add_language() { + public void should_add_language_to_facet() { ProjectsPage page = nav.openProjects(); page.getFacetByProperty("languages") .selectOptionItem("xoo2") .shouldHaveValue("xoo2", "0"); } + @Test + public void should_add_tag_to_facet() { + // Add some tags to this project + wsClient.wsConnector().call( + new PostRequest("api/project_tags/set") + .setParam("project", PROJECT_KEY) + .setParam("tags", "aa,bb,cc,dd,ee,ff,gg,hh,ii,jj,zz") + ); + + ProjectsPage page = nav.openProjects(); + page.getFacetByProperty("tags") + .shouldHaveValue("aa", "1") + .shouldHaveValue("ii", "1") + .selectOptionItem("zz") + .shouldHaveValue("zz", "1"); + } + @Test public void should_sort_by_facet() { ProjectsPage page = nav.openProjects(); diff --git a/it/it-tests/src/test/java/pageobjects/projects/FacetItem.java b/it/it-tests/src/test/java/pageobjects/projects/FacetItem.java index 79ff84d1005..528cb3c2505 100644 --- a/it/it-tests/src/test/java/pageobjects/projects/FacetItem.java +++ b/it/it-tests/src/test/java/pageobjects/projects/FacetItem.java @@ -42,7 +42,10 @@ public class FacetItem { } public FacetItem selectOptionItem(String value) { - this.elt.$(".Select-input input").val(value).pressEnter(); + SelenideElement selectInput = this.elt.$(".Select-input input"); + selectInput.val(value); + this.elt.$("div.Select-option.is-focused").should(Condition.exist); + selectInput.pressEnter(); return this; } diff --git a/server/sonar-web/src/main/js/api/components.js b/server/sonar-web/src/main/js/api/components.js index 456cb90e0e5..f0469af18ec 100644 --- a/server/sonar-web/src/main/js/api/components.js +++ b/server/sonar-web/src/main/js/api/components.js @@ -56,6 +56,16 @@ export function createProject (data: { return postJSON(url, data); } +export function searchProjectTags (data?: { ps?: number, q?: string }) { + const url = '/api/project_tags/search'; + return getJSON(url, data); +} + +export function setProjectTags (data: { project: string, tags: string }) { + const url = '/api/project_tags/set'; + return postJSON(url, data); +} + export function getComponentTree ( strategy: string, componentKey: string, 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 f3c3a21b04e..11c4fb75cef 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 @@ -27,8 +27,9 @@ import QualityGateFilter from '../filters/QualityGateFilter'; import ReliabilityFilter from '../filters/ReliabilityFilter'; import SecurityFilter from '../filters/SecurityFilter'; import MaintainabilityFilter from '../filters/MaintainabilityFilter'; -import LanguageFilter from '../filters/LanguageFilter'; +import TagsFilterContainer from '../filters/TagsFilterContainer'; import SearchFilterContainer from '../filters/SearchFilterContainer'; +import LanguagesFilterContainer from '../filters/LanguagesFilterContainer'; import { translate } from '../../../helpers/l10n'; export default class PageSidebar extends React.PureComponent { @@ -93,7 +94,11 @@ export default class PageSidebar extends React.PureComponent { query={this.props.query} isFavorite={this.props.isFavorite} organization={this.props.organization}/> - + 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 8789a3aef5e..69312bb565d 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 @@ -18,7 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; -import { FilterContainer } from './containers'; +import FilterContainer from './FilterContainer'; +import FilterHeader from './FilterHeader'; import SortingFilter from './SortingFilter'; import CoverageRating from '../../../components/ui/CoverageRating'; import { getCoverageRatingLabel, getCoverageRatingAverageValue } from '../../../helpers/ratings'; @@ -32,7 +33,7 @@ export default class CoverageFilter extends React.PureComponent { property = 'coverage'; - renderOption = (option, selected) => { + renderOption (option, selected) { return ( ); - }; - - renderSort = () => { - return ( - - ); - }; + } - getFacetValueForOption = (facet, option) => { + 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]]; - }; - - getOptions = () => [1, 2, 3, 4, 5]; - - renderName = () => 'Coverage'; + } render () { return ( + organization={this.props.organization} + getFacetValueForOption={this.getFacetValueForOption} + highlightUnder={1} + header={ + + + + }/> ); } } 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 94e47088095..48dbc3331cd 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 @@ -18,7 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; -import { FilterContainer } from './containers'; +import FilterContainer from './FilterContainer'; +import FilterHeader from './FilterHeader'; import SortingFilter from './SortingFilter'; import DuplicationsRating from '../../../components/ui/DuplicationsRating'; import { @@ -35,7 +36,7 @@ export default class DuplicationsFilter extends React.PureComponent { property = 'duplications'; - renderOption = (option, selected) => { + renderOption (option, selected) { return ( ); - }; - - renderSort = () => { - return ( - - ); - }; + } - getFacetValueForOption = (facet, option) => { + 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]]; - }; - - getOptions = () => [1, 2, 3, 4, 5]; - - renderName = () => 'Duplications'; + } render () { return ( - + + + + }/> ); } } diff --git a/server/sonar-web/src/main/js/apps/projects/filters/Filter.js b/server/sonar-web/src/main/js/apps/projects/filters/Filter.js index 7e407cb23c2..7008f2bc86f 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/Filter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/Filter.js @@ -26,21 +26,25 @@ import { translate } from '../../../helpers/l10n'; export default class Filter extends React.PureComponent { static propTypes = { - value: React.PropTypes.any, property: React.PropTypes.string.isRequired, - getOptions: React.PropTypes.func.isRequired, + options: React.PropTypes.array.isRequired, + query: React.PropTypes.object.isRequired, + renderOption: React.PropTypes.func.isRequired, + + value: React.PropTypes.any, + facet: React.PropTypes.object, maxFacetValue: React.PropTypes.number, optionClassName: React.PropTypes.string, - - renderName: React.PropTypes.func.isRequired, - renderOption: React.PropTypes.func.isRequired, - renderFooter: React.PropTypes.func, - renderSort: React.PropTypes.func, + isFavorite: React.PropTypes.bool, + organization: React.PropTypes.object, getFacetValueForOption: React.PropTypes.func, halfWidth: React.PropTypes.bool, - highlightUnder: React.PropTypes.number + highlightUnder: React.PropTypes.number, + + header: React.PropTypes.object, + footer: React.PropTypes.object }; static defaultProps = { @@ -74,20 +78,10 @@ export default class Filter extends React.PureComponent { return getFilterUrl(this.props, { [property]: urlOption }); } - renderHeader () { - return ( -
- {this.props.renderName()} - {this.props.renderSort && this.props.renderSort()} -
- ); - } - renderOptionBar (facetValue) { if (facetValue == null || !this.props.maxFacetValue) { return null; } - return (
0) { return (
@@ -149,23 +143,12 @@ export default class Filter extends React.PureComponent { } } - renderFooter () { - if (!this.props.renderFooter) { - return null; - } - return ( -
- {this.props.renderFooter()} -
- ); - } - render () { return (
- {this.renderHeader()} + {this.props.header} {this.renderOptions()} - {this.renderFooter()} + {this.props.footer}
); } diff --git a/server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.js b/server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.js new file mode 100644 index 00000000000..e70b9b5904e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.js @@ -0,0 +1,32 @@ +/* + * 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 { connect } from 'react-redux'; +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) +}); +export default connect(mapStateToProps)(Filter); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/FilterHeader.js b/server/sonar-web/src/main/js/apps/projects/filters/FilterHeader.js new file mode 100644 index 00000000000..ee98c26230b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/FilterHeader.js @@ -0,0 +1,39 @@ +/* + * 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 = { + name: string, + children?: {} +}; + +export default class FilterHeader extends React.PureComponent { + props: Props; + + render () { + return ( +
+ {this.props.name} + {this.props.children} +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.js index c39cf006769..b73200036cc 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.js @@ -18,7 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; -import { FilterContainer } from './containers'; +import FilterContainer from './FilterContainer'; +import FilterHeader from './FilterHeader'; import SortingFilter from './SortingFilter'; import Rating from '../../../components/ui/Rating'; @@ -31,46 +32,39 @@ export default class IssuesFilter extends React.PureComponent { organization: React.PropTypes.object }; - renderOption = (option, selected) => { + renderOption (option, selected) { return ( {option > 1 && option < 5 && and worse} ); - }; - - renderSort = () => { - return ( - - ); - }; + } - getFacetValueForOption = (facet, option) => { + getFacetValueForOption (facet, option) { return facet[option]; - }; - - getOptions = () => [1, 2, 3, 4, 5]; - - renderName = () => this.props.name; + } render () { return ( + organization={this.props.organization} + getFacetValueForOption={this.getFacetValueForOption} + highlightUnder={1} + header={ + + + + }/> ); } } diff --git a/server/sonar-web/src/main/js/apps/projects/filters/LanguageFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/LanguageFilter.js deleted file mode 100644 index d7cdb3947c3..00000000000 --- a/server/sonar-web/src/main/js/apps/projects/filters/LanguageFilter.js +++ /dev/null @@ -1,73 +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 sortBy from 'lodash/sortBy'; -import { - FilterContainer, - LanguageFilterFooterContainer, - LanguageFilterOptionContainer -} from './containers'; - -export default class LanguageFilter extends React.PureComponent { - static propTypes = { - query: React.PropTypes.object.isRequired, - isFavorite: React.PropTypes.bool, - organization: React.PropTypes.object - }; - - property = 'languages'; - - renderOption = option => { - return ; - }; - - getSortedOptions (facet) { - return sortBy(Object.keys(facet), [option => -facet[option]]); - } - - renderFooter = () => ( - - ); - - getFacetValueForOption = (facet, option) => facet[option]; - - getOptions = facet => facet ? this.getSortedOptions(facet) : []; - - renderName = () => 'Languages'; - - render () { - return ( - - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/LanguageFilterFooter.js b/server/sonar-web/src/main/js/apps/projects/filters/LanguageFilterFooter.js deleted file mode 100644 index 12b8058bbea..00000000000 --- a/server/sonar-web/src/main/js/apps/projects/filters/LanguageFilterFooter.js +++ /dev/null @@ -1,61 +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 Select from 'react-select'; -import difference from 'lodash/difference'; -import { getFilterUrl } from './utils'; -import { translate } from '../../../helpers/l10n'; - -export default class LanguageFilterFooter extends React.Component { - static propTypes = { - property: React.PropTypes.string.isRequired, - query: React.PropTypes.object.isRequired, - languages: React.PropTypes.object, - value: React.PropTypes.any, - facet: React.PropTypes.object - } - - handleLanguageChange = ({ value }) => { - const urlOptions = (this.props.value || []).concat(value).join(','); - const path = getFilterUrl(this.props, { [this.props.property]: urlOptions }); - this.props.router.push(path); - } - - getOptions () { - const { languages, facet } = this.props; - let options = Object.keys(languages); - if (facet) { - options = difference(options, Object.keys(facet)); - } - return options.map(key => ({ label: languages[key].name, value: key })); - } - - render () { - return ( - +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterOption.js b/server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterOption.js new file mode 100644 index 00000000000..af658f6834d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterOption.js @@ -0,0 +1,33 @@ +/* + * 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 { translate } from '../../../helpers/l10n'; + +export default class SearchableFilterOption extends React.PureComponent { + static propTypes = { + optionKey: React.PropTypes.string.isRequired, + option: React.PropTypes.object + }; + + render () { + const optionName = this.props.option ? this.props.option.name : this.props.optionKey; + return {this.props.optionKey !== '' ? optionName : translate('unknown')}; + } +} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.js index f84b730cd92..61120c1a245 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.js @@ -22,11 +22,6 @@ import IssuesFilter from './IssuesFilter'; export default class SecurityFilter extends React.Component { render () { - return ( - - ); + return ; } } 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 9b920f50c22..65e61a2c4fb 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 @@ -18,7 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; -import { FilterContainer } from './containers'; +import FilterContainer from './FilterContainer'; +import FilterHeader from './FilterHeader'; import SortingFilter from './SortingFilter'; import SizeRating from '../../../components/ui/SizeRating'; import { translate } from '../../../helpers/l10n'; @@ -33,7 +34,7 @@ export default class SizeFilter extends React.PureComponent { property = 'size'; - renderOption = (option, selected) => { + renderOption (option, selected) { return ( @@ -42,7 +43,7 @@ export default class SizeFilter extends React.PureComponent { ); - }; + } renderSort = () => { return ( @@ -56,7 +57,7 @@ export default class SizeFilter extends React.PureComponent { ); }; - getFacetValueForOption = (facet, option) => { + getFacetValueForOption (facet, option) { const map = [ '*-1000.0', '1000.0-10000.0', @@ -65,25 +66,30 @@ export default class SizeFilter extends React.PureComponent { '500000.0-*' ]; return facet[map[option - 1]]; - }; - - getOptions = () => [1, 2, 3, 4, 5]; - - renderName = () => 'Size'; + } render () { return ( + organization={this.props.organization} + getFacetValueForOption={this.getFacetValueForOption} + highlightUnder={1} + header={ + + + + }/> ); } } 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 index 7bf9cd829f8..80030468909 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/SortingFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/SortingFilter.js @@ -32,8 +32,7 @@ export default class SortingFilter extends React.PureComponent { sortDesc: React.PropTypes.oneOf(['left', 'right']), leftText: React.PropTypes.string, rightText: React.PropTypes.string - } - + }; static defaultProps = { sortDesc: 'left', leftText: translate('worst'), @@ -72,19 +71,23 @@ export default class SortingFilter extends React.PureComponent { const { leftText, rightText } = this.props; return ( -
- {translate('projects.sort_list')} -
- {leftText} - {rightText} -
+
+ {translate('projects.sort_list')} +
+ + {leftText} + + + {rightText} +
+
); } } diff --git a/server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.js new file mode 100644 index 00000000000..50463304e54 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.js @@ -0,0 +1,119 @@ +/* + * 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 debounce from 'lodash/debounce'; +import difference from 'lodash/difference'; +import sortBy from 'lodash/sortBy'; +import Filter from './Filter'; +import FilterHeader from './FilterHeader'; +import SearchableFilterFooter from './SearchableFilterFooter'; +import SearchableFilterOption from './SearchableFilterOption'; +import { searchProjectTags } from '../../../api/components'; + +type Props = { + query: {}, + router: { push: ({ pathname: string, query?: {} }) => void }, + value?: Array, + facet?: {}, + isFavorite?: boolean, + organization?: {}, + maxFacetValue?: number, +}; + +type State = { + isLoading: boolean, + search: string, + tags: Array +}; + +const PAGE_SIZE = 20; + +export default class TagsFilter extends React.PureComponent { + getSearchOptions: () => [{ label: string, value: string }]; + props: Props; + state: State = { + isLoading: false, + search: '', + tags: [] + }; + property = 'tags'; + + constructor (props: Props) { + super(props); + this.handleSearch = debounce(this.handleSearch.bind(this), 250); + } + + renderOption = (option: string) => ; + + getSearchOptions (facet: {}, tags: Array) { + let tagsCopy = [...tags]; + if (facet) { + tagsCopy = difference(tagsCopy, Object.keys(facet)); + } + return tagsCopy.map(tag => ({ label: tag, value: tag })); + } + + handleSearch = (search?: string) => { + if (search !== this.state.search) { + search = search || ''; + this.setState({ search, isLoading: true }); + searchProjectTags({ q: search, ps: PAGE_SIZE }).then(result => { + this.setState({ isLoading: false, tags: result.tags }); + }); + } + }; + + getSortedOptions (facet: {} = {}) { + return sortBy(Object.keys(facet), [option => -facet[option], option => option]); + } + + getFacetValueForOption = (facet: {}, option: string) => facet[option]; + + render () { + return ( + } + footer={ + + }/> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/TagsFilterContainer.js b/server/sonar-web/src/main/js/apps/projects/filters/TagsFilterContainer.js new file mode 100644 index 00000000000..8c841346a42 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/TagsFilterContainer.js @@ -0,0 +1,33 @@ +/* + * 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 { connect } from 'react-redux'; +import { withRouter } from 'react-router'; +import TagsFilter from './TagsFilter'; +import { + getProjectsAppFacetByProperty, + getProjectsAppMaxFacetValue +} from '../../../store/rootReducer'; + +const mapStateToProps = (state, ownProps) => ({ + value: ownProps.query['tags'], + facet: getProjectsAppFacetByProperty(state, 'tags'), + maxFacetValue: getProjectsAppMaxFacetValue(state) +}); +export default connect(mapStateToProps)(withRouter(TagsFilter)); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguageFilterFooter-test.js b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguageFilterFooter-test.js deleted file mode 100644 index 1c90902fe30..00000000000 --- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguageFilterFooter-test.js +++ /dev/null @@ -1,62 +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 LanguageFilterFooter from '../LanguageFilterFooter'; - -const languages = { - java: { - key: 'java', - name: 'Java' - }, - cs: { - key: 'cs', - name: 'C#' - }, - js: { - key: 'js', - name: 'JavaScript' - }, - flex: { - key: 'flex', - name: 'Flex' - }, - php: { - key: 'php', - name: 'PHP' - }, - py: { - key: 'py', - name: 'Python' - } -}; -const facet = { java: 39, cs: 4, js: 1 }; - -it('should render the languages without the ones in the facet', () => { - const wrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - expect(wrapper.find('Select').props().options.length).toBe(3); -}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguagesFilter-test.js b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguagesFilter-test.js new file mode 100644 index 00000000000..c508af7616a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguagesFilter-test.js @@ -0,0 +1,76 @@ +/* + * 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 LanguagesFilter from '../LanguagesFilter'; + +const languages = { + java: { + key: 'java', + name: 'Java' + }, + cs: { + key: 'cs', + name: 'C#' + }, + js: { + key: 'js', + name: 'JavaScript' + }, + flex: { + key: 'flex', + name: 'Flex' + }, + php: { + key: 'php', + name: 'PHP' + }, + py: { + key: 'py', + name: 'Python' + } +}; +const languagesFacet = { java: 39, cs: 4, js: 1 }; +const fakeRouter = { push: () => {} }; + +it('should render the languages without the ones in the facet', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render the languages facet with the selected languages', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('Filter').shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterFooter-test.js b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterFooter-test.js new file mode 100644 index 00000000000..e62d8f89299 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterFooter-test.js @@ -0,0 +1,58 @@ +/* + * 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 SearchableFilterFooter from '../SearchableFilterFooter'; + +const languageOptions = [ + { label: 'Flex', value: 'flex' }, + { label: 'PHP', value: 'php' }, + { label: 'Python', value: 'py' } +]; +const tagOptions = [ + { label: 'lang', value: 'lang' }, + { label: 'sonar', value: 'sonar' }, + { label: 'csharp', value: 'csharp' } +]; +const fakeRouter = { push: () => {} }; + +it('should render the languages without the ones in the facet', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('Select').props().options.length).toBe(3); +}); + +it('should render the tags without the ones in the facet', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('Select').props().options.length).toBe(3); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/TagsFilter-test.js b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/TagsFilter-test.js new file mode 100644 index 00000000000..26f8eda70d1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/TagsFilter-test.js @@ -0,0 +1,51 @@ +/* + * 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 TagsFilter from '../TagsFilter'; + +const tags = ['lang', 'sonar', 'csharp', 'dotnet', 'it', 'net']; +const tagsFacet = { lang: 4, sonar: 3, csharp: 1 }; +const fakeRouter = { push: () => {} }; + +it('should render the tags without the ones in the facet', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + wrapper.setState({ tags }); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render the tags facet with the selected tags', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('Filter').shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/LanguageFilterFooter-test.js.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/LanguageFilterFooter-test.js.snap deleted file mode 100644 index 22d352f4ca5..00000000000 --- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/LanguageFilterFooter-test.js.snap +++ /dev/null @@ -1,58 +0,0 @@ -exports[`test should render the languages without the ones in the facet 1`] = ` - +
+`; + +exports[`test should render the tags without the ones in the facet 1`] = ` +
+