From e4af315006ebfb8e6a606d2a29d1aff5706b7452 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Fri, 24 Mar 2017 11:26:27 +0100 Subject: [PATCH] SONAR-8990 Add the list of members of an organization --- .../organizations/components/MembersList.js | 45 +++++++++ .../components/MembersListItem.js | 54 ++++++++++ .../components/OrganizationMembers.js | 76 ++++++++++++++ .../OrganizationMembersContainer.js | 42 ++++++++ .../organizations/components/PageHeader.js | 48 +++++++++ .../components/__tests__/MembersList-test.js | 38 +++++++ .../__tests__/MembersListItem-test.js | 46 +++++++++ .../__tests__/OrganizationMembers-test.js | 57 +++++++++++ .../components/__tests__/PageHeader-test.js | 45 +++++++++ .../__snapshots__/MembersList-test.js.snap | 37 +++++++ .../MembersListItem-test.js.snap | 37 +++++++ .../OrganizationMembers-test.js.snap | 77 +++++++++++++++ .../__snapshots__/PageHeader-test.js.snap | 42 ++++++++ .../navigation/OrganizationNavigation.js | 5 + .../OrganizationNavigation-test.js.snap | 27 +++++ .../src/main/js/apps/organizations/routes.js | 5 + .../js/apps/users/components/UsersSearch.js | 82 +++++++++++++++ .../components/__tests__/UserSearch-test.js | 41 ++++++++ .../__snapshots__/UserSearch-test.js.snap | 99 +++++++++++++++++++ .../controls/__tests__/ListFooter-test.js | 8 +- .../src/main/js/components/ui/Avatar.js | 3 +- .../js/components/ui/__tests__/Avatar-test.js | 16 +++ .../src/main/less/components/page.less | 10 +- .../src/main/less/components/search.less | 9 ++ .../resources/org/sonar/l10n/core.properties | 10 ++ 25 files changed, 956 insertions(+), 3 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/MembersList.js create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.js create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/PageHeader.js create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersList-test.js create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.js create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.js create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/PageHeader-test.js create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersList-test.js.snap create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/PageHeader-test.js.snap create mode 100644 server/sonar-web/src/main/js/apps/users/components/UsersSearch.js create mode 100644 server/sonar-web/src/main/js/apps/users/components/__tests__/UserSearch-test.js create mode 100644 server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserSearch-test.js.snap diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersList.js b/server/sonar-web/src/main/js/apps/organizations/components/MembersList.js new file mode 100644 index 00000000000..156f8262e27 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/MembersList.js @@ -0,0 +1,45 @@ +/* + * 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 MembersListItem from './MembersListItem'; +import type { Member } from '../../../store/organizationsMembers/actions'; +import type { Organization } from '../../../store/organizations/duck'; + +type Props = { + members: Array, + organization?: Organization +}; + +export default class MembersList extends React.PureComponent { + props: Props; + + render() { + return ( + + + {this.props.members.map(member => ( + + ))} + +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js b/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js new file mode 100644 index 00000000000..496d950a8e1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.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. + */ +//@flow +import React from 'react'; +import Avatar from '../../../components/ui/Avatar'; +import { translate } from '../../../helpers/l10n'; +import { formatMeasure } from '../../../helpers/measures'; +import type { Member } from '../../../store/organizationsMembers/actions'; +import type { Organization } from '../../../store/organizations/duck'; + +type Props = { + member: Member, + organization: Organization +}; + +const AVATAR_SIZE: number = 36; + +export default class MembersListItem extends React.PureComponent { + props: Props; + + render() { + const { member, organization } = this.props; + return ( + + + + + {member.name} + {organization.canAdmin && + + {translate('organization.members.x_group(s)', formatMeasure(member.groupCount, 'INT'))} + + } + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js new file mode 100644 index 00000000000..b3e252c0669 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.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. + */ +// @flow +import React from 'react'; +import PageHeader from './PageHeader'; +import MembersList from './MembersList'; +import UsersSearch from '../../users/components/UsersSearch'; +import ListFooter from '../../../components/controls/ListFooter'; +import type { Organization } from '../../../store/organizations/duck'; +import type { Member } from '../../../store/organizationsMembers/actions'; + +type Props = { + members: Array, + status: { loading?: boolean, total?: number, pageIndex?: number, query?: string }, + organization: Organization, + fetchOrganizationMembers: (organizationKey: string, query?: string) => void, + fetchMoreOrganizationMembers: (organizationKey: string, query?: string) => void, +}; + +export default class OrganizationMembers extends React.PureComponent { + props: Props; + + componentDidMount() { + const notLoadedYet = this.props.members.length < 1 || this.props.status.query != null; + if (!this.props.loading && notLoadedYet) { + this.handleSearchMembers(); + } + } + + handleSearchMembers = (query?: string) => { + this.props.fetchOrganizationMembers(this.props.organization.key, query); + }; + + handleLoadMoreMembers = () => { + this.props.fetchMoreOrganizationMembers(this.props.organization.key, this.props.status.query); + }; + + addMember() { + // TODO Not yet implemented + } + + render() { + const { organization, status, members } = this.props; + return ( +
+ + + + {status.total != null && + } +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.js new file mode 100644 index 00000000000..27dd084b063 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.js @@ -0,0 +1,42 @@ +/* + * 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 OrganizationMembers from './OrganizationMembers'; +import { + getOrganizationByKey, + getOrganizationMembersLogins, + getUsersByLogins, + getOrganizationMembersState +} from '../../../store/rootReducer'; +import { fetchOrganizationMembers, fetchMoreOrganizationMembers } from '../actions'; + +const mapStateToProps = (state, ownProps) => { + const { organizationKey } = ownProps.params; + const memberLogins = getOrganizationMembersLogins(state, organizationKey); + return { + members: getUsersByLogins(state, memberLogins), + organization: getOrganizationByKey(state, organizationKey), + status: getOrganizationMembersState(state, organizationKey) + }; +}; + +export default connect(mapStateToProps, { fetchOrganizationMembers, fetchMoreOrganizationMembers })( + OrganizationMembers +); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/PageHeader.js b/server/sonar-web/src/main/js/apps/organizations/components/PageHeader.js new file mode 100644 index 00000000000..7ea0ae70c4d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/PageHeader.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 { formatMeasure } from '../../../helpers/measures'; +import { translate } from '../../../helpers/l10n'; + +type Props = { + loading: boolean, + total?: number, + children?: {} +}; + +export default class PageHeader extends React.PureComponent { + props: Props; + + render() { + return ( +
+ {this.props.loading && } + {this.props.children} + {this.props.total != null && + + {formatMeasure(this.props.total, 'INT')} + {' '} + {translate('organization.members.member(s)')} + } +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersList-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersList-test.js new file mode 100644 index 00000000000..d96f62874d5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersList-test.js @@ -0,0 +1,38 @@ +/* + * 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 MembersList from '../MembersList'; + +const organization = { key: 'foo', name: 'Foo' }; +const members = [ + { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }, + { login: 'john', name: 'John Doe', avatar: '7daf6c79d4802916d83f6266e24850af', groupCount: 1 } +]; + +it('should render a list of members of an organization', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.js new file mode 100644 index 00000000000..d0cb52edbca --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.js @@ -0,0 +1,46 @@ +/* + * 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 MembersListItem from '../MembersListItem'; + +const organization = { key: 'foo', name: 'Foo' }; +const admin = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }; +const john = { login: 'john', name: 'John Doe', avatar: '7daf6c79d4802916d83f6266e24850af', groupCount: 1 }; + +it('should not render actions and groups for non admin', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render actions and groups for admin', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.js new file mode 100644 index 00000000000..461722d4b3e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.js @@ -0,0 +1,57 @@ +/* + * 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 OrganizationMembers from '../OrganizationMembers'; + +const organization = { key: 'foo', name: 'Foo' }; +const members = [ + { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }, + { login: 'john', name: 'John Doe', avatar: '7daf6c79d4802916d83f6266e24850af', groupCount: 1 } +]; +const status = { total: members.length }; +const fetchOrganizationMembers = jest.fn(); +const fetchMoreOrganizationMembers = jest.fn(); + +it('should not render actions for non admin', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render actions for admin', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/PageHeader-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/PageHeader-test.js new file mode 100644 index 00000000000..06f9ef06e90 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/PageHeader-test.js @@ -0,0 +1,45 @@ +/* + * 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 PageHeader from '../PageHeader'; + +it('should render the members page header', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + wrapper.setProps({ loading: true }); + expect(wrapper.find('.spinner')).toMatchSnapshot(); +}); + +it('should render the members page header with the total', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render its children', () => { + const wrapper = shallow( + + children test + + ); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersList-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersList-test.js.snap new file mode 100644 index 00000000000..11a42a6752d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersList-test.js.snap @@ -0,0 +1,37 @@ +exports[`test should render a list of members of an organization 1`] = ` + + + + + +
+`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap new file mode 100644 index 00000000000..debea9c01a0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap @@ -0,0 +1,37 @@ +exports[`test should not render actions and groups for non admin 1`] = ` + + + + + + + John Doe + + + +`; + +exports[`test should render actions and groups for admin 1`] = ` + + + + + + + Admin Istrator + + + + organization.members.x_group(s).3 + + +`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap new file mode 100644 index 00000000000..3af82c13471 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap @@ -0,0 +1,77 @@ +exports[`test should not render actions for non admin 1`] = ` +
+ + + + +
+`; + +exports[`test should render actions for admin 1`] = ` +
+ + + + +
+`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/PageHeader-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/PageHeader-test.js.snap new file mode 100644 index 00000000000..9e31b2215cd --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/PageHeader-test.js.snap @@ -0,0 +1,42 @@ +exports[`test should render its children 1`] = ` +
+ + + children test + + + + 5 + + + organization.members.member(s) + +
+`; + +exports[`test should render the members page header 1`] = ` +
+`; + +exports[`test should render the members page header 2`] = ` + +`; + +exports[`test should render the members page header with the total 1`] = ` +
+ + + 5 + + + organization.members.member(s) + +
+`; diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js index c1ffa729acf..4e221313939 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js @@ -153,6 +153,11 @@ export default class OrganizationNavigation extends React.Component { {translate('projects.page')} +
  • + + {translate('organization.members.page')} + +
  • {organization.canAdmin && this.renderAdministration()} diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap index 02421f1020d..8fbbcd9b6a3 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap @@ -35,6 +35,15 @@ exports[`test admin 1`] = ` projects.page +
  • + + organization.members.page + +
  • +
  • + + organization.members.page + +
  • @@ -190,6 +208,15 @@ exports[`test undeletable org 1`] = ` projects.page +
  • + + organization.members.page + +
  • void +}; + +type State = { + query?: string +}; + +export default class UsersSearch extends React.PureComponent { + props: Props; + state: State = { + query: '' + } + + constructor(props: Props) { + super(props); + this.handleSearch = debounce(this.handleSearch, 250); + } + + handleSearch = (query: string) => { + this.props.onSearch(query); + } + + handleInputChange = ({ target }: { target: HTMLInputElement }) => { + this.setState({ query: target.value }); + if (!target.value || target.value.length >= 2) { + this.handleSearch(target.value); + } + }; + + render() { + const { query } = this.state; + const inputClassName = classNames('search-box-input', { + touched: query != null && query !== '' && query.length < 2 + }); + return ( +
    +
    + + + + {translateWithParameters('select2.tooShort', 2)} + +
    +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/UserSearch-test.js b/server/sonar-web/src/main/js/apps/users/components/__tests__/UserSearch-test.js new file mode 100644 index 00000000000..33a4c3c9b86 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/UserSearch-test.js @@ -0,0 +1,41 @@ +/* + * 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 UsersSearch from '../UsersSearch'; + +const onSearch = jest.fn(); + +it('should render correctly without any search query', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render with a search query', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it('should display a help message when there is less than 2 characters', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + wrapper.setState({ query: 'foo' }); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserSearch-test.js.snap b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserSearch-test.js.snap new file mode 100644 index 00000000000..edc0fee72b3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserSearch-test.js.snap @@ -0,0 +1,99 @@ +exports[`test should display a help message when there is less than 2 characters 1`] = ` +
    +
    + + + + select2.tooShort.2 + +
    +
    +`; + +exports[`test should display a help message when there is less than 2 characters 2`] = ` +
    +
    + + + + select2.tooShort.2 + +
    +
    +`; + +exports[`test should render correctly without any search query 1`] = ` +
    +
    + + + + select2.tooShort.2 + +
    +
    +`; + +exports[`test should render with a search query 1`] = ` +
    +
    + + + + select2.tooShort.2 + +
    +
    +`; diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js b/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js index c8adb510bd5..8d5f42cb719 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js +++ b/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js @@ -22,6 +22,8 @@ import React from 'react'; import ListFooter from '../ListFooter'; import { click } from '../../../helpers/testUtils'; +const loadMore = jest.fn(); + it('should render "3 of 5 shown"', () => { const listFooter = shallow(); expect(listFooter.text()).toContain('x_of_y_shown.3.5'); @@ -32,8 +34,12 @@ it('should not render "show more"', () => { expect(listFooter.find('a').length).toBe(0); }); +it('should not render "show more"', () => { + const listFooter = shallow(); + expect(listFooter.find('a').length).toBe(0); +}); + it('should "show more"', () => { - const loadMore = jest.fn(); const listFooter = shallow(); const link = listFooter.find('a'); expect(link.length).toBe(1); diff --git a/server/sonar-web/src/main/js/components/ui/Avatar.js b/server/sonar-web/src/main/js/components/ui/Avatar.js index 12e6e5500a9..dea1f233288 100644 --- a/server/sonar-web/src/main/js/components/ui/Avatar.js +++ b/server/sonar-web/src/main/js/components/ui/Avatar.js @@ -28,6 +28,7 @@ class Avatar extends React.Component { enableGravatar: React.PropTypes.bool.isRequired, gravatarServerUrl: React.PropTypes.string.isRequired, email: React.PropTypes.string, + hash: React.PropTypes.string, size: React.PropTypes.number.isRequired, className: React.PropTypes.string }; @@ -37,7 +38,7 @@ class Avatar extends React.Component { return null; } - const emailHash = md5.md5((this.props.email || '').toLowerCase()).trim(); + const emailHash = this.props.hash || md5.md5((this.props.email || '').toLowerCase()).trim(); const url = this.props.gravatarServerUrl .replace('{EMAIL_MD5}', emailHash) .replace('{SIZE}', this.props.size * 2); diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js b/server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js index a83df0fa337..d52a47ad4ac 100644 --- a/server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js +++ b/server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js @@ -50,3 +50,19 @@ it('should not render', () => { ); expect(avatar.is('img')).toBe(false); }); + +it('should be able to render with hash only', () => { + const avatar = shallow( + + ); + expect(avatar.is('img')).toBe(true); + expect(avatar.prop('width')).toBe(30); + expect(avatar.prop('height')).toBe(30); + expect(avatar.prop('alt')).toBeUndefined(); + expect(avatar.prop('src')).toBe('http://example.com/7daf6c79d4802916d83f6266e24850af.jpg?s=60'); +}); diff --git a/server/sonar-web/src/main/less/components/page.less b/server/sonar-web/src/main/less/components/page.less index fc40dab1f57..ec4df716077 100644 --- a/server/sonar-web/src/main/less/components/page.less +++ b/server/sonar-web/src/main/less/components/page.less @@ -74,6 +74,8 @@ } .page-header { + position: relative; + .clearfix; margin-bottom: 20px; @@ -82,6 +84,12 @@ top: 3px; margin-left: 8px; } + + .page-totalcount { + position: absolute; + bottom: -50px; + right: 0; + } } .page-title { @@ -176,4 +184,4 @@ width: 260px; } } -} \ No newline at end of file +} diff --git a/server/sonar-web/src/main/less/components/search.less b/server/sonar-web/src/main/less/components/search.less index f76c61c340b..c7da998c101 100644 --- a/server/sonar-web/src/main/less/components/search.less +++ b/server/sonar-web/src/main/less/components/search.less @@ -31,6 +31,15 @@ width: 250px; border: none !important; font-size: @baseFontSize; + + & ~ .note { + opacity: 0; + transition: opacity 0.3s ease; + } + + &.touched ~ .note { + opacity: 1; + } } .search-box-submit { diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index eaeb0a821f5..0d4555c94ba 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1765,6 +1765,12 @@ user.scm_account_already_used=The scm account '{0}' is already used by user(s) : user.login_or_email_used_as_scm_account=Login and email are automatically considered as SCM accounts user.password_cant_be_changed_on_external_auth=Password cannot be changed when external authentication is used +#------------------------------------------------------------------------------ +# +# USERS PAGE +# +#------------------------------------------------------------------------------ +users.create=Create User #------------------------------------------------------------------------------ # @@ -2810,3 +2816,7 @@ organization.name.description=Name of the organization (2 to 64 characters). organization.updated=Organization details have been updated. organization.url=Url organization.url.description=Url of the homepage of the organization. +organization.members.page=Members +organization.members.add=Add member +organization.members.x_group(s)={0} group(s) +organization.members.member(s)=member(s) -- 2.39.5