aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src')
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.js48
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js19
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/MembersPageHeader.js (renamed from server/sonar-web/src/main/js/apps/organizations/components/PageHeader.js)10
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.js55
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js23
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.js36
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.js14
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersPageHeader-test.js (renamed from server/sonar-web/src/main/js/apps/organizations/components/__tests__/PageHeader-test.js)10
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.js47
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.js10
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListHeader-test.js.snap25
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap98
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersPageHeader-test.js.snap (renamed from server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/PageHeader-test.js.snap)19
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.js.snap53
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap16
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js34
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js3
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js5
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.js33
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js4
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap99
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UsersSearch.js8
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js10
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/__tests__/UserSearch-test.js16
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserSearch-test.js.snap140
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearch-test.js.snap4
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js5
-rw-r--r--server/sonar-web/src/main/less/components/page.less6
-rw-r--r--server/sonar-web/src/main/less/init/lists.less8
29 files changed, 577 insertions, 281 deletions
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.js b/server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.js
new file mode 100644
index 00000000000..a609ffce38e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.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 UsersSearch from '../../users/components/UsersSearch';
+import { formatMeasure } from '../../../helpers/measures';
+import { translate } from '../../../helpers/l10n';
+
+type Props = {
+ handleSearch: (query?: string) => void,
+ total?: number
+};
+
+export default class MembersListHeader extends React.PureComponent {
+ props: Props;
+
+ render() {
+ const { total } = this.props;
+ return (
+ <div className="panel panel-vertical bordered-bottom spacer-bottom">
+ <UsersSearch onSearch={this.props.handleSearch} className="display-inline-block" />
+ {total != null &&
+ <span className="pull-right little-spacer-top">
+ <strong>{formatMeasure(total, 'INT')}</strong>
+ {' '}
+ {translate('organization.members.members')}
+ </span>}
+ </div>
+ );
+ }
+}
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
index 3c2f7c8815b..2324b9d4c7f 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js
@@ -20,7 +20,7 @@
//@flow
import React from 'react';
import Avatar from '../../../components/ui/Avatar';
-import { translate } from '../../../helpers/l10n';
+import { translateWithParameters } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import RemoveMemberForm from './forms/RemoveMemberForm';
import ManageMemberGroupsForm from './forms/ManageMemberGroupsForm';
@@ -47,10 +47,13 @@ export default class MembersListItem extends React.PureComponent {
<td className="thin nowrap">
<Avatar hash={member.avatar} email={member.email} size={AVATAR_SIZE} />
</td>
- <td className="nowrap text-middle"><strong>{member.name}</strong></td>
+ <td className="nowrap text-middle">
+ <strong>{member.login}</strong>
+ <span className="note little-spacer-left">{member.name}</span>
+ </td>
{organization.canAdmin &&
<td className="text-right text-middle">
- {translate('organization.members.x_group(s)', formatMeasure(member.groupCount, 'INT'))}
+ {translateWithParameters('organization.members.x_groups', formatMeasure(member.groupCount || 0, 'INT'))}
</td>}
{organization.canAdmin &&
<td className="nowrap text-middle text-right">
@@ -62,18 +65,18 @@ export default class MembersListItem extends React.PureComponent {
</button>
<ul className="dropdown-menu dropdown-menu-right">
<li>
- <RemoveMemberForm
+ <ManageMemberGroupsForm
+ organizationGroups={this.props.organizationGroups}
organization={this.props.organization}
- removeMember={this.props.removeMember}
+ updateMemberGroups={this.props.updateMemberGroups}
member={this.props.member}
/>
</li>
<li role="separator" className="divider" />
<li>
- <ManageMemberGroupsForm
- organizationGroups={this.props.organizationGroups}
+ <RemoveMemberForm
organization={this.props.organization}
- updateMemberGroups={this.props.updateMemberGroups}
+ removeMember={this.props.removeMember}
member={this.props.member}
/>
</li>
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/PageHeader.js b/server/sonar-web/src/main/js/apps/organizations/components/MembersPageHeader.js
index 7ea0ae70c4d..1a8f6999f77 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/PageHeader.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/MembersPageHeader.js
@@ -19,8 +19,6 @@
*/
//@flow
import React from 'react';
-import { formatMeasure } from '../../../helpers/measures';
-import { translate } from '../../../helpers/l10n';
type Props = {
loading: boolean,
@@ -28,7 +26,7 @@ type Props = {
children?: {}
};
-export default class PageHeader extends React.PureComponent {
+export default class MembersPageHeader extends React.PureComponent {
props: Props;
render() {
@@ -36,12 +34,6 @@ export default class PageHeader extends React.PureComponent {
<header className="page-header">
{this.props.loading && <i className="spinner" />}
{this.props.children}
- {this.props.total != null &&
- <span className="page-totalcount">
- <strong>{formatMeasure(this.props.total, 'INT')}</strong>
- {' '}
- {translate('organization.members.member(s)')}
- </span>}
</header>
);
}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.js
new file mode 100644
index 00000000000..f21f52b0094
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.js
@@ -0,0 +1,55 @@
+/*
+ * 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 Checkbox from '../../../components/controls/Checkbox';
+import type { OrgGroup } from '../../../store/organizations/duck';
+
+type Props = {
+ group: OrgGroup,
+ checked: boolean,
+ onCheck: (string, boolean) => void
+};
+
+export default class OrganizationGroupCheckbox extends React.PureComponent {
+ props: Props;
+
+ onCheck = (checked: boolean) => {
+ this.props.onCheck(this.props.group.id, checked);
+ };
+
+ toggleCheck = () => {
+ this.props.onCheck(this.props.group.id, !this.props.checked);
+ };
+
+ render() {
+ return (
+ <li
+ className="capitalize list-item-checkable-link"
+ onClick={this.toggleCheck}
+ tabIndex={0}
+ role="listitem"
+ >
+ <Checkbox checked={this.props.checked} onCheck={this.onCheck} />
+ {' '}{this.props.group.name}
+ </li>
+ );
+ }
+}
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
index 6caa05de692..b4448548149 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js
@@ -19,10 +19,10 @@
*/
// @flow
import React from 'react';
-import PageHeader from './PageHeader';
+import MembersPageHeader from './MembersPageHeader';
+import MembersListHeader from './MembersListHeader';
import MembersList from './MembersList';
import AddMemberForm from './forms/AddMemberForm';
-import UsersSearch from '../../users/components/UsersSearch';
import ListFooter from '../../../components/controls/ListFooter';
import type { Organization, OrgGroup } from '../../../store/organizations/duck';
import type { Member } from '../../../store/organizationsMembers/actions';
@@ -38,18 +38,19 @@ type Props = {
fetchOrganizationGroups: (organizationKey: string) => void,
addOrganizationMember: (organizationKey: string, member: Member) => void,
removeOrganizationMember: (organizationKey: string, member: Member) => void,
- updateOrganizationMemberGroups: (member: Member, add: Array<string>, remove: Array<string>) => void,
+ updateOrganizationMemberGroups: (
+ member: Member,
+ add: Array<string>,
+ remove: Array<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();
- }
- if (this.props.organizationGroups.length <= 0) {
+ this.handleSearchMembers();
+ if (this.props.organization.canAdmin) {
this.props.fetchOrganizationGroups(this.props.organization.key);
}
}
@@ -74,15 +75,15 @@ export default class OrganizationMembers extends React.PureComponent {
const { organization, status, members } = this.props;
return (
<div className="page page-limited">
- <PageHeader loading={status.loading} total={status.total}>
+ <MembersPageHeader loading={status.loading} total={status.total}>
{organization.canAdmin &&
<div className="page-actions">
<div className="button-group">
<AddMemberForm memberLogins={this.props.memberLogins} addMember={this.addMember} />
</div>
</div>}
- </PageHeader>
- <UsersSearch onSearch={this.handleSearchMembers} />
+ </MembersPageHeader>
+ <MembersListHeader total={status.total} handleSearch={this.handleSearchMembers} />
<MembersList
members={members}
organizationGroups={this.props.organizationGroups}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.js
new file mode 100644
index 00000000000..439f998b4b7
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.js
@@ -0,0 +1,36 @@
+/*
+ * 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 MembersListHeader from '../MembersListHeader';
+
+it('should render without the total', () => {
+ const wrapper = shallow(
+ <MembersListHeader handleSearch={jest.fn()} />
+ );
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render with the total', () => {
+ const wrapper = shallow(
+ <MembersListHeader handleSearch={jest.fn()} total={8} />
+ );
+ 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
index d0cb52edbca..dd97722ea42 100644
--- 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
@@ -23,13 +23,13 @@ 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 };
+const john = { login: 'john', name: 'John Doe', avatar: '7daf6c79d4802916d83f6266e24850af' };
it('should not render actions and groups for non admin', () => {
const wrapper = shallow(
<MembersListItem
organization={organization}
- member={john}
+ member={admin}
/>
);
expect(wrapper).toMatchSnapshot();
@@ -44,3 +44,13 @@ it('should render actions and groups for admin', () => {
);
expect(wrapper).toMatchSnapshot();
});
+
+it('should groups at 0 if the groupCount field is not defined (just added user)', () => {
+ const wrapper = shallow(
+ <MembersListItem
+ organization={{ ...organization, canAdmin: true }}
+ member={john}
+ />
+ );
+ 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__/MembersPageHeader-test.js
index 06f9ef06e90..4e8fd9a2760 100644
--- 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__/MembersPageHeader-test.js
@@ -19,11 +19,11 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
-import PageHeader from '../PageHeader';
+import MembersPageHeader from '../MembersPageHeader';
it('should render the members page header', () => {
const wrapper = shallow(
- <PageHeader />
+ <MembersPageHeader />
);
expect(wrapper).toMatchSnapshot();
wrapper.setProps({ loading: true });
@@ -31,15 +31,15 @@ it('should render the members page header', () => {
});
it('should render the members page header with the total', () => {
- const wrapper = shallow(<PageHeader total="5" />);
+ const wrapper = shallow(<MembersPageHeader total="5" />);
expect(wrapper).toMatchSnapshot();
});
it('should render its children', () => {
const wrapper = shallow(
- <PageHeader loading={true} total="5">
+ <MembersPageHeader loading={true} total="5">
<span>children test</span>
- </PageHeader>
+ </MembersPageHeader>
);
expect(wrapper).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.js
new file mode 100644
index 00000000000..146e8aacba1
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.js
@@ -0,0 +1,47 @@
+/*
+ * 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 OrganizationGroupCheckbox from '../OrganizationGroupCheckbox';
+
+const group = {
+ id: '7',
+ name: 'professionals',
+ description: '',
+ membersCount: 12
+};
+
+it('should render unchecked', () => {
+ const wrapper = shallow(
+ <OrganizationGroupCheckbox group={group} checked={false} onCheck={jest.fn()} />
+ );
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should be able to toggle check', () => {
+ const onCheck = jest.fn((group, checked) => wrapper.setProps({ checked }));
+ const wrapper = shallow(
+ <OrganizationGroupCheckbox group={group} checked={true} onCheck={onCheck} />
+ );
+ expect(wrapper).toMatchSnapshot();
+ wrapper.instance().toggleCheck();
+ expect(onCheck.mock.calls).toMatchSnapshot();
+ 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
index 461722d4b3e..9be07a22d0b 100644
--- 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
@@ -27,8 +27,6 @@ const members = [
{ 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(
@@ -36,8 +34,8 @@ it('should not render actions for non admin', () => {
organization={organization}
members={members}
status={status}
- fetchOrganizationMembers={fetchOrganizationMembers}
- fetchMoreOrganizationMembers={fetchMoreOrganizationMembers}
+ fetchOrganizationMembers={jest.fn()}
+ fetchMoreOrganizationMembers={jest.fn()}
/>
);
expect(wrapper).toMatchSnapshot();
@@ -49,8 +47,8 @@ it('should render actions for admin', () => {
organization={{ ...organization, canAdmin: true }}
members={members}
status={{ ...status, loading: true }}
- fetchOrganizationMembers={fetchOrganizationMembers}
- fetchMoreOrganizationMembers={fetchMoreOrganizationMembers}
+ fetchOrganizationMembers={jest.fn()}
+ fetchMoreOrganizationMembers={jest.fn()}
/>
);
expect(wrapper).toMatchSnapshot();
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListHeader-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListHeader-test.js.snap
new file mode 100644
index 00000000000..1d320f196fc
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListHeader-test.js.snap
@@ -0,0 +1,25 @@
+exports[`test should render with the total 1`] = `
+<div
+ className="panel panel-vertical bordered-bottom spacer-bottom">
+ <UsersSearch
+ className="display-inline-block"
+ onSearch={[Function]} />
+ <span
+ className="pull-right little-spacer-top">
+ <strong>
+ 8
+ </strong>
+
+ organization.members.members
+ </span>
+</div>
+`;
+
+exports[`test should render without the total 1`] = `
+<div
+ className="panel panel-vertical bordered-bottom spacer-bottom">
+ <UsersSearch
+ className="display-inline-block"
+ onSearch={[Function]} />
+</div>
+`;
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
index 02e1106b6b9..39076a98a53 100644
--- 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
@@ -1,4 +1,4 @@
-exports[`test should not render actions and groups for non admin 1`] = `
+exports[`test should groups at 0 if the groupCount field is not defined (just added user) 1`] = `
<tr>
<td
className="thin nowrap">
@@ -9,8 +9,92 @@ exports[`test should not render actions and groups for non admin 1`] = `
<td
className="nowrap text-middle">
<strong>
+ john
+ </strong>
+ <span
+ className="note little-spacer-left">
John Doe
+ </span>
+ </td>
+ <td
+ className="text-right text-middle">
+ organization.members.x_groups.0
+ </td>
+ <td
+ className="nowrap text-middle text-right">
+ <div
+ className="dropdown">
+ <button
+ className="dropdown-toggle little-spacer-right"
+ data-toggle="dropdown">
+ <i
+ className="icon-settings" />
+
+ <i
+ className="icon-dropdown" />
+ </button>
+ <ul
+ className="dropdown-menu dropdown-menu-right">
+ <li>
+ <ManageMemberGroupsForm
+ member={
+ Object {
+ "avatar": "7daf6c79d4802916d83f6266e24850af",
+ "login": "john",
+ "name": "John Doe",
+ }
+ }
+ organization={
+ Object {
+ "canAdmin": true,
+ "key": "foo",
+ "name": "Foo",
+ }
+ } />
+ </li>
+ <li
+ className="divider"
+ role="separator" />
+ <li>
+ <RemoveMemberForm
+ member={
+ Object {
+ "avatar": "7daf6c79d4802916d83f6266e24850af",
+ "login": "john",
+ "name": "John Doe",
+ }
+ }
+ organization={
+ Object {
+ "canAdmin": true,
+ "key": "foo",
+ "name": "Foo",
+ }
+ } />
+ </li>
+ </ul>
+ </div>
+ </td>
+</tr>
+`;
+
+exports[`test should not render actions and groups for non admin 1`] = `
+<tr>
+ <td
+ className="thin nowrap">
+ <Connect(Avatar)
+ hash=""
+ size={36} />
+ </td>
+ <td
+ className="nowrap text-middle">
+ <strong>
+ admin
</strong>
+ <span
+ className="note little-spacer-left">
+ Admin Istrator
+ </span>
</td>
</tr>
`;
@@ -26,12 +110,16 @@ exports[`test should render actions and groups for admin 1`] = `
<td
className="nowrap text-middle">
<strong>
- Admin Istrator
+ admin
</strong>
+ <span
+ className="note little-spacer-left">
+ Admin Istrator
+ </span>
</td>
<td
className="text-right text-middle">
- organization.members.x_group(s).3
+ organization.members.x_groups.3
</td>
<td
className="nowrap text-middle text-right">
@@ -49,7 +137,7 @@ exports[`test should render actions and groups for admin 1`] = `
<ul
className="dropdown-menu dropdown-menu-right">
<li>
- <RemoveMemberForm
+ <ManageMemberGroupsForm
member={
Object {
"avatar": "",
@@ -70,7 +158,7 @@ exports[`test should render actions and groups for admin 1`] = `
className="divider"
role="separator" />
<li>
- <ManageMemberGroupsForm
+ <RemoveMemberForm
member={
Object {
"avatar": "",
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__/MembersPageHeader-test.js.snap
index 9e31b2215cd..dd2b2349739 100644
--- 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__/MembersPageHeader-test.js.snap
@@ -6,14 +6,6 @@ exports[`test should render its children 1`] = `
<span>
children test
</span>
- <span
- className="page-totalcount">
- <strong>
- 5
- </strong>
-
- organization.members.member(s)
- </span>
</header>
`;
@@ -29,14 +21,5 @@ exports[`test should render the members page header 2`] = `
exports[`test should render the members page header with the total 1`] = `
<header
- className="page-header">
- <span
- className="page-totalcount">
- <strong>
- 5
- </strong>
-
- organization.members.member(s)
- </span>
-</header>
+ className="page-header" />
`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.js.snap
new file mode 100644
index 00000000000..2ecdb53cf0d
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.js.snap
@@ -0,0 +1,53 @@
+exports[`test should be able to toggle check 1`] = `
+<li
+ className="capitalize list-item-checkable-link"
+ onClick={[Function]}
+ role="listitem"
+ tabIndex={0}>
+ <Checkbox
+ checked={true}
+ onCheck={[Function]}
+ thirdState={false} />
+
+ professionals
+</li>
+`;
+
+exports[`test should be able to toggle check 2`] = `
+Array [
+ Array [
+ "7",
+ false,
+ ],
+]
+`;
+
+exports[`test should be able to toggle check 3`] = `
+<li
+ className="capitalize list-item-checkable-link"
+ onClick={[Function]}
+ role="listitem"
+ tabIndex={0}>
+ <Checkbox
+ checked={false}
+ onCheck={[Function]}
+ thirdState={false} />
+
+ professionals
+</li>
+`;
+
+exports[`test should render unchecked 1`] = `
+<li
+ className="capitalize list-item-checkable-link"
+ onClick={[Function]}
+ role="listitem"
+ tabIndex={0}>
+ <Checkbox
+ checked={false}
+ onCheck={[Function]}
+ thirdState={false} />
+
+ professionals
+</li>
+`;
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
index e5fd4a477e0..bcd0da07201 100644
--- 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
@@ -1,10 +1,11 @@
exports[`test should not render actions for non admin 1`] = `
<div
className="page page-limited">
- <PageHeader
+ <MembersPageHeader
+ total={2} />
+ <MembersListHeader
+ handleSearch={[Function]}
total={2} />
- <UsersSearch
- onSearch={[Function]} />
<MembersList
members={
Array [
@@ -40,7 +41,7 @@ exports[`test should not render actions for non admin 1`] = `
exports[`test should render actions for admin 1`] = `
<div
className="page page-limited">
- <PageHeader
+ <MembersPageHeader
loading={true}
total={2}>
<div
@@ -51,9 +52,10 @@ exports[`test should render actions for admin 1`] = `
addMember={[Function]} />
</div>
</div>
- </PageHeader>
- <UsersSearch
- onSearch={[Function]} />
+ </MembersPageHeader>
+ <MembersListHeader
+ handleSearch={[Function]}
+ total={2} />
<MembersList
members={
Array [
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js
index 554b363dd35..ac19d3e725f 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js
@@ -21,9 +21,9 @@
import React from 'react';
import Modal from 'react-modal';
import { keyBy, pickBy } from 'lodash';
-import Checkbox from '../../../../components/controls/Checkbox';
import { getUserGroups } from '../../../../api/users';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
+import OrganizationGroupCheckbox from '../OrganizationGroupCheckbox';
import type { Member } from '../../../../store/organizationsMembers/actions';
import type { Organization, OrgGroup } from '../../../../store/organizations/duck';
@@ -47,15 +47,14 @@ export default class ManageMemberGroupsForm extends React.PureComponent {
open: false
};
- openForm = () => {
- if (!this.state.userGroups) {
- this.loadUserGroups();
- }
+ openForm = (evt: MouseEvent) => {
+ evt.preventDefault();
+ this.loadUserGroups();
this.setState({ open: true });
};
closeForm = () => {
- this.setState({ userGroups: undefined, open: false });
+ this.setState({ open: false });
};
loadUserGroups = () => {
@@ -77,7 +76,7 @@ export default class ManageMemberGroupsForm extends React.PureComponent {
return false;
};
- onCheck = (checked: boolean, groupId: string) => {
+ onCheck = (groupId: string, checked: boolean) => {
this.setState((prevState: State) => {
const userGroups = prevState.userGroups || {};
const group = userGroups[groupId] || {};
@@ -93,11 +92,12 @@ export default class ManageMemberGroupsForm extends React.PureComponent {
handleSubmit = (e: Object) => {
e.preventDefault();
- this.props.updateMemberGroups(this.props.member,
+ this.props.updateMemberGroups(
+ this.props.member,
Object.keys(pickBy(this.state.userGroups, group => group.status === 'add')),
Object.keys(pickBy(this.state.userGroups, group => group.status === 'remove'))
);
- this.setState({ open: false });
+ this.closeForm();
};
renderModal() {
@@ -115,19 +115,17 @@ export default class ManageMemberGroupsForm extends React.PureComponent {
<form onSubmit={this.handleSubmit}>
<div className="modal-body">
<strong>
- {translateWithParameters('organization.members.x_groups', this.props.member.name)}
+ {translateWithParameters('organization.members.members_groups', this.props.member.name)}
</strong>{' '}{this.state.loading && <i className="spinner" />}
{!this.state.loading &&
<ul className="list-spaced">
{this.props.organizationGroups.map(group => (
- <li className="capitalize" key={group.id}>
- <Checkbox
- id={group.id}
- checked={this.isGroupSelected(group.id)}
- onCheck={this.onCheck}
- />
- {' '}{group.name}
- </li>
+ <OrganizationGroupCheckbox
+ key={group.id}
+ group={group}
+ checked={this.isGroupSelected(group.id)}
+ onCheck={this.onCheck}
+ />
))}
</ul>}
</div>
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js
index 6d4c9e59adf..29bccf54aca 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js
@@ -41,7 +41,8 @@ export default class RemoveMemberForm extends React.PureComponent {
open: false
};
- openForm = () => {
+ openForm = (evt: MouseEvent) => {
+ evt.preventDefault();
this.setState({ open: true });
};
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js
index 403975ed4a1..1874608a815 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js
@@ -23,17 +23,16 @@ import { click } from '../../../../../helpers/testUtils';
import AddMemberForm from '../AddMemberForm';
const memberLogins = ['admin'];
-const addMember = jest.fn();
it('should render and open the modal', () => {
- const wrapper = shallow(<AddMemberForm memberLogins={memberLogins} addMember={addMember} />);
+ const wrapper = shallow(<AddMemberForm memberLogins={memberLogins} addMember={jest.fn()} />);
expect(wrapper).toMatchSnapshot();
wrapper.setState({ open: true });
expect(wrapper).toMatchSnapshot();
});
it('should correctly handle user interactions', () => {
- const wrapper = mount(<AddMemberForm memberLogins={memberLogins} addMember={addMember} />);
+ const wrapper = mount(<AddMemberForm memberLogins={memberLogins} addMember={jest.fn()} />);
click(wrapper.find('button'));
expect(wrapper.state('open')).toBeTruthy();
wrapper.instance().closeForm();
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.js
index 4d7eb28c9bb..224a1222658 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.js
@@ -47,15 +47,14 @@ const organizationGroups = [
const userGroups = {
11: { id: 11, name: 'pull-request-analysers', description: 'Technical accounts', selected: true }
};
-const updateMemberGroups = jest.fn();
-const getMountedForm = function() {
+const getMountedForm = function(updateFunc = jest.fn()) {
const wrapper = mount(
<ManageMemberGroupsForm
member={member}
organization={organization}
organizationGroups={organizationGroups}
- updateMemberGroups={updateMemberGroups}
+ updateMemberGroups={updateFunc}
/>
);
const instance = wrapper.instance();
@@ -71,7 +70,7 @@ it('should render and open the modal', () => {
member={member}
organization={organization}
organizationGroups={organizationGroups}
- updateMemberGroups={updateMemberGroups}
+ updateMemberGroups={jest.fn()}
/>
);
expect(wrapper).toMatchSnapshot();
@@ -89,34 +88,34 @@ it('should correctly handle user interactions', () => {
it('should correctly select the groups', () => {
const form = getMountedForm();
- form.instance.openForm();
+ form.instance.openForm(mockEvent);
expect(form.instance.isGroupSelected(11)).toBeTruthy();
expect(form.instance.isGroupSelected(7)).toBeFalsy();
- form.instance.onCheck(false, 11);
- form.instance.onCheck(true, 7);
+ form.instance.onCheck(11, false);
+ form.instance.onCheck(7, true);
expect(form.wrapper.state('userGroups')).toMatchSnapshot();
expect(form.instance.isGroupSelected(11)).toBeFalsy();
expect(form.instance.isGroupSelected(7)).toBeTruthy();
});
it('should correctly handle the submit event and close the modal', () => {
- const form = getMountedForm();
- form.instance.openForm();
- form.instance.onCheck(false, 11);
- form.instance.onCheck(true, 7);
+ const updateMemberGroups = jest.fn();
+ const form = getMountedForm(updateMemberGroups);
+ form.instance.openForm(mockEvent);
+ form.instance.onCheck(11, false);
+ form.instance.onCheck(7, true);
form.instance.handleSubmit(mockEvent);
expect(updateMemberGroups.mock.calls).toMatchSnapshot();
expect(form.wrapper.state()).toMatchSnapshot();
- form.instance.openForm();
- expect(form.wrapper.state()).toMatchSnapshot();
});
-it('should reset the selected groups when the modal is closed', () => {
+it('should reset the selected groups when the modal is opened', () => {
const form = getMountedForm();
- form.instance.openForm();
- form.instance.onCheck(false, 11);
- form.instance.onCheck(true, 7);
+ form.instance.openForm(mockEvent);
+ form.instance.onCheck(11, false);
+ form.instance.onCheck(7, true);
expect(form.wrapper.state()).toMatchSnapshot();
form.instance.closeForm();
+ form.instance.openForm(mockEvent);
expect(form.wrapper.state()).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js
index e8f813e2a0d..427559eb011 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js
@@ -23,12 +23,11 @@ import { click, mockEvent } from '../../../../../helpers/testUtils';
import RemoveMemberForm from '../RemoveMemberForm';
const member = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 };
-const removeMember = jest.fn();
const organization = { name: 'MyOrg' };
it('should render and open the modal', () => {
const wrapper = shallow(
- <RemoveMemberForm member={member} removeMember={removeMember} organization={organization} />
+ <RemoveMemberForm member={member} removeMember={jest.fn()} organization={organization} />
);
expect(wrapper).toMatchSnapshot();
wrapper.setState({ open: true });
@@ -36,6 +35,7 @@ it('should render and open the modal', () => {
});
it('should correctly handle user interactions', () => {
+ const removeMember = jest.fn();
const wrapper = mount(
<RemoveMemberForm member={member} removeMember={removeMember} organization={organization} />
);
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap
index 2e069febb73..a15557608a4 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap
@@ -36,25 +36,6 @@ Object {
}
`;
-exports[`test should correctly handle the submit event and close the modal 3`] = `
-Object {
- "loading": false,
- "open": true,
- "userGroups": Object {
- "11": Object {
- "description": "Technical accounts",
- "id": 11,
- "name": "pull-request-analysers",
- "selected": true,
- "status": "remove",
- },
- "7": Object {
- "status": "add",
- },
- },
-}
-`;
-
exports[`test should correctly handle user interactions 1`] = `
Object {
"loading": false,
@@ -120,41 +101,44 @@ exports[`test should render and open the modal 2`] = `
<div
className="modal-body">
<strong>
- organization.members.x_groups.Admin Istrator
+ organization.members.members_groups.Admin Istrator
</strong>
<ul
className="list-spaced">
- <li
- className="capitalize">
- <Checkbox
- checked={false}
- id="7"
- onCheck={[Function]}
- thirdState={false} />
-
- professionals
- </li>
- <li
- className="capitalize">
- <Checkbox
- checked={false}
- id="11"
- onCheck={[Function]}
- thirdState={false} />
-
- pull-request-analysers
- </li>
- <li
- className="capitalize">
- <Checkbox
- checked={false}
- id="1"
- onCheck={[Function]}
- thirdState={false} />
-
- sonar-administrators
- </li>
+ <OrganizationGroupCheckbox
+ checked={false}
+ group={
+ Object {
+ "description": "",
+ "id": "7",
+ "membersCount": 12,
+ "name": "professionals",
+ }
+ }
+ onCheck={[Function]} />
+ <OrganizationGroupCheckbox
+ checked={false}
+ group={
+ Object {
+ "description": "Technical accounts",
+ "id": "11",
+ "membersCount": 3,
+ "name": "pull-request-analysers",
+ }
+ }
+ onCheck={[Function]} />
+ <OrganizationGroupCheckbox
+ checked={false}
+ group={
+ Object {
+ "description": "System administrators",
+ "id": "1",
+ "membersCount": 17,
+ "name": "sonar-administrators",
+ }
+ }
+ onCheck={[Function]} />
</ul>
</div>
<footer
@@ -177,7 +161,7 @@ exports[`test should render and open the modal 2`] = `
</a>
`;
-exports[`test should reset the selected groups when the modal is closed 1`] = `
+exports[`test should reset the selected groups when the modal is opened 1`] = `
Object {
"loading": false,
"open": true,
@@ -196,10 +180,17 @@ Object {
}
`;
-exports[`test should reset the selected groups when the modal is closed 2`] = `
+exports[`test should reset the selected groups when the modal is opened 2`] = `
Object {
"loading": false,
- "open": false,
- "userGroups": undefined,
+ "open": true,
+ "userGroups": Object {
+ "11": Object {
+ "description": "Technical accounts",
+ "id": 11,
+ "name": "pull-request-analysers",
+ "selected": true,
+ },
+ },
}
`;
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 1b8290cd35d..d09b9cbb93c 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
@@ -24,7 +24,8 @@ import classNames from 'classnames';
import { translate, translateWithParameters } from '../../../helpers/l10n';
type Props = {
- onSearch: (query?: string) => void
+ onSearch: (query?: string) => void,
+ className?: string
};
type State = {
@@ -55,12 +56,12 @@ export default class UsersSearch extends React.PureComponent {
render() {
const { query } = this.state;
+ const searchBoxClass = classNames('search-box', this.props.className);
const inputClassName = classNames('search-box-input', {
touched: query != null && query.length === 1
});
return (
- <div className="panel panel-vertical bordered-bottom spacer-bottom">
- <div className="search-box">
+ <div className={searchBoxClass}>
<button className="search-box-submit button-clean">
<i className="icon-search" />
</button>
@@ -76,7 +77,6 @@ export default class UsersSearch extends React.PureComponent {
{translateWithParameters('select2.tooShort', 2)}
</span>
</div>
- </div>
);
}
}
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
index 5026720f2b2..4fdecd3935e 100644
--- a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js
+++ b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js
@@ -21,6 +21,7 @@
import React from 'react';
import Select from 'react-select';
import { debounce } from 'lodash';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
import UsersSelectSearchOption from './UsersSelectSearchOption';
import UsersSelectSearchValue from './UsersSelectSearchValue';
import './UsersSelectSearch.css';
@@ -54,7 +55,7 @@ export default class UsersSelectSearch extends React.PureComponent {
constructor(props: Props) {
super(props);
- this.handleSearch = debounce(this.handleSearch);
+ this.handleSearch = debounce(this.handleSearch, 250);
this.state = { searchResult: [], isLoading: false, search: '' };
}
@@ -73,7 +74,8 @@ export default class UsersSelectSearch extends React.PureComponent {
handleSearch = (search: string) => {
this.setState({ isLoading: true, search });
- this.props.searchUsers(search, Math.min(this.props.excludedUsers.length + LIST_SIZE, 500))
+ this.props
+ .searchUsers(search, Math.min(this.props.excludedUsers.length + LIST_SIZE, 500))
.then(this.filterSearchResult)
.then(searchResult => {
this.setState({ isLoading: false, searchResult });
@@ -81,6 +83,9 @@ export default class UsersSelectSearch extends React.PureComponent {
};
render() {
+ const noResult = this.state.search.length === 1
+ ? translateWithParameters('select2.tooShort', 2)
+ : translate('no_results');
return (
<Select
className="Select-big"
@@ -92,6 +97,7 @@ export default class UsersSelectSearch extends React.PureComponent {
onInputChange={this.handleSearch}
value={this.props.selectedUser}
placeholder=""
+ noResultsText={noResult}
labelKey="name"
valueKey="login"
clearable={false}
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
index 33a4c3c9b86..71fd3cc465e 100644
--- 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
@@ -21,21 +21,15 @@ 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(<UsersSearch onSearch={onSearch} query={null} />);
+it('should render correctly', () => {
+ const wrapper = shallow(<UsersSearch onSearch={jest.fn()} className="test" />);
expect(wrapper).toMatchSnapshot();
-});
-
-it('should render with a search query', () => {
- const wrapper = shallow(<UsersSearch onSearch={onSearch} query={'foo'} />);
+ wrapper.setState({ query: 'foo' });
expect(wrapper).toMatchSnapshot();
});
it('should display a help message when there is less than 2 characters', () => {
- const wrapper = shallow(<UsersSearch onSearch={onSearch} query={'a'} />);
- expect(wrapper).toMatchSnapshot();
- wrapper.setState({ query: 'foo' });
+ const wrapper = shallow(<UsersSearch onSearch={jest.fn()} />);
+ wrapper.setState({ query: 'f' });
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
index edc0fee72b3..5bac91a7a55 100644
--- 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
@@ -1,99 +1,65 @@
exports[`test should display a help message when there is less than 2 characters 1`] = `
<div
- className="panel panel-vertical bordered-bottom spacer-bottom">
- <div
- className="search-box">
- <button
- className="search-box-submit button-clean">
- <i
- className="icon-search" />
- </button>
- <input
- autoComplete="off"
- className="search-box-input"
- onChange={[Function]}
- placeholder="search_verb"
- type="search"
- value="" />
- <span
- className="note spacer-left text-middle">
- select2.tooShort.2
- </span>
- </div>
+ className="search-box">
+ <button
+ className="search-box-submit button-clean">
+ <i
+ className="icon-search" />
+ </button>
+ <input
+ autoComplete="off"
+ className="search-box-input touched"
+ onChange={[Function]}
+ placeholder="search_verb"
+ type="search"
+ value="f" />
+ <span
+ className="note spacer-left text-middle">
+ select2.tooShort.2
+ </span>
</div>
`;
-exports[`test should display a help message when there is less than 2 characters 2`] = `
+exports[`test should render correctly 1`] = `
<div
- className="panel panel-vertical bordered-bottom spacer-bottom">
- <div
- className="search-box">
- <button
- className="search-box-submit button-clean">
- <i
- className="icon-search" />
- </button>
- <input
- autoComplete="off"
- className="search-box-input"
- onChange={[Function]}
- placeholder="search_verb"
- type="search"
- value="foo" />
- <span
- className="note spacer-left text-middle">
- select2.tooShort.2
- </span>
- </div>
+ className="search-box test">
+ <button
+ className="search-box-submit button-clean">
+ <i
+ className="icon-search" />
+ </button>
+ <input
+ autoComplete="off"
+ className="search-box-input"
+ onChange={[Function]}
+ placeholder="search_verb"
+ type="search"
+ value="" />
+ <span
+ className="note spacer-left text-middle">
+ select2.tooShort.2
+ </span>
</div>
`;
-exports[`test should render correctly without any search query 1`] = `
+exports[`test should render correctly 2`] = `
<div
- className="panel panel-vertical bordered-bottom spacer-bottom">
- <div
- className="search-box">
- <button
- className="search-box-submit button-clean">
- <i
- className="icon-search" />
- </button>
- <input
- autoComplete="off"
- className="search-box-input"
- onChange={[Function]}
- placeholder="search_verb"
- type="search"
- value="" />
- <span
- className="note spacer-left text-middle">
- select2.tooShort.2
- </span>
- </div>
-</div>
-`;
-
-exports[`test should render with a search query 1`] = `
-<div
- className="panel panel-vertical bordered-bottom spacer-bottom">
- <div
- className="search-box">
- <button
- className="search-box-submit button-clean">
- <i
- className="icon-search" />
- </button>
- <input
- autoComplete="off"
- className="search-box-input"
- onChange={[Function]}
- placeholder="search_verb"
- type="search"
- value="" />
- <span
- className="note spacer-left text-middle">
- select2.tooShort.2
- </span>
- </div>
+ className="search-box test">
+ <button
+ className="search-box-submit button-clean">
+ <i
+ className="icon-search" />
+ </button>
+ <input
+ autoComplete="off"
+ className="search-box-input"
+ onChange={[Function]}
+ placeholder="search_verb"
+ type="search"
+ value="foo" />
+ <span
+ className="note spacer-left text-middle">
+ select2.tooShort.2
+ </span>
</div>
`;
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
index e4f6d80595e..7ab91b1de70 100644
--- 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
@@ -24,7 +24,7 @@ exports[`test should render correctly 1`] = `
menuBuffer={0}
menuRenderer={[Function]}
multi={false}
- noResultsText="No results found"
+ noResultsText="no_results"
onBlurResetsInput={true}
onChange={[Function]}
onCloseResetsInput={true}
@@ -91,7 +91,7 @@ exports[`test should render correctly 3`] = `
menuBuffer={0}
menuRenderer={[Function]}
multi={false}
- noResultsText="No results found"
+ noResultsText="no_results"
onBlurResetsInput={true}
onChange={[Function]}
onCloseResetsInput={true}
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 8d5f42cb719..d663e9162ca 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,8 +22,6 @@ 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(<ListFooter count={3} total={5} />);
expect(listFooter.text()).toContain('x_of_y_shown.3.5');
@@ -35,11 +33,12 @@ it('should not render "show more"', () => {
});
it('should not render "show more"', () => {
- const listFooter = shallow(<ListFooter count={5} total={5} loadMore={loadMore} />);
+ const listFooter = shallow(<ListFooter count={5} total={5} loadMore={jest.fn()} />);
expect(listFooter.find('a').length).toBe(0);
});
it('should "show more"', () => {
+ const loadMore = jest.fn();
const listFooter = shallow(<ListFooter count={3} total={5} loadMore={loadMore} />);
const link = listFooter.find('a');
expect(link.length).toBe(1);
diff --git a/server/sonar-web/src/main/less/components/page.less b/server/sonar-web/src/main/less/components/page.less
index ec4df716077..9e24c26252f 100644
--- a/server/sonar-web/src/main/less/components/page.less
+++ b/server/sonar-web/src/main/less/components/page.less
@@ -84,12 +84,6 @@
top: 3px;
margin-left: 8px;
}
-
- .page-totalcount {
- position: absolute;
- bottom: -50px;
- right: 0;
- }
}
.page-title {
diff --git a/server/sonar-web/src/main/less/init/lists.less b/server/sonar-web/src/main/less/init/lists.less
index f62499995ac..193e2f2d301 100644
--- a/server/sonar-web/src/main/less/init/lists.less
+++ b/server/sonar-web/src/main/less/init/lists.less
@@ -63,6 +63,14 @@ ol, ul {
}
}
+.list-item-checkable-link {
+ cursor: pointer;
+
+ &:focus {
+ outline: none;
+ }
+}
+
// Definition lists