diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-03-28 11:58:01 +0200 |
---|---|---|
committer | Grégoire Aubert <gregaubert@users.noreply.github.com> | 2017-03-31 10:29:27 +0200 |
commit | 4b2cf358d38ad0e1931246530d3ff6ada7467079 (patch) | |
tree | 6cc4745b769dc225cf26cbf76801ad0b303d17f7 /server/sonar-web/src/main/js/apps/users | |
parent | e4af315006ebfb8e6a606d2a29d1aff5706b7452 (diff) | |
download | sonarqube-4b2cf358d38ad0e1931246530d3ff6ada7467079.tar.gz sonarqube-4b2cf358d38ad0e1931246530d3ff6ada7467079.zip |
SONAR-8992 Add a member to an organization
Diffstat (limited to 'server/sonar-web/src/main/js/apps/users')
11 files changed, 616 insertions, 1 deletions
diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersSearch.js b/server/sonar-web/src/main/js/apps/users/components/UsersSearch.js index 5c17f90f8cf..1b8290cd35d 100644 --- a/server/sonar-web/src/main/js/apps/users/components/UsersSearch.js +++ b/server/sonar-web/src/main/js/apps/users/components/UsersSearch.js @@ -56,7 +56,7 @@ export default class UsersSearch extends React.PureComponent { render() { const { query } = this.state; const inputClassName = classNames('search-box-input', { - touched: query != null && query !== '' && query.length < 2 + touched: query != null && query.length === 1 }); return ( <div className="panel panel-vertical bordered-bottom spacer-bottom"> diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.css b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.css new file mode 100644 index 00000000000..c8780833809 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.css @@ -0,0 +1,13 @@ +.Select-big .Select-control { + padding-top: 4px; + padding-bottom: 4px; +} + +.Select-big .Select-placeholder { + margin-top: 4px; + margin-bottom: 4px; +} + +.Select-big .Select-value-label { + margin-top: 5px; +} diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js new file mode 100644 index 00000000000..5026720f2b2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js @@ -0,0 +1,102 @@ +/* + * 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 { debounce } from 'lodash'; +import UsersSelectSearchOption from './UsersSelectSearchOption'; +import UsersSelectSearchValue from './UsersSelectSearchValue'; +import './UsersSelectSearch.css'; + +export type Option = { + login: string, + name: string, + email?: string, + avatar?: string, + groupCount?: number +}; + +type Props = { + selectedUser?: Option, + excludedUsers: Array<string>, + searchUsers: (string, number) => Promise<*>, + handleValueChange: (Option) => void +}; + +type State = { + searchResult: Array<Option>, + isLoading: boolean, + search: string +}; + +const LIST_SIZE = 10; + +export default class UsersSelectSearch extends React.PureComponent { + props: Props; + state: State; + + constructor(props: Props) { + super(props); + this.handleSearch = debounce(this.handleSearch); + this.state = { searchResult: [], isLoading: false, search: '' }; + } + + componentDidMount() { + this.handleSearch(this.state.search); + } + + componentWillReceiveProps(nextProps: Props) { + if (this.props.excludedUsers !== nextProps.excludedUsers) { + this.handleSearch(this.state.search); + } + } + + filterSearchResult = ({ users }: { users: Array<Option> }) => + users.filter(user => !this.props.excludedUsers.includes(user.login)).slice(0, LIST_SIZE); + + handleSearch = (search: string) => { + this.setState({ isLoading: true, search }); + this.props.searchUsers(search, Math.min(this.props.excludedUsers.length + LIST_SIZE, 500)) + .then(this.filterSearchResult) + .then(searchResult => { + this.setState({ isLoading: false, searchResult }); + }); + }; + + render() { + return ( + <Select + className="Select-big" + options={this.state.searchResult} + isLoading={this.state.isLoading} + optionComponent={UsersSelectSearchOption} + valueComponent={UsersSelectSearchValue} + onChange={this.props.handleValueChange} + onInputChange={this.handleSearch} + value={this.props.selectedUser} + placeholder="" + labelKey="name" + valueKey="login" + clearable={false} + searchable={true} + /> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchOption.js b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchOption.js new file mode 100644 index 00000000000..1861cb83d0c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchOption.js @@ -0,0 +1,74 @@ +/* + * 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 Avatar from '../../../components/ui/Avatar'; +import type { Option } from './UsersSelectSearch'; + +type Props = { + option: Option, + children?: Element | Text, + className?: string, + isFocused?: boolean, + onFocus: (Option, MouseEvent) => void, + onSelect: (Option, MouseEvent) => void +}; + +const AVATAR_SIZE: number = 20; + +export default class UsersSelectSearchOption 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 user = this.props.option; + return ( + <div + className={this.props.className} + onMouseDown={this.handleMouseDown} + onMouseEnter={this.handleMouseEnter} + onMouseMove={this.handleMouseMove} + title={user.name} + > + <div className="little-spacer-bottom little-spacer-top"> + <Avatar hash={user.avatar} email={user.email} size={AVATAR_SIZE} /> + <strong className="spacer-left">{user.login}</strong> + <span className="note little-spacer-left">{this.props.children}</span> + </div> + </div> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchValue.js b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchValue.js new file mode 100644 index 00000000000..b0939f127a9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchValue.js @@ -0,0 +1,48 @@ +/* + * 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 Avatar from '../../../components/ui/Avatar'; +import type { Option } from './UsersSelectSearch'; + +type Props = { + value: Option, + children?: Element | Text +}; + +const AVATAR_SIZE: number = 20; + +export default class UsersSelectSearchValue extends React.PureComponent { + props: Props; + + render() { + const user = this.props.value; + return ( + <div className="Select-value" title={user ? user.name : ''}> + {user && user.login && + <div className="Select-value-label"> + <Avatar hash={user.avatar} email={user.email} size={AVATAR_SIZE} /> + <strong className="spacer-left">{user.login}</strong> + <span className="note little-spacer-left">{this.props.children}</span> + </div>} + </div> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/UsersSelectSearch-test.js b/server/sonar-web/src/main/js/apps/users/components/__tests__/UsersSelectSearch-test.js new file mode 100644 index 00000000000..cfc0481145c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/UsersSelectSearch-test.js @@ -0,0 +1,54 @@ +/* + * 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 UsersSelectSearch from '../UsersSelectSearch'; + +const selectedUser = { + login: 'admin', + name: 'Administrator', + avatar: '7daf6c79d4802916d83f6266e24850af' +}; +const users = [ + { login: 'admin', name: 'Administrator', email: 'admin@admin.ch' }, + { login: 'test', name: 'Tester', email: 'tester@testing.ch' }, + { login: 'foo', name: 'Foo Bar', email: 'foo@bar.ch' } +]; +const excludedUsers = ['admin']; +const onSearch = jest.fn(() => { + return Promise.resolve(users); +}); +const onChange = jest.fn(); + +it('should render correctly', () => { + const wrapper = shallow( + <UsersSelectSearch + selectedUser={selectedUser} + excludedUsers={excludedUsers} + isLoading={false} + handleValueChange={onChange} + searchUsers={onSearch} + /> + ); + expect(wrapper).toMatchSnapshot(); + const searchResult = wrapper.instance().filterSearchResult({ users }); + expect(searchResult).toMatchSnapshot(); + expect(wrapper.setState({ searchResult })).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/UsersSelectSearchOption-test.js b/server/sonar-web/src/main/js/apps/users/components/__tests__/UsersSelectSearchOption-test.js new file mode 100644 index 00000000000..3023f73918c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/UsersSelectSearchOption-test.js @@ -0,0 +1,48 @@ +/* + * 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 UsersSelectSearchOption from '../UsersSelectSearchOption'; + +const user = { + login: 'admin', + name: 'Administrator', + avatar: '7daf6c79d4802916d83f6266e24850af' +}; + +const user2 = { + login: 'admin', + name: 'Administrator', + email: 'admin@admin.ch' +}; + +it('should render correctly without all parameters', () => { + const wrapper = shallow( + <UsersSelectSearchOption option={user}>{user.name}</UsersSelectSearchOption> + ); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render correctly with email instead of hash', () => { + const wrapper = shallow( + <UsersSelectSearchOption option={user2}>{user.name}</UsersSelectSearchOption> + ); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/UsersSelectSearchValue-test.js b/server/sonar-web/src/main/js/apps/users/components/__tests__/UsersSelectSearchValue-test.js new file mode 100644 index 00000000000..357365a0826 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/UsersSelectSearchValue-test.js @@ -0,0 +1,53 @@ +/* + * 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 UsersSelectSearchValue from '../UsersSelectSearchValue'; + +const user = { + login: 'admin', + name: 'Administrator', + avatar: '7daf6c79d4802916d83f6266e24850af' +}; + +const user2 = { + login: 'admin', + name: 'Administrator', + email: 'admin@admin.ch' +}; + +it('should render correctly with a user', () => { + const wrapper = shallow( + <UsersSelectSearchValue value={user}>{user.name}</UsersSelectSearchValue> + ); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render correctly with email instead of hash', () => { + const wrapper = shallow( + <UsersSelectSearchValue value={user2}>{user2.name}</UsersSelectSearchValue> + ); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render correctly without value', () => { + const wrapper = shallow(<UsersSelectSearchValue />); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearch-test.js.snap b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearch-test.js.snap new file mode 100644 index 00000000000..e4f6d80595e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearch-test.js.snap @@ -0,0 +1,131 @@ +exports[`test should render correctly 1`] = ` +<Select + addLabelText="Add \"{label}\"?" + arrowRenderer={[Function]} + autosize={true} + backspaceRemoves={true} + backspaceToRemoveMessage="Press backspace to remove {label}" + className="Select-big" + 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="name" + matchPos="any" + matchProp="any" + menuBuffer={0} + menuRenderer={[Function]} + multi={false} + noResultsText="No results found" + onBlurResetsInput={true} + onChange={[Function]} + onCloseResetsInput={true} + onInputChange={[Function]} + openAfterFocus={false} + optionComponent={[Function]} + options={Array []} + pageSize={5} + placeholder="" + required={false} + scrollMenuIntoView={true} + searchable={true} + simpleValue={false} + tabSelectsValue={true} + value={ + Object { + "avatar": "7daf6c79d4802916d83f6266e24850af", + "login": "admin", + "name": "Administrator", + } + } + valueComponent={[Function]} + valueKey="login" /> +`; + +exports[`test should render correctly 2`] = ` +Array [ + Object { + "email": "tester@testing.ch", + "login": "test", + "name": "Tester", + }, + Object { + "email": "foo@bar.ch", + "login": "foo", + "name": "Foo Bar", + }, +] +`; + +exports[`test should render correctly 3`] = ` +<Select + addLabelText="Add \"{label}\"?" + arrowRenderer={[Function]} + autosize={true} + backspaceRemoves={true} + backspaceToRemoveMessage="Press backspace to remove {label}" + className="Select-big" + 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="name" + matchPos="any" + matchProp="any" + menuBuffer={0} + menuRenderer={[Function]} + multi={false} + noResultsText="No results found" + onBlurResetsInput={true} + onChange={[Function]} + onCloseResetsInput={true} + onInputChange={[Function]} + openAfterFocus={false} + optionComponent={[Function]} + options={ + Array [ + Object { + "email": "tester@testing.ch", + "login": "test", + "name": "Tester", + }, + Object { + "email": "foo@bar.ch", + "login": "foo", + "name": "Foo Bar", + }, + ] + } + pageSize={5} + placeholder="" + required={false} + scrollMenuIntoView={true} + searchable={true} + simpleValue={false} + tabSelectsValue={true} + value={ + Object { + "avatar": "7daf6c79d4802916d83f6266e24850af", + "login": "admin", + "name": "Administrator", + } + } + valueComponent={[Function]} + valueKey="login" /> +`; diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchOption-test.js.snap b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchOption-test.js.snap new file mode 100644 index 00000000000..92c64c9030f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchOption-test.js.snap @@ -0,0 +1,45 @@ +exports[`test should render correctly with email instead of hash 1`] = ` +<div + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseMove={[Function]} + title="Administrator"> + <div + className="little-spacer-bottom little-spacer-top"> + <Connect(Avatar) + email="admin@admin.ch" + size={20} /> + <strong + className="spacer-left"> + admin + </strong> + <span + className="note little-spacer-left"> + Administrator + </span> + </div> +</div> +`; + +exports[`test should render correctly without all parameters 1`] = ` +<div + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseMove={[Function]} + title="Administrator"> + <div + className="little-spacer-bottom little-spacer-top"> + <Connect(Avatar) + hash="7daf6c79d4802916d83f6266e24850af" + size={20} /> + <strong + className="spacer-left"> + admin + </strong> + <span + className="note little-spacer-left"> + Administrator + </span> + </div> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchValue-test.js.snap b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchValue-test.js.snap new file mode 100644 index 00000000000..2e5a2ab9cd6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchValue-test.js.snap @@ -0,0 +1,47 @@ +exports[`test should render correctly with a user 1`] = ` +<div + className="Select-value" + title="Administrator"> + <div + className="Select-value-label"> + <Connect(Avatar) + hash="7daf6c79d4802916d83f6266e24850af" + size={20} /> + <strong + className="spacer-left"> + admin + </strong> + <span + className="note little-spacer-left"> + Administrator + </span> + </div> +</div> +`; + +exports[`test should render correctly with email instead of hash 1`] = ` +<div + className="Select-value" + title="Administrator"> + <div + className="Select-value-label"> + <Connect(Avatar) + email="admin@admin.ch" + size={20} /> + <strong + className="spacer-left"> + admin + </strong> + <span + className="note little-spacer-left"> + Administrator + </span> + </div> +</div> +`; + +exports[`test should render correctly without value 1`] = ` +<div + className="Select-value" + title="" /> +`; |