From facddc67e65ce303e40e494d921a53446277cd43 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Wed, 22 Aug 2018 13:49:25 +0200 Subject: drop organization members from redux store (#637) --- server/sonar-web/src/main/js/api/organizations.ts | 17 +- .../js/apps/organizationMembers/AddMemberForm.tsx | 115 ++++++++++ .../organizationMembers/ManageMemberGroupsForm.tsx | 146 +++++++++++++ .../js/apps/organizationMembers/MembersList.tsx | 66 ++++++ .../apps/organizationMembers/MembersListHeader.tsx | 45 ++++ .../apps/organizationMembers/MembersListItem.tsx | 135 ++++++++++++ .../apps/organizationMembers/MembersPageHeader.tsx | 52 +++++ .../organizationMembers/OrganizationMembers.tsx | 232 +++++++++++++++++++++ .../OrganizationMembersContainer.tsx | 40 ++++ .../apps/organizationMembers/RemoveMemberForm.tsx | 67 ++++++ .../__tests__/AddMemberForm-test.tsx | 100 +++++++++ .../__tests__/ManageMemberGroupsForm-test.tsx | 101 +++++++++ .../__tests__/MembersList-test.tsx | 54 +++++ .../__tests__/MembersListHeader-test.tsx | 32 +++ .../__tests__/MembersListItem-test.tsx | 104 +++++++++ .../__tests__/MembersPageHeader-test.tsx | 31 +++ .../__tests__/OrganizationMembers-test.tsx | 151 ++++++++++++++ .../__tests__/RemoveMemberForm-test.tsx | 57 +++++ .../__snapshots__/AddMemberForm-test.tsx.snap | 77 +++++++ .../ManageMemberGroupsForm-test.tsx.snap | 97 +++++++++ .../__snapshots__/MembersList-test.tsx.snap | 62 ++++++ .../__snapshots__/MembersListHeader-test.tsx.snap | 34 +++ .../__snapshots__/MembersListItem-test.tsx.snap | 129 ++++++++++++ .../__snapshots__/MembersPageHeader-test.tsx.snap | 39 ++++ .../OrganizationMembers-test.tsx.snap | 176 ++++++++++++++++ .../__snapshots__/RemoveMemberForm-test.tsx.snap | 43 ++++ .../src/main/js/apps/organizations/actions.ts | 107 +--------- .../apps/organizations/components/MembersList.tsx | 57 ----- .../organizations/components/MembersListHeader.tsx | 45 ---- .../organizations/components/MembersListItem.tsx | 135 ------------ .../organizations/components/MembersPageHeader.tsx | 52 ----- .../components/OrganizationMembers.tsx | 119 ----------- .../components/OrganizationMembersContainer.tsx | 89 -------- .../components/__tests__/MembersList-test.tsx | 41 ---- .../__tests__/MembersListHeader-test.tsx | 32 --- .../components/__tests__/MembersListItem-test.tsx | 65 ------ .../__tests__/MembersPageHeader-test.tsx | 31 --- .../__tests__/OrganizationMembers-test.tsx | 67 ------ .../__snapshots__/MembersList-test.tsx.snap | 54 ----- .../__snapshots__/MembersListHeader-test.tsx.snap | 34 --- .../__snapshots__/MembersListItem-test.tsx.snap | 129 ------------ .../__snapshots__/MembersPageHeader-test.tsx.snap | 39 ---- .../OrganizationMembers-test.tsx.snap | 132 ------------ .../components/forms/AddMemberForm.tsx | 116 ----------- .../components/forms/ManageMemberGroupsForm.tsx | 146 ------------- .../components/forms/RemoveMemberForm.tsx | 67 ------ .../forms/__tests__/AddMemberForm-test.tsx | 54 ----- .../__tests__/ManageMemberGroupsForm-test.tsx | 101 --------- .../forms/__tests__/RemoveMemberForm-test.tsx | 57 ----- .../__snapshots__/AddMemberForm-test.tsx.snap | 73 ------- .../ManageMemberGroupsForm-test.tsx.snap | 97 --------- .../__snapshots__/RemoveMemberForm-test.tsx.snap | 43 ---- .../src/main/js/apps/organizations/routes.ts | 2 +- .../components/AppContainer.js | 3 +- .../js/apps/project-admin/store/rootReducer.js | 8 +- .../sonar-web/src/main/js/store/appState/duck.ts | 4 - .../src/main/js/store/globalMessages/duck.js | 4 +- .../sonar-web/src/main/js/store/metrics/reducer.js | 1 - .../src/main/js/store/organizations/utils.ts | 36 ---- .../main/js/store/organizationsMembers/actions.js | 86 -------- .../main/js/store/organizationsMembers/reducer.js | 91 -------- server/sonar-web/src/main/js/store/rootActions.js | 6 - server/sonar-web/src/main/js/store/rootReducer.js | 30 --- .../sonar-web/src/main/js/store/users/actions.ts | 6 - .../sonar-web/src/main/js/store/users/reducer.ts | 38 +--- .../src/main/js/store/utils/generalReducers.js | 107 ---------- 66 files changed, 2211 insertions(+), 2395 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/AddMemberForm.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/ManageMemberGroupsForm.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/MembersList.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembersContainer.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/RemoveMemberForm.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/AddMemberForm-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/ManageMemberGroupsForm-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersList-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListHeader-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListItem-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersPageHeader-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/OrganizationMembers-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/RemoveMemberForm-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/AddMemberForm-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersList-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListHeader-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListItem-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/MembersList.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/MembersPageHeader.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersList-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersPageHeader-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersList-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListHeader-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/store/organizations/utils.ts delete mode 100644 server/sonar-web/src/main/js/store/organizationsMembers/actions.js delete mode 100644 server/sonar-web/src/main/js/store/organizationsMembers/reducer.js delete mode 100644 server/sonar-web/src/main/js/store/utils/generalReducers.js diff --git a/server/sonar-web/src/main/js/api/organizations.ts b/server/sonar-web/src/main/js/api/organizations.ts index ffa4cb24fb2..78d8f63ac1c 100644 --- a/server/sonar-web/src/main/js/api/organizations.ts +++ b/server/sonar-web/src/main/js/api/organizations.ts @@ -19,7 +19,7 @@ */ import { getJSON, post, postJSON } from '../helpers/request'; import throwGlobalError from '../app/utils/throwGlobalError'; -import { Organization, OrganizationBase, Paging } from '../app/types'; +import { Organization, OrganizationBase, Paging, OrganizationMember } from '../app/types'; export function getOrganizations(data: { organizations?: string; @@ -69,16 +69,19 @@ export function searchMembers(data: { ps?: number; q?: string; selected?: string; -}): Promise<{ paging: Paging; users: Array<{ avatar?: string; login: string; name: string }> }> { - return getJSON('/api/organizations/search_members', data); +}): Promise<{ paging: Paging; users: OrganizationMember[] }> { + return getJSON('/api/organizations/search_members', data).catch(throwGlobalError); } -export function addMember(data: { login: string; organization: string }): Promise { - return postJSON('/api/organizations/add_member', data).then(r => r.user); +export function addMember(data: { + login: string; + organization: string; +}): Promise { + return postJSON('/api/organizations/add_member', data).then(r => r.user, throwGlobalError); } -export function removeMember(data: { login: string; organization: string }): Promise { - return post('/api/organizations/remove_member', data); +export function removeMember(data: { login: string; organization: string }) { + return post('/api/organizations/remove_member', data).catch(throwGlobalError); } export interface OrganizationBilling { diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/AddMemberForm.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/AddMemberForm.tsx new file mode 100644 index 00000000000..2617234955e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/AddMemberForm.tsx @@ -0,0 +1,115 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as React from 'react'; +import UsersSelectSearch from '../users/components/UsersSelectSearch'; +import { searchMembers } from '../../api/organizations'; +import Modal from '../../components/controls/Modal'; +import { translate } from '../../helpers/l10n'; +import { SubmitButton, ResetButtonLink, Button } from '../../components/ui/buttons'; +import { Organization, OrganizationMember } from '../../app/types'; + +interface Props { + addMember: (member: OrganizationMember) => void; + organization: Organization; + memberLogins: string[]; +} + +interface State { + open: boolean; + selectedMember?: OrganizationMember; +} + +export default class AddMemberForm extends React.PureComponent { + state: State = { + open: false + }; + + openForm = () => { + this.setState({ open: true }); + }; + + closeForm = () => { + this.setState({ open: false, selectedMember: undefined }); + }; + + handleSearch = (query: string | undefined, ps: number) => { + const data = { organization: this.props.organization.key, ps, selected: 'deselected' }; + if (!query) { + return searchMembers(data); + } + return searchMembers({ ...data, q: query }); + }; + + handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + if (this.state.selectedMember) { + this.props.addMember(this.state.selectedMember); + this.closeForm(); + } + }; + + selectedMemberChange = (member: OrganizationMember) => { + this.setState({ selectedMember: member }); + }; + + renderModal() { + const header = translate('users.add'); + return ( + +
+

{header}

+
+
+
+
+ + +
+
+
+
+ + {translate('organization.members.add_to_members')} + + {translate('cancel')} +
+
+
+
+ ); + } + + render() { + return ( + <> + + {this.state.open && this.renderModal()} + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/ManageMemberGroupsForm.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/ManageMemberGroupsForm.tsx new file mode 100644 index 00000000000..6d92e7fce8b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/ManageMemberGroupsForm.tsx @@ -0,0 +1,146 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as React from 'react'; +import { keyBy, pickBy } from 'lodash'; +import { getUserGroups, UserGroup } from '../../api/users'; +import Modal from '../../components/controls/Modal'; +import { translate, translateWithParameters } from '../../helpers/l10n'; +import OrganizationGroupCheckbox from '../organizations/components/OrganizationGroupCheckbox'; +import { SubmitButton, ResetButtonLink } from '../../components/ui/buttons'; +import { Organization, OrganizationMember, Group } from '../../app/types'; + +interface Props { + onClose: () => void; + member: OrganizationMember; + organization: Organization; + organizationGroups: Group[]; + updateMemberGroups: (member: OrganizationMember, add: string[], remove: string[]) => void; +} + +interface State { + userGroups?: { [k: string]: UserGroup & { status?: string } }; + loading?: boolean; +} + +export default class ManageMemberGroupsForm extends React.PureComponent { + mounted = false; + state: State = {}; + + componentDidMount() { + this.mounted = true; + this.loadUserGroups(); + } + + componentWillUnmount() { + this.mounted = false; + } + + loadUserGroups = () => { + this.setState({ loading: true }); + getUserGroups(this.props.member.login, this.props.organization.key).then( + response => { + if (this.mounted) { + this.setState({ loading: false, userGroups: keyBy(response.groups, 'name') }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + }; + + isGroupSelected = (groupName: string) => { + if (this.state.userGroups) { + const group = this.state.userGroups[groupName] || {}; + if (group.status) { + return group.status === 'add'; + } else { + return group.selected === true; + } + } + return false; + }; + + onCheck = (groupName: string, checked: boolean) => { + this.setState((prevState: State) => { + const userGroups = prevState.userGroups || {}; + const group = userGroups[groupName] || {}; + let status = ''; + if (group.selected && !checked) { + status = 'remove'; + } else if (!group.selected && checked) { + status = 'add'; + } + return { userGroups: { ...userGroups, [groupName]: { ...group, status } } }; + }); + }; + + handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + 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.props.onClose(); + }; + + render() { + const header = translate('organization.members.manage_groups'); + return ( + +
+

{header}

+
+
+
+ + {translateWithParameters( + 'organization.members.members_groups', + this.props.member.name + )} + {' '} + {this.state.loading && } + {!this.state.loading && ( +
    + {this.props.organizationGroups.map(group => ( + + ))} +
+ )} +
+
+
+ {translate('save')} + {translate('cancel')} +
+
+ +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/MembersList.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersList.tsx new file mode 100644 index 00000000000..676478bbbff --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/MembersList.tsx @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as React from 'react'; +import { sortBy } from 'lodash'; +import MembersListItem from './MembersListItem'; +import { Group, Organization, OrganizationMember } from '../../app/types'; +import { translate } from '../../helpers/l10n'; + +interface Props { + members: OrganizationMember[]; + organizationGroups: Group[]; + organization: Organization; + removeMember: (member: OrganizationMember) => void; + updateMemberGroups: ( + member: OrganizationMember, + add: Array, + remove: Array + ) => void; +} + +export default class MembersList extends React.PureComponent { + render() { + const { members } = this.props; + + if (!members.length) { + return
{translate('no_results')}
; + } + + const sortedMembers = sortBy(members, member => member.name, member => member.login); + return ( +
+ + + {sortedMembers.map(member => ( + + ))} + +
+
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx new file mode 100644 index 00000000000..e0af17ca664 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as React from 'react'; +import SearchBox from '../../components/controls/SearchBox'; +import { formatMeasure } from '../../helpers/measures'; +import { translate } from '../../helpers/l10n'; + +interface Props { + handleSearch: (query?: string) => void; + total?: number; +} + +export default function MembersListHeader({ handleSearch, total }: Props) { + return ( +
+ + {total !== undefined && ( + + {formatMeasure(total, 'INT')} {translate('organization.members.members')} + + )} +
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx new file mode 100644 index 00000000000..43b2535d68f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx @@ -0,0 +1,135 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as React from 'react'; +import RemoveMemberForm from './RemoveMemberForm'; +import ManageMemberGroupsForm from './ManageMemberGroupsForm'; +import Avatar from '../../components/ui/Avatar'; +import { translate, translateWithParameters } from '../../helpers/l10n'; +import { formatMeasure } from '../../helpers/measures'; +import ActionsDropdown, { + ActionsDropdownDivider, + ActionsDropdownItem +} from '../../components/controls/ActionsDropdown'; +import { Group, Organization, OrganizationMember } from '../../app/types'; + +interface Props { + member: OrganizationMember; + organization: Organization; + organizationGroups: Group[]; + removeMember: (member: OrganizationMember) => void; + updateMemberGroups: (member: OrganizationMember, add: string[], remove: string[]) => void; +} + +interface State { + removeMemberForm: boolean; + manageGroupsForm: boolean; +} + +const AVATAR_SIZE = 36; + +export default class MembersListItem extends React.PureComponent { + mounted = false; + state: State = { removeMemberForm: false, manageGroupsForm: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleManageGroupsClick = () => { + this.setState({ manageGroupsForm: true }); + }; + + closeManageGroupsForm = () => { + if (this.mounted) { + this.setState({ manageGroupsForm: false }); + } + }; + + handleRemoveMemberClick = () => { + this.setState({ removeMemberForm: true }); + }; + + closeRemoveMemberForm = () => { + if (this.mounted) { + this.setState({ removeMemberForm: false }); + } + }; + + render() { + const { member, organization } = this.props; + return ( + + + + + + {member.name} + {member.login} + + {organization.canAdmin && ( + + {translateWithParameters( + 'organization.members.x_groups', + formatMeasure(member.groupCount || 0, 'INT') + )} + + )} + {organization.canAdmin && ( + + + + + {translate('organization.members.manage_groups')} + + + + {translate('organization.members.remove')} + + + + + {this.state.manageGroupsForm && ( + + )} + + {this.state.removeMemberForm && ( + + )} + + )} + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx new file mode 100644 index 00000000000..171dfc48aa8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router'; +import { translate } from '../../helpers/l10n'; +import DeferredSpinner from '../../components/common/DeferredSpinner'; + +interface Props { + children?: React.ReactNode; + loading: boolean; +} + +export default function MembersPageHeader(props: Props) { + return ( +
+

{translate('organization.members.page')}

+ + {props.children} +

+ + {translate('organization.members.manage_a_team')} + + ) + }} + /> +

+
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx new file mode 100644 index 00000000000..5acb076d053 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx @@ -0,0 +1,232 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as React from 'react'; +import Helmet from 'react-helmet'; +import MembersPageHeader from './MembersPageHeader'; +import MembersListHeader from './MembersListHeader'; +import MembersList from './MembersList'; +import AddMemberForm from './AddMemberForm'; +import Suggestions from '../../app/components/embed-docs-modal/Suggestions'; +import ListFooter from '../../components/controls/ListFooter'; +import DocTooltip from '../../components/docs/DocTooltip'; +import { translate } from '../../helpers/l10n'; +import { Group, Organization, OrganizationMember, Paging } from '../../app/types'; +import { searchMembers, addMember, removeMember } from '../../api/organizations'; +import { searchUsersGroups, addUserToGroup, removeUserFromGroup } from '../../api/user_groups'; + +interface Props { + organization: Organization; +} + +interface State { + groups: Group[]; + loading: boolean; + members?: OrganizationMember[]; + paging?: Paging; + query: string; +} + +const PAGE_SIZE = 50; + +export default class OrganizationMembers extends React.PureComponent { + mounted = false; + state: State = { + groups: [], + loading: true, + query: '' + }; + + componentDidMount() { + this.mounted = true; + this.fetchMembers(); + if (this.props.organization.canAdmin) { + this.fetchGroups(); + } + } + + componentWillUnmount() { + this.mounted = false; + } + + stopLoading = () => { + if (this.mounted) { + this.setState({ loading: false }); + } + }; + + fetchMembers = (query?: string) => { + this.setState({ loading: true }); + searchMembers({ + organization: this.props.organization.key, + ps: PAGE_SIZE, + q: query + }).then(({ paging, users }) => { + if (this.mounted) { + this.setState({ loading: false, members: users, paging }); + } + }, this.stopLoading); + }; + + fetchGroups = () => { + searchUsersGroups({ organization: this.props.organization.key }).then( + ({ groups }) => { + if (this.mounted) { + this.setState({ groups }); + } + }, + () => {} + ); + }; + + handleSearchMembers = (query: string) => { + this.setState({ query }); + this.fetchMembers(query || undefined); // empty string -> undefined + }; + + handleLoadMoreMembers = () => { + const { paging, query } = this.state; + if (!paging) { + return; + } + + this.setState({ loading: true }); + searchMembers({ + organization: this.props.organization.key, + p: paging.pageIndex + 1, + ps: PAGE_SIZE, + q: query || undefined // empty string -> undefined + }).then(({ paging, users }) => { + if (this.mounted) { + this.setState(({ members = [] }) => ({ + loading: false, + members: [...members, ...users], + paging + })); + } + }, this.stopLoading); + }; + + handleAddMember = ({ login }: OrganizationMember) => { + // TODO optimistic update + addMember({ login, organization: this.props.organization.key }).then( + member => { + if (this.mounted) { + this.setState(({ members, paging }) => ({ + members: members && [...members, member], + paging: paging && { ...paging, total: paging.total + 1 } + })); + } + }, + () => {} + ); + }; + + handleRemoveMember = ({ login }: OrganizationMember) => { + // TODO optimistic update + removeMember({ login, organization: this.props.organization.key }).then( + () => { + if (this.mounted) { + this.setState(({ members, paging }) => ({ + members: members && members.filter(member => member.login !== login), + paging: paging && { ...paging, total: paging.total - 1 } + })); + } + }, + () => {} + ); + }; + + updateGroup = (login: string, updater: (member: OrganizationMember) => OrganizationMember) => { + this.setState(({ members }) => ({ + members: members && members.map(member => (member.login === login ? updater(member) : member)) + })); + }; + + updateMemberGroups = ({ login }: OrganizationMember, add: string[], remove: string[]) => { + // TODO optimistic update + const promises = [ + ...add.map(name => + addUserToGroup({ name, login, organization: this.props.organization.key }) + ), + ...remove.map(name => + removeUserFromGroup({ name, login, organization: this.props.organization.key }) + ) + ]; + Promise.all(promises).then( + () => { + if (this.mounted) { + this.updateGroup(login, member => ({ + ...member, + groupCount: (member.groupCount || 0) + add.length - remove.length + })); + } + }, + () => {} + ); + }; + + render() { + const { organization } = this.props; + const { groups, loading, members, paging } = this.state; + const memberLogins = members ? members.map(member => member.login) : []; + return ( +
+ + + + {organization.canAdmin && ( +
+ + +
+ )} +
+ {members !== undefined && + paging !== undefined && ( + <> + + + {paging.total !== 0 && ( + + )} + + )} +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembersContainer.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembersContainer.tsx new file mode 100644 index 00000000000..6f13da8a3c2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembersContainer.tsx @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 { Organization } from '../../app/types'; +import { getOrganizationByKey } from '../../store/rootReducer'; + +interface OwnProps { + params: { organizationKey: string }; +} + +interface StateProps { + organization: Organization; +} + +const mapStateToProps = (state: any, ownProps: OwnProps): StateProps => { + const { organizationKey } = ownProps.params; + return { + organization: getOrganizationByKey(state, organizationKey)! + }; +}; + +export default connect(mapStateToProps)(OrganizationMembers); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/RemoveMemberForm.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/RemoveMemberForm.tsx new file mode 100644 index 00000000000..636ae892a31 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/RemoveMemberForm.tsx @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as React from 'react'; +import Modal from '../../components/controls/Modal'; +import { translate, translateWithParameters } from '../../helpers/l10n'; +import { SubmitButton, ResetButtonLink } from '../../components/ui/buttons'; +import { Organization, OrganizationMember } from '../../app/types'; + +interface Props { + onClose: () => void; + member: OrganizationMember; + organization: Organization; + removeMember: (member: OrganizationMember) => void; +} + +export default class RemoveMemberForm extends React.PureComponent { + handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + this.props.removeMember(this.props.member); + this.props.onClose(); + }; + + render() { + const header = translate('users.remove'); + return ( + +
+

{header}

+
+
+
+ {translateWithParameters( + 'organization.members.remove_x', + this.props.member.name, + this.props.organization.name + )} +
+
+
+ + {translate('remove')} + + {translate('cancel')} +
+
+
+
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/AddMemberForm-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/AddMemberForm-test.tsx new file mode 100644 index 00000000000..2908edeb1c9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/AddMemberForm-test.tsx @@ -0,0 +1,100 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as React from 'react'; +import { shallow } from 'enzyme'; +import { click, submit } from '../../../helpers/testUtils'; +import AddMemberForm from '../AddMemberForm'; +import { searchMembers } from '../../../api/organizations'; + +jest.mock('../../../api/organizations', () => ({ + searchMembers: jest.fn().mockResolvedValue({ paging: {}, users: [] }) +})); + +const memberLogins = ['admin']; + +it('should render and open the modal', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + wrapper.setState({ open: true }); + expect(wrapper).toMatchSnapshot(); +}); + +it('should correctly handle user interactions', () => { + const wrapper = shallow( + + ); + click(wrapper.find('Button')); + expect(wrapper.state('open')).toBeTruthy(); + (wrapper.instance() as AddMemberForm).closeForm(); + expect(wrapper.state('open')).toBeFalsy(); +}); + +it('should search users', () => { + const wrapper = shallow( + + ); + click(wrapper.find('Button')); + + wrapper.find('UsersSelectSearch').prop('searchUsers')('foo', 100); + expect(searchMembers).lastCalledWith({ + organization: 'foo', + ps: 100, + q: 'foo', + selected: 'deselected' + }); + + wrapper.find('UsersSelectSearch').prop('searchUsers')('', 100); + expect(searchMembers).lastCalledWith({ + organization: 'foo', + ps: 100, + selected: 'deselected' + }); +}); + +it('should select user', () => { + const addMember = jest.fn(); + const user = { login: 'luke', name: 'Luke' }; + const wrapper = shallow( + + ); + click(wrapper.find('Button')); + + wrapper.find('UsersSelectSearch').prop('handleValueChange')(user); + submit(wrapper.find('form')); + expect(addMember).toBeCalledWith(user); +}); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/ManageMemberGroupsForm-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/ManageMemberGroupsForm-test.tsx new file mode 100644 index 00000000000..8a011e697f7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/ManageMemberGroupsForm-test.tsx @@ -0,0 +1,101 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as React from 'react'; +import { shallow } from 'enzyme'; +import { mockEvent } from '../../../helpers/testUtils'; +import ManageMemberGroupsForm from '../ManageMemberGroupsForm'; + +const member = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }; +const organization = { name: 'MyOrg', key: 'myorg' }; +const organizationGroups = [ + { + id: 7, + name: 'professionals', + description: '', + membersCount: 12 + }, + { + id: 11, + name: 'pull-request-analysers', + description: 'Technical accounts', + membersCount: 3 + }, + { + id: 1, + name: 'sonar-administrators', + description: 'System administrators', + membersCount: 17 + } +]; +const userGroups = { + 11: { id: 11, name: 'pull-request-analysers', description: 'Technical accounts', selected: true } +}; + +function getMountedForm(updateFunc = jest.fn()) { + const wrapper = shallow( + , + { disableLifecycleMethods: true } + ); + const instance = wrapper.instance(); + wrapper.setState({ loading: false, userGroups }); + return { wrapper, instance }; +} + +it('should render', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); +}); + +it('should correctly select the groups', () => { + const form = getMountedForm(); + const instance = form.instance as ManageMemberGroupsForm; + expect(instance.isGroupSelected('11')).toBeTruthy(); + expect(instance.isGroupSelected('7')).toBeFalsy(); + instance.onCheck('11', false); + instance.onCheck('7', true); + expect(form.wrapper.state('userGroups')).toMatchSnapshot(); + expect(instance.isGroupSelected('11')).toBeFalsy(); + expect(instance.isGroupSelected('7')).toBeTruthy(); +}); + +it('should correctly handle the submit event and close the modal', () => { + const updateMemberGroups = jest.fn(); + const form = getMountedForm(updateMemberGroups); + const instance = form.instance as ManageMemberGroupsForm; + instance.onCheck('11', false); + instance.onCheck('7', true); + instance.handleSubmit(mockEvent as any); + expect(updateMemberGroups.mock.calls).toMatchSnapshot(); + expect(form.wrapper.state()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersList-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersList-test.tsx new file mode 100644 index 00000000000..514ac814a16 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersList-test.tsx @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as 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(); +}); + +it('should render "no results"', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListHeader-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListHeader-test.tsx new file mode 100644 index 00000000000..ffc4241d145 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListHeader-test.tsx @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as React from 'react'; +import { shallow } from 'enzyme'; +import MembersListHeader from '../MembersListHeader'; + +it('should render without the total', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render with the total', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListItem-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListItem-test.tsx new file mode 100644 index 00000000000..18c91ecec61 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListItem-test.tsx @@ -0,0 +1,104 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as React from 'react'; +import { shallow } from 'enzyme'; +import MembersListItem from '../MembersListItem'; +import { click } from '../../../helpers/testUtils'; + +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' }; + +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(); +}); + +it('should groups at 0 if the groupCount field is not defined (just added user)', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); +}); + +it('should open groups form', () => { + const wrapper = shallow( + + ); + + click(wrapper.find('ActionsDropdownItem').first()); + expect(wrapper.find('ManageMemberGroupsForm').exists()).toBe(true); + + wrapper.find('ManageMemberGroupsForm').prop('onClose')(); + wrapper.update(); + expect(wrapper.find('ManageMemberGroupsForm').exists()).toBe(false); +}); + +it('should open remove member form', () => { + const wrapper = shallow( + + ); + + click(wrapper.find('ActionsDropdownItem').last()); + expect(wrapper.find('RemoveMemberForm').exists()).toBe(true); + + wrapper.find('RemoveMemberForm').prop('onClose')(); + wrapper.update(); + expect(wrapper.find('RemoveMemberForm').exists()).toBe(false); +}); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersPageHeader-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersPageHeader-test.tsx new file mode 100644 index 00000000000..eb2dbe02f4d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersPageHeader-test.tsx @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as React from 'react'; +import { shallow } from 'enzyme'; +import MembersPageHeader from '../MembersPageHeader'; + +it('should render', () => { + const wrapper = shallow( + + children test + + ); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/OrganizationMembers-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/OrganizationMembers-test.tsx new file mode 100644 index 00000000000..d60b0e9c3c0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/OrganizationMembers-test.tsx @@ -0,0 +1,151 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as React from 'react'; +import { shallow } from 'enzyme'; +import OrganizationMembers from '../OrganizationMembers'; +import { waitAndUpdate } from '../../../helpers/testUtils'; +import { searchMembers, addMember, removeMember } from '../../../api/organizations'; +import { searchUsersGroups, addUserToGroup, removeUserFromGroup } from '../../../api/user_groups'; +import { OrganizationMember } from '../../../app/types'; + +jest.mock('../../../api/organizations', () => ({ + addMember: jest.fn().mockResolvedValue({ login: 'bar', name: 'Bar', groupCount: 1 }), + removeMember: jest.fn().mockResolvedValue(undefined), + searchMembers: jest.fn().mockResolvedValue({ + paging: { pageIndex: 1, pageSize: 2, total: 3 }, + users: [ + { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }, + { login: 'john', name: 'John Doe', avatar: '7daf6c79d4802916d83f6266e24850af', groupCount: 1 } + ] + }) +})); + +jest.mock('../../../api/user_groups', () => ({ + addUserToGroup: jest.fn().mockResolvedValue(undefined), + removeUserFromGroup: jest.fn().mockResolvedValue(undefined), + searchUsersGroups: jest.fn().mockResolvedValue({ + paging: { pageIndex: 1, pageSize: 100, total: 2 }, + groups: [ + { id: 1, name: 'Members', description: '', membersCount: 2, default: true }, + { id: 2, name: 'Watchers', description: '', membersCount: 0, default: false } + ] + }) +})); + +const organization = { key: 'foo', name: 'Foo' }; + +beforeEach(() => { + (searchMembers as jest.Mock).mockClear(); + (searchUsersGroups as jest.Mock).mockClear(); +}); + +it('should fetch members and render for non-admin', async () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); + expect(searchMembers).toBeCalledWith({ organization: 'foo', ps: 50, q: undefined }); +}); + +it('should fetch members and groups and render for admin', async () => { + const wrapper = shallow( + + ); + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); + expect(searchMembers).toBeCalledWith({ organization: 'foo', ps: 50, q: undefined }); + expect(searchUsersGroups).toBeCalledWith({ organization: 'foo' }); +}); + +it('should search users', async () => { + const wrapper = shallow( + + ); + await waitAndUpdate(wrapper); + wrapper.find('MembersListHeader').prop('handleSearch')('user'); + expect(searchMembers).lastCalledWith({ organization: 'foo', ps: 50, q: 'user' }); +}); + +it('should load more members', async () => { + const wrapper = shallow( + + ); + await waitAndUpdate(wrapper); + wrapper.find('ListFooter').prop('loadMore')(); + expect(searchMembers).lastCalledWith({ organization: 'foo', p: 2, ps: 50, q: undefined }); +}); + +it('should add new member', async () => { + const wrapper = shallow( + + ); + await waitAndUpdate(wrapper); + wrapper.find('AddMemberForm').prop('addMember')({ login: 'bar' }); + await waitAndUpdate(wrapper); + expect( + wrapper + .find('MembersList') + .prop('members') + .find(m => m.login === 'bar') + ).toBeDefined(); + expect(wrapper.find('ListFooter').prop('total')).toEqual(4); + expect(addMember).toBeCalledWith({ login: 'bar', organization: 'foo' }); +}); + +it('should remove member', async () => { + const wrapper = shallow( + + ); + await waitAndUpdate(wrapper); + wrapper.find('MembersList').prop('removeMember')({ login: 'john' }); + await waitAndUpdate(wrapper); + expect( + wrapper + .find('MembersList') + .prop('members') + .find(m => m.login === 'john') + ).toBeUndefined(); + expect(wrapper.find('ListFooter').prop('total')).toEqual(2); + expect(removeMember).toBeCalledWith({ login: 'john', organization: 'foo' }); +}); + +it('should update groups', async () => { + const wrapper = shallow( + + ); + await waitAndUpdate(wrapper); + wrapper.find('MembersList').prop('updateMemberGroups')( + { login: 'john' }, + ['cats', 'dogs'], // add to + ['birds'] // remove from + ); + await waitAndUpdate(wrapper); + const john = wrapper + .find('MembersList') + .prop('members') + .find(m => m.login === 'john'); + expect(john && john.groupCount).toBe(2); + expect(addUserToGroup).toHaveBeenCalledTimes(2); + expect(addUserToGroup).toBeCalledWith({ login: 'john', name: 'cats', organization: 'foo' }); + expect(addUserToGroup).toBeCalledWith({ login: 'john', name: 'dogs', organization: 'foo' }); + expect(removeUserFromGroup).toHaveBeenCalledTimes(1); + expect(removeUserFromGroup).toBeCalledWith({ login: 'john', name: 'birds', organization: 'foo' }); +}); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/RemoveMemberForm-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/RemoveMemberForm-test.tsx new file mode 100644 index 00000000000..48c45996da9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/RemoveMemberForm-test.tsx @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as React from 'react'; +import { shallow } from 'enzyme'; +import { mockEvent } from '../../../helpers/testUtils'; +import RemoveMemberForm from '../RemoveMemberForm'; + +const member = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }; +const organization = { key: 'myorg', name: 'MyOrg' }; + +it('should render ', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); +}); + +it('should correctly handle user interactions', () => { + const removeMember = jest.fn(); + const wrapper = shallow( + + ); + (wrapper.instance() as RemoveMemberForm).handleSubmit(mockEvent as any); + expect(removeMember).toBeCalledWith({ + avatar: '', + groupCount: 3, + login: 'admin', + name: 'Admin Istrator' + }); +}); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/AddMemberForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/AddMemberForm-test.tsx.snap new file mode 100644 index 00000000000..ca0444d7328 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/AddMemberForm-test.tsx.snap @@ -0,0 +1,77 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render and open the modal 1`] = ` + + + +`; + +exports[`should render and open the modal 2`] = ` + + + +
+

+ users.add +

+
+
+
+
+ + +
+
+
+
+ + organization.members.add_to_members + + + cancel + +
+
+
+
+
+`; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap new file mode 100644 index 00000000000..660a194e06c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap @@ -0,0 +1,97 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should correctly handle the submit event and close the modal 1`] = ` +Array [ + Array [ + Object { + "avatar": "", + "groupCount": 3, + "login": "admin", + "name": "Admin Istrator", + }, + Array [ + "7", + ], + Array [ + "11", + ], + ], +] +`; + +exports[`should correctly handle the submit event and close the modal 2`] = ` +Object { + "loading": false, + "userGroups": Object { + "11": Object { + "description": "Technical accounts", + "id": 11, + "name": "pull-request-analysers", + "selected": true, + "status": "remove", + }, + "7": Object { + "status": "add", + }, + }, +} +`; + +exports[`should correctly select the groups 1`] = ` +Object { + "11": Object { + "description": "Technical accounts", + "id": 11, + "name": "pull-request-analysers", + "selected": true, + "status": "remove", + }, + "7": Object { + "status": "add", + }, +} +`; + +exports[`should render 1`] = ` + +
+

+ organization.members.manage_groups +

+
+
+
+ + organization.members.members_groups.Admin Istrator + + + +
+
+
+ + save + + + cancel + +
+
+ +
+`; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersList-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersList-test.tsx.snap new file mode 100644 index 00000000000..9635b1e2685 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersList-test.tsx.snap @@ -0,0 +1,62 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render "no results" 1`] = ` +
+ no_results +
+`; + +exports[`should render a list of members of an organization 1`] = ` +
+ + + + + +
+
+`; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListHeader-test.tsx.snap new file mode 100644 index 00000000000..7bfe2725e60 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListHeader-test.tsx.snap @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render with the total 1`] = ` +
+ + + + 8 + + + organization.members.members + +
+`; + +exports[`should render without the total 1`] = ` +
+ +
+`; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListItem-test.tsx.snap new file mode 100644 index 00000000000..2f04b608477 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListItem-test.tsx.snap @@ -0,0 +1,129 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should groups at 0 if the groupCount field is not defined (just added user) 1`] = ` + + + + + + + John Doe + + + john + + + + organization.members.x_groups.0 + + + + + + organization.members.manage_groups + + + + organization.members.remove + + + + + +`; + +exports[`should not render actions and groups for non admin 1`] = ` + + + + + + + Admin Istrator + + + admin + + + +`; + +exports[`should render actions and groups for admin 1`] = ` + + + + + + + Admin Istrator + + + admin + + + + organization.members.x_groups.3 + + + + + + organization.members.manage_groups + + + + organization.members.remove + + + + + +`; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap new file mode 100644 index 00000000000..86a16038cda --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` +
+

+ organization.members.page +

+ + + children test + +

+ + organization.members.manage_a_team + , + } + } + /> +

+
+`; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap new file mode 100644 index 00000000000..336f1e66691 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap @@ -0,0 +1,176 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should fetch members and groups and render for admin 1`] = ` +
+ + + +
+ + +
+
+ + + + + +
+`; + +exports[`should fetch members and render for non-admin 1`] = ` +
+ + + +
+`; + +exports[`should fetch members and render for non-admin 2`] = ` +
+ + + + + + + + +
+`; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap new file mode 100644 index 00000000000..c4a8afef3ef --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` + +
+

+ users.remove +

+
+
+
+ organization.members.remove_x.Admin Istrator.MyOrg +
+
+
+ + remove + + + cancel + +
+
+
+
+`; diff --git a/server/sonar-web/src/main/js/apps/organizations/actions.ts b/server/sonar-web/src/main/js/apps/organizations/actions.ts index 7f5d8897a97..ee00b6b3147 100644 --- a/server/sonar-web/src/main/js/apps/organizations/actions.ts +++ b/server/sonar-web/src/main/js/apps/organizations/actions.ts @@ -20,16 +20,10 @@ import { Dispatch } from 'redux'; import * as api from '../../api/organizations'; import * as actions from '../../store/organizations/duck'; -import * as membersActions from '../../store/organizationsMembers/actions'; -import { searchUsersGroups, addUserToGroup, removeUserFromGroup } from '../../api/user_groups'; -import { receiveUser } from '../../store/users/actions'; import { onFail } from '../../store/rootActions'; -import { getOrganizationMembersState } from '../../store/rootReducer'; import { addGlobalSuccessMessage } from '../../store/globalMessages/duck'; import { translate, translateWithParameters } from '../../helpers/l10n'; -import { Organization, OrganizationMember, OrganizationBase } from '../../app/types'; - -const PAGE_SIZE = 50; +import { Organization, OrganizationBase } from '../../app/types'; const onRejected = (dispatch: Dispatch) => (error: any) => { onFail(dispatch)(error); @@ -48,12 +42,6 @@ export const fetchOrganization = (key: string) => (dispatch: Dispatch) => { ); }; -export const fetchOrganizationGroups = (organization: string) => (dispatch: Dispatch) => { - return searchUsersGroups({ organization }).then(response => { - dispatch(actions.receiveOrganizationGroups(organization, response.groups)); - }, onFail(dispatch)); -}; - export const createOrganization = (organization: OrganizationBase) => (dispatch: Dispatch) => { return api.createOrganization(organization).then((organization: Organization) => { dispatch(actions.createOrganization(organization)); @@ -79,96 +67,3 @@ export const deleteOrganization = (key: string) => (dispatch: Dispatch) => dispatch(addGlobalSuccessMessage(translate('organization.deleted'))); }, onFail(dispatch)); }; - -const fetchMembers = ( - data: { - organization: string; - p?: number; - ps?: number; - q?: string; - }, - dispatch: Dispatch, - receiveAction: Function -) => { - dispatch(membersActions.updateState(data.organization, { loading: true })); - if (data.ps === undefined) { - data.ps = PAGE_SIZE; - } - if (!data.q) { - data.q = undefined; - } - return api.searchMembers(data).then( - response => { - dispatch( - receiveAction(data.organization, response.users, { - loading: false, - total: response.paging.total, - pageIndex: response.paging.pageIndex, - query: data.q || null - }) - ); - }, - (error: any) => { - onFail(dispatch)(error); - dispatch(membersActions.updateState(data.organization, { loading: false })); - } - ); -}; - -export const fetchOrganizationMembers = (key: string, query?: string) => ( - dispatch: Dispatch -) => fetchMembers({ organization: key, q: query }, dispatch, membersActions.receiveMembers); - -export const fetchMoreOrganizationMembers = (key: string, query?: string) => ( - dispatch: Dispatch, - getState: () => any -) => - fetchMembers( - { organization: key, p: getOrganizationMembersState(getState(), key).pageIndex + 1, q: query }, - dispatch, - membersActions.receiveMoreMembers - ); - -export const addOrganizationMember = (key: string, member: OrganizationMember) => ( - dispatch: Dispatch -) => { - return api - .addMember({ login: member.login, organization: key }) - .then(user => dispatch(membersActions.addMember(key, user)), onFail(dispatch)); -}; - -export const removeOrganizationMember = (key: string, member: OrganizationMember) => ( - dispatch: Dispatch -) => { - dispatch(membersActions.removeMember(key, member)); - return api.removeMember({ login: member.login, organization: key }).catch((error: any) => { - onFail(dispatch)(error); - dispatch(membersActions.addMember(key, member)); - }); -}; - -export const updateOrganizationMemberGroups = ( - organization: Organization, - member: OrganizationMember, - add: string[], - remove: string[] -) => (dispatch: Dispatch) => { - dispatch( - receiveUser({ - ...member, - groupCount: (member.groupCount || 0) + add.length - remove.length - }) - ); - const promises = [ - ...add.map(name => - addUserToGroup({ name, login: member.login, organization: organization.key }) - ), - ...remove.map(name => - removeUserFromGroup({ name, login: member.login, organization: organization.key }) - ) - ]; - return Promise.all(promises).catch(error => { - dispatch(receiveUser(member)); - onFail(dispatch)(error); - }); -}; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersList.tsx b/server/sonar-web/src/main/js/apps/organizations/components/MembersList.tsx deleted file mode 100644 index 641486606b5..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/MembersList.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as React from 'react'; -import MembersListItem from './MembersListItem'; -import { Group, Organization, OrganizationMember } from '../../../app/types'; - -interface Props { - members: OrganizationMember[]; - organizationGroups: Group[]; - organization: Organization; - removeMember: (member: OrganizationMember) => void; - updateMemberGroups: ( - member: OrganizationMember, - add: Array, - remove: Array - ) => void; -} - -export default class MembersList extends React.PureComponent { - render() { - return ( -
- - - {this.props.members.map(member => ( - - ))} - -
-
- ); - } -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.tsx b/server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.tsx deleted file mode 100644 index b703131b34a..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as React from 'react'; -import SearchBox from '../../../components/controls/SearchBox'; -import { formatMeasure } from '../../../helpers/measures'; -import { translate } from '../../../helpers/l10n'; - -interface Props { - handleSearch: (query?: string) => void; - total?: number; -} - -export default function MembersListHeader({ handleSearch, total }: Props) { - return ( -
- - {total != null && ( - - {formatMeasure(total, 'INT')} {translate('organization.members.members')} - - )} -
- ); -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.tsx b/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.tsx deleted file mode 100644 index 99d71d9a4fa..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.tsx +++ /dev/null @@ -1,135 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as React from 'react'; -import RemoveMemberForm from './forms/RemoveMemberForm'; -import ManageMemberGroupsForm from './forms/ManageMemberGroupsForm'; -import Avatar from '../../../components/ui/Avatar'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { formatMeasure } from '../../../helpers/measures'; -import ActionsDropdown, { - ActionsDropdownDivider, - ActionsDropdownItem -} from '../../../components/controls/ActionsDropdown'; -import { Group, Organization, OrganizationMember } from '../../../app/types'; - -interface Props { - member: OrganizationMember; - organization: Organization; - organizationGroups: Group[]; - removeMember: (member: OrganizationMember) => void; - updateMemberGroups: (member: OrganizationMember, add: string[], remove: string[]) => void; -} - -interface State { - removeMemberForm: boolean; - manageGroupsForm: boolean; -} - -const AVATAR_SIZE = 36; - -export default class MembersListItem extends React.PureComponent { - mounted = false; - state: State = { removeMemberForm: false, manageGroupsForm: false }; - - componentDidMount() { - this.mounted = true; - } - - componentWillUnmount() { - this.mounted = false; - } - - handleManageGroupsClick = () => { - this.setState({ manageGroupsForm: true }); - }; - - closeManageGroupsForm = () => { - if (this.mounted) { - this.setState({ manageGroupsForm: false }); - } - }; - - handleRemoveMemberClick = () => { - this.setState({ removeMemberForm: true }); - }; - - closeRemoveMemberForm = () => { - if (this.mounted) { - this.setState({ removeMemberForm: false }); - } - }; - - render() { - const { member, organization } = this.props; - return ( - - - - - - {member.name} - {member.login} - - {organization.canAdmin && ( - - {translateWithParameters( - 'organization.members.x_groups', - formatMeasure(member.groupCount || 0, 'INT') - )} - - )} - {organization.canAdmin && ( - - - - - {translate('organization.members.manage_groups')} - - - - {translate('organization.members.remove')} - - - - - {this.state.manageGroupsForm && ( - - )} - - {this.state.removeMemberForm && ( - - )} - - )} - - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersPageHeader.tsx b/server/sonar-web/src/main/js/apps/organizations/components/MembersPageHeader.tsx deleted file mode 100644 index 9481d010480..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/MembersPageHeader.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { Link } from 'react-router'; -import { translate } from '../../../helpers/l10n'; -import DeferredSpinner from '../../../components/common/DeferredSpinner'; - -interface Props { - children?: React.ReactNode; - loading: boolean; -} - -export default function MembersPageHeader(props: Props) { - return ( -
-

{translate('organization.members.page')}

- - {props.children} -

- - {translate('organization.members.manage_a_team')} - - ) - }} - /> -

-
- ); -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.tsx deleted file mode 100644 index 86e505b33b3..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.tsx +++ /dev/null @@ -1,119 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as React from 'react'; -import Helmet from 'react-helmet'; -import MembersPageHeader from './MembersPageHeader'; -import MembersListHeader from './MembersListHeader'; -import MembersList from './MembersList'; -import AddMemberForm from './forms/AddMemberForm'; -import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; -import ListFooter from '../../../components/controls/ListFooter'; -import DocTooltip from '../../../components/docs/DocTooltip'; -import { translate } from '../../../helpers/l10n'; -import { Group, Organization, OrganizationMember } from '../../../app/types'; - -interface Props { - addOrganizationMember: (organizationKey: string, member: OrganizationMember) => void; - fetchMoreOrganizationMembers: (organizationKey: string, query?: string) => void; - fetchOrganizationGroups: (organizationKey: string) => void; - fetchOrganizationMembers: (organizationKey: string, query?: string) => void; - members: OrganizationMember[]; - memberLogins: string[]; - organization: Organization; - organizationGroups: Group[]; - removeOrganizationMember: (organizationKey: string, member: OrganizationMember) => void; - status: { loading?: boolean; total?: number; pageIndex?: number; query?: string }; - updateOrganizationMemberGroups: ( - organization: Organization, - member: OrganizationMember, - add: string[], - remove: string[] - ) => void; -} - -export default class OrganizationMembers extends React.PureComponent { - componentDidMount() { - this.handleSearchMembers(); - if (this.props.organization.canAdmin) { - this.props.fetchOrganizationGroups(this.props.organization.key); - } - } - - handleSearchMembers = (query?: string) => { - this.props.fetchOrganizationMembers(this.props.organization.key, query); - }; - - handleLoadMoreMembers = () => { - this.props.fetchMoreOrganizationMembers(this.props.organization.key, this.props.status.query); - }; - - addMember = (member: OrganizationMember) => { - this.props.addOrganizationMember(this.props.organization.key, member); - }; - - removeMember = (member: OrganizationMember) => { - this.props.removeOrganizationMember(this.props.organization.key, member); - }; - - updateMemberGroups = (member: OrganizationMember, add: string[], remove: string[]) => { - this.props.updateOrganizationMemberGroups(this.props.organization, member, add, remove); - }; - - render() { - const { organization, status, members } = this.props; - return ( -
- - - - {organization.canAdmin && ( -
- - -
- )} -
- - - {status.total != null && ( - - )} -
- ); - } -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.tsx deleted file mode 100644 index 01226a51009..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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, - getOrganizationGroupsByKey, - getOrganizationMembersLogins, - getUsersByLogins, - getOrganizationMembersState -} from '../../../store/rootReducer'; -import { - fetchOrganizationMembers, - fetchMoreOrganizationMembers, - fetchOrganizationGroups, - addOrganizationMember, - removeOrganizationMember, - updateOrganizationMemberGroups -} from '../actions'; -import { Organization, OrganizationMember, Group } from '../../../app/types'; - -interface OwnProps { - params: { organizationKey: string }; -} - -interface StateProps { - memberLogins: string[]; - members: OrganizationMember[]; - organization: Organization; - organizationGroups: Group[]; - status: { loading?: boolean; total?: number; pageIndex?: number; query?: string }; -} - -interface DispatchProps { - addOrganizationMember: (organizationKey: string, member: OrganizationMember) => void; - fetchMoreOrganizationMembers: (organizationKey: string, query?: string) => void; - fetchOrganizationGroups: (organizationKey: string) => void; - fetchOrganizationMembers: (organizationKey: string, query?: string) => void; - removeOrganizationMember: (organizationKey: string, member: OrganizationMember) => void; - updateOrganizationMemberGroups: ( - organization: Organization, - member: OrganizationMember, - add: string[], - remove: string[] - ) => void; -} - -const mapStateToProps = (state: any, ownProps: OwnProps): StateProps => { - const { organizationKey } = ownProps.params; - const memberLogins = getOrganizationMembersLogins(state, organizationKey); - return { - memberLogins, - members: getUsersByLogins(state, memberLogins), - organization: getOrganizationByKey(state, organizationKey)!, - organizationGroups: getOrganizationGroupsByKey(state, organizationKey), - status: getOrganizationMembersState(state, organizationKey) - }; -}; - -const mapDispatchToProps = { - addOrganizationMember, - fetchMoreOrganizationMembers, - fetchOrganizationGroups, - fetchOrganizationMembers, - removeOrganizationMember, - updateOrganizationMemberGroups -}; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(OrganizationMembers); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersList-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersList-test.tsx deleted file mode 100644 index 0910f62995c..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersList-test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as 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__/MembersListHeader-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.tsx deleted file mode 100644 index ffc4241d145..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as React from 'react'; -import { shallow } from 'enzyme'; -import MembersListHeader from '../MembersListHeader'; - -it('should render without the total', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); -}); - -it('should render with the total', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.tsx deleted file mode 100644 index 9838194849f..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as 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' }; - -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(); -}); - -it('should groups at 0 if the groupCount field is not defined (just added user)', () => { - const wrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersPageHeader-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersPageHeader-test.tsx deleted file mode 100644 index eb2dbe02f4d..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersPageHeader-test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as React from 'react'; -import { shallow } from 'enzyme'; -import MembersPageHeader from '../MembersPageHeader'; - -it('should render', () => { - const wrapper = shallow( - - children test - - ); - expect(wrapper).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.tsx deleted file mode 100644 index de18ce76e3b..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as 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 }; - -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__/__snapshots__/MembersList-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersList-test.tsx.snap deleted file mode 100644 index 57ff0c52ea6..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersList-test.tsx.snap +++ /dev/null @@ -1,54 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render a list of members of an organization 1`] = ` -
- - - - - -
-
-`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListHeader-test.tsx.snap deleted file mode 100644 index 7bfe2725e60..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListHeader-test.tsx.snap +++ /dev/null @@ -1,34 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render with the total 1`] = ` -
- - - - 8 - - - organization.members.members - -
-`; - -exports[`should render without the total 1`] = ` -
- -
-`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.tsx.snap deleted file mode 100644 index 2f04b608477..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.tsx.snap +++ /dev/null @@ -1,129 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should groups at 0 if the groupCount field is not defined (just added user) 1`] = ` - - - - - - - John Doe - - - john - - - - organization.members.x_groups.0 - - - - - - organization.members.manage_groups - - - - organization.members.remove - - - - - -`; - -exports[`should not render actions and groups for non admin 1`] = ` - - - - - - - Admin Istrator - - - admin - - - -`; - -exports[`should render actions and groups for admin 1`] = ` - - - - - - - Admin Istrator - - - admin - - - - organization.members.x_groups.3 - - - - - - organization.members.manage_groups - - - - organization.members.remove - - - - - -`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap deleted file mode 100644 index 86a16038cda..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap +++ /dev/null @@ -1,39 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render 1`] = ` -
-

- organization.members.page -

- - - children test - -

- - organization.members.manage_a_team - , - } - } - /> -

-
-`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap deleted file mode 100644 index 094f4735c81..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap +++ /dev/null @@ -1,132 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should not render actions for non admin 1`] = ` -
- - - - - - -
-`; - -exports[`should render actions for admin 1`] = ` -
- - - -
- - -
-
- - - -
-`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.tsx b/server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.tsx deleted file mode 100644 index 00408077132..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as React from 'react'; -import UsersSelectSearch from '../../../users/components/UsersSelectSearch'; -import { searchMembers } from '../../../../api/organizations'; -import Modal from '../../../../components/controls/Modal'; -import { translate } from '../../../../helpers/l10n'; -import { SubmitButton, ResetButtonLink, Button } from '../../../../components/ui/buttons'; -import { Organization, OrganizationMember } from '../../../../app/types'; - -interface Props { - addMember: (member: OrganizationMember) => void; - organization: Organization; - memberLogins: string[]; -} - -interface State { - open: boolean; - selectedMember?: OrganizationMember; -} - -export default class AddMemberForm extends React.PureComponent { - state: State = { - open: false - }; - - openForm = () => { - this.setState({ open: true }); - }; - - closeForm = () => { - this.setState({ open: false, selectedMember: undefined }); - }; - - handleSearch = (query: string | undefined, ps: number) => { - const data = { organization: this.props.organization.key, ps, selected: 'deselected' }; - if (!query) { - return searchMembers(data); - } - return searchMembers({ ...data, q: query }); - }; - - handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - if (this.state.selectedMember) { - this.props.addMember(this.state.selectedMember); - this.closeForm(); - } - }; - - selectedMemberChange = (member: OrganizationMember) => { - this.setState({ selectedMember: member }); - }; - - renderModal() { - const header = translate('users.add'); - return ( - -
-

{header}

-
-
-
-
- - -
-
-
-
- - {translate('organization.members.add_to_members')} - - {translate('cancel')} -
-
-
-
- ); - } - - render() { - const buttonComponent = ( - - ); - if (this.state.open) { - return [buttonComponent, this.renderModal()]; - } - return buttonComponent; - } -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.tsx b/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.tsx deleted file mode 100644 index c9c963b3c1b..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.tsx +++ /dev/null @@ -1,146 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as React from 'react'; -import { keyBy, pickBy } from 'lodash'; -import { getUserGroups, UserGroup } from '../../../../api/users'; -import Modal from '../../../../components/controls/Modal'; -import { translate, translateWithParameters } from '../../../../helpers/l10n'; -import OrganizationGroupCheckbox from '../OrganizationGroupCheckbox'; -import { SubmitButton, ResetButtonLink } from '../../../../components/ui/buttons'; -import { Organization, OrganizationMember, Group } from '../../../../app/types'; - -interface Props { - onClose: () => void; - member: OrganizationMember; - organization: Organization; - organizationGroups: Group[]; - updateMemberGroups: (member: OrganizationMember, add: string[], remove: string[]) => void; -} - -interface State { - userGroups?: { [k: string]: UserGroup & { status?: string } }; - loading?: boolean; -} - -export default class ManageMemberGroupsForm extends React.PureComponent { - mounted = false; - state: State = {}; - - componentDidMount() { - this.mounted = true; - this.loadUserGroups(); - } - - componentWillUnmount() { - this.mounted = false; - } - - loadUserGroups = () => { - this.setState({ loading: true }); - getUserGroups(this.props.member.login, this.props.organization.key).then( - response => { - if (this.mounted) { - this.setState({ loading: false, userGroups: keyBy(response.groups, 'name') }); - } - }, - () => { - if (this.mounted) { - this.setState({ loading: false }); - } - } - ); - }; - - isGroupSelected = (groupName: string) => { - if (this.state.userGroups) { - const group = this.state.userGroups[groupName] || {}; - if (group.status) { - return group.status === 'add'; - } else { - return group.selected === true; - } - } - return false; - }; - - onCheck = (groupName: string, checked: boolean) => { - this.setState((prevState: State) => { - const userGroups = prevState.userGroups || {}; - const group = userGroups[groupName] || {}; - let status = ''; - if (group.selected && !checked) { - status = 'remove'; - } else if (!group.selected && checked) { - status = 'add'; - } - return { userGroups: { ...userGroups, [groupName]: { ...group, status } } }; - }); - }; - - handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - 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.props.onClose(); - }; - - render() { - const header = translate('organization.members.manage_groups'); - return ( - -
-

{header}

-
-
-
- - {translateWithParameters( - 'organization.members.members_groups', - this.props.member.name - )} - {' '} - {this.state.loading && } - {!this.state.loading && ( -
    - {this.props.organizationGroups.map(group => ( - - ))} -
- )} -
-
-
- {translate('save')} - {translate('cancel')} -
-
- -
- ); - } -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.tsx b/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.tsx deleted file mode 100644 index c6e0d603dd2..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as React from 'react'; -import Modal from '../../../../components/controls/Modal'; -import { translate, translateWithParameters } from '../../../../helpers/l10n'; -import { SubmitButton, ResetButtonLink } from '../../../../components/ui/buttons'; -import { Organization, OrganizationMember } from '../../../../app/types'; - -interface Props { - onClose: () => void; - member: OrganizationMember; - organization: Organization; - removeMember: (member: OrganizationMember) => void; -} - -export default class RemoveMemberForm extends React.PureComponent { - handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - this.props.removeMember(this.props.member); - this.props.onClose(); - }; - - render() { - const header = translate('users.remove'); - return ( - -
-

{header}

-
-
-
- {translateWithParameters( - 'organization.members.remove_x', - this.props.member.name, - this.props.organization.name - )} -
-
-
- - {translate('remove')} - - {translate('cancel')} -
-
-
-
- ); - } -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.tsx deleted file mode 100644 index 3406cd7361d..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as React from 'react'; -import { shallow } from 'enzyme'; -import { click } from '../../../../../helpers/testUtils'; -import AddMemberForm from '../AddMemberForm'; - -const memberLogins = ['admin']; - -it('should render and open the modal', () => { - const wrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - wrapper.setState({ open: true }); - - // FIXME Can probably be removed when https://github.com/airbnb/enzyme/issues/1149 is resolved - expect(wrapper.first().getElements()).toMatchSnapshot(); -}); - -it('should correctly handle user interactions', () => { - const wrapper = shallow( - - ); - click(wrapper.find('Button')); - expect(wrapper.state('open')).toBeTruthy(); - (wrapper.instance() as AddMemberForm).closeForm(); - expect(wrapper.state('open')).toBeFalsy(); -}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.tsx deleted file mode 100644 index 32b0839c275..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.tsx +++ /dev/null @@ -1,101 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as React from 'react'; -import { shallow } from 'enzyme'; -import { mockEvent } from '../../../../../helpers/testUtils'; -import ManageMemberGroupsForm from '../ManageMemberGroupsForm'; - -const member = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }; -const organization = { name: 'MyOrg', key: 'myorg' }; -const organizationGroups = [ - { - id: 7, - name: 'professionals', - description: '', - membersCount: 12 - }, - { - id: 11, - name: 'pull-request-analysers', - description: 'Technical accounts', - membersCount: 3 - }, - { - id: 1, - name: 'sonar-administrators', - description: 'System administrators', - membersCount: 17 - } -]; -const userGroups = { - 11: { id: 11, name: 'pull-request-analysers', description: 'Technical accounts', selected: true } -}; - -function getMountedForm(updateFunc = jest.fn()) { - const wrapper = shallow( - , - { disableLifecycleMethods: true } - ); - const instance = wrapper.instance(); - wrapper.setState({ loading: false, userGroups }); - return { wrapper, instance }; -} - -it('should render', () => { - const wrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); -}); - -it('should correctly select the groups', () => { - const form = getMountedForm(); - const instance = form.instance as ManageMemberGroupsForm; - expect(instance.isGroupSelected('11')).toBeTruthy(); - expect(instance.isGroupSelected('7')).toBeFalsy(); - instance.onCheck('11', false); - instance.onCheck('7', true); - expect(form.wrapper.state('userGroups')).toMatchSnapshot(); - expect(instance.isGroupSelected('11')).toBeFalsy(); - expect(instance.isGroupSelected('7')).toBeTruthy(); -}); - -it('should correctly handle the submit event and close the modal', () => { - const updateMemberGroups = jest.fn(); - const form = getMountedForm(updateMemberGroups); - const instance = form.instance as ManageMemberGroupsForm; - instance.onCheck('11', false); - instance.onCheck('7', true); - instance.handleSubmit(mockEvent as any); - expect(updateMemberGroups.mock.calls).toMatchSnapshot(); - expect(form.wrapper.state()).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.tsx deleted file mode 100644 index 92fbdb9723d..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 * as React from 'react'; -import { shallow } from 'enzyme'; -import { mockEvent } from '../../../../../helpers/testUtils'; -import RemoveMemberForm from '../RemoveMemberForm'; - -const member = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }; -const organization = { key: 'myorg', name: 'MyOrg' }; - -it('should render ', () => { - const wrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); -}); - -it('should correctly handle user interactions', () => { - const removeMember = jest.fn(); - const wrapper = shallow( - - ); - (wrapper.instance() as RemoveMemberForm).handleSubmit(mockEvent as any); - expect(removeMember).toBeCalledWith({ - avatar: '', - groupCount: 3, - login: 'admin', - name: 'Admin Istrator' - }); -}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.tsx.snap deleted file mode 100644 index d480a393f5b..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.tsx.snap +++ /dev/null @@ -1,73 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render and open the modal 1`] = ` - -`; - -exports[`should render and open the modal 2`] = ` -Array [ - , - -
-

- users.add -

-
-
-
-
- - -
-
-
-
- - organization.members.add_to_members - - - cancel - -
-
-
-
, -] -`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap deleted file mode 100644 index 660a194e06c..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap +++ /dev/null @@ -1,97 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should correctly handle the submit event and close the modal 1`] = ` -Array [ - Array [ - Object { - "avatar": "", - "groupCount": 3, - "login": "admin", - "name": "Admin Istrator", - }, - Array [ - "7", - ], - Array [ - "11", - ], - ], -] -`; - -exports[`should correctly handle the submit event and close the modal 2`] = ` -Object { - "loading": false, - "userGroups": Object { - "11": Object { - "description": "Technical accounts", - "id": 11, - "name": "pull-request-analysers", - "selected": true, - "status": "remove", - }, - "7": Object { - "status": "add", - }, - }, -} -`; - -exports[`should correctly select the groups 1`] = ` -Object { - "11": Object { - "description": "Technical accounts", - "id": 11, - "name": "pull-request-analysers", - "selected": true, - "status": "remove", - }, - "7": Object { - "status": "add", - }, -} -`; - -exports[`should render 1`] = ` - -
-

- organization.members.manage_groups -

-
-
-
- - organization.members.members_groups.Admin Istrator - - - -
-
-
- - save - - - cancel - -
-
- -
-`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap deleted file mode 100644 index c4a8afef3ef..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap +++ /dev/null @@ -1,43 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render 1`] = ` - -
-

- users.remove -

-
-
-
- organization.members.remove_x.Admin Istrator.MyOrg -
-
-
- - remove - - - cancel - -
-
-
-
-`; diff --git a/server/sonar-web/src/main/js/apps/organizations/routes.ts b/server/sonar-web/src/main/js/apps/organizations/routes.ts index b6fa1ef378e..d78634091ec 100644 --- a/server/sonar-web/src/main/js/apps/organizations/routes.ts +++ b/server/sonar-web/src/main/js/apps/organizations/routes.ts @@ -60,7 +60,7 @@ const routes = [ }, { path: 'members', - component: lazyLoad(() => import('./components/OrganizationMembersContainer')) + component: lazyLoad(() => import('../organizationMembers/OrganizationMembersContainer')) }, { path: 'quality_profiles', diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.js b/server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.js index 6a8777641c8..0b45197d190 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.js +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.js @@ -21,11 +21,10 @@ import { connect } from 'react-redux'; import App from './App'; import forSingleOrganization from '../../organizations/forSingleOrganization'; import { getAppState } from '../../../store/rootReducer'; -import { getRootQualifiers } from '../../../store/appState/duck'; const mapStateToProps = state => ({ // treat applications as portfolios - topQualifiers: getRootQualifiers(getAppState(state)).filter(q => q !== 'APP') + topQualifiers: getAppState(state).qualifiers.filter(q => q !== 'APP') }); export default forSingleOrganization(connect(mapStateToProps)(App)); diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js b/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js index c00df0c9e03..ee9db3cf5ff 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js +++ b/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js @@ -20,14 +20,10 @@ import { combineReducers } from 'redux'; import components, { getComponentByKey as nextGetComponentByKey } from './components'; import modulesByProject, { getProjectModules as nextGetProjectModules } from './modulesByProject'; -import globalMessages, { - getGlobalMessages as nextGetGlobalMessages -} from '../../../store/globalMessages/duck'; const rootReducer = combineReducers({ components, - modulesByProject, - globalMessages + modulesByProject }); export default rootReducer; @@ -42,5 +38,3 @@ export const getProjectModules = (state, projectKey) => { } return moduleKeys.map(moduleKey => getComponentByKey(state, moduleKey)); }; - -export const getGlobalMessages = state => nextGetGlobalMessages(state.globalMessages); diff --git a/server/sonar-web/src/main/js/store/appState/duck.ts b/server/sonar-web/src/main/js/store/appState/duck.ts index 89f3613fd0f..79ada585879 100644 --- a/server/sonar-web/src/main/js/store/appState/duck.ts +++ b/server/sonar-web/src/main/js/store/appState/duck.ts @@ -72,7 +72,3 @@ export default function(state: AppState = defaultValue, action: Action): AppStat return state; } - -export function getRootQualifiers(state: AppState): string[] | undefined { - return state.qualifiers; -} diff --git a/server/sonar-web/src/main/js/store/globalMessages/duck.js b/server/sonar-web/src/main/js/store/globalMessages/duck.js index e5ee4d3a7e9..81c4cba0ff6 100644 --- a/server/sonar-web/src/main/js/store/globalMessages/duck.js +++ b/server/sonar-web/src/main/js/store/globalMessages/duck.js @@ -40,8 +40,8 @@ export type State = Array; type Action = Object; */ -export const ERROR = 'ERROR'; -export const SUCCESS = 'SUCCESS'; +const ERROR = 'ERROR'; +const SUCCESS = 'SUCCESS'; /* Actions */ const ADD_GLOBAL_MESSAGE = 'ADD_GLOBAL_MESSAGE'; diff --git a/server/sonar-web/src/main/js/store/metrics/reducer.js b/server/sonar-web/src/main/js/store/metrics/reducer.js index c93aa635941..d2d5284cc3a 100644 --- a/server/sonar-web/src/main/js/store/metrics/reducer.js +++ b/server/sonar-web/src/main/js/store/metrics/reducer.js @@ -47,5 +47,4 @@ const keys = (state /*: StateKeys */ = [], action = {}) => { export default combineReducers({ byKey, keys }); export const getMetrics = (state /*: State */) => state.byKey; -export const getMetricByKey = (state /*: State */, key /*: string */) => state.byKey[key]; export const getMetricsKey = (state /*: State */) => state.keys; diff --git a/server/sonar-web/src/main/js/store/organizations/utils.ts b/server/sonar-web/src/main/js/store/organizations/utils.ts deleted file mode 100644 index 5b3957e1f6c..00000000000 --- a/server/sonar-web/src/main/js/store/organizations/utils.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 getStore from '../../app/utils/getStore'; -import { - getOrganizationByKey, - areThereCustomOrganizations as customOrganizations -} from '../rootReducer'; - -export function getOrganization(key: string) { - const store = getStore(); - const state = store.getState(); - return getOrganizationByKey(state, key); -} - -export function areThereCustomOrganizations() { - const store = getStore(); - const state = store.getState(); - return customOrganizations(state); -} diff --git a/server/sonar-web/src/main/js/store/organizationsMembers/actions.js b/server/sonar-web/src/main/js/store/organizationsMembers/actions.js deleted file mode 100644 index a56abe68722..00000000000 --- a/server/sonar-web/src/main/js/store/organizationsMembers/actions.js +++ /dev/null @@ -1,86 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 -/*:: -export type Member = { - login: string, - name: string, - avatar?: string, - email?: string, - groupCount?: number -}; -*/ - -/*:: -type MembersState = { - paging?: number, - total?: number, - loading?: boolean, - query?: string | null -}; -*/ - -export const actions = { - UPDATE_STATE: 'organizations/UPDATE_STATE', - RECEIVE_MEMBERS: 'organizations/RECEIVE_MEMBERS', - RECEIVE_MORE_MEMBERS: 'organizations/RECEIVE_MORE_MEMBERS', - ADD_MEMBER: 'organizations/ADD_MEMBER', - REMOVE_MEMBER: 'organizations/REMOVE_MEMBER' -}; - -export const receiveMembers = ( - organizationKey /*: string */, - members /*: Array */, - stateChanges /*: MembersState */ -) => ({ - type: actions.RECEIVE_MEMBERS, - organization: organizationKey, - members, - stateChanges -}); - -export const receiveMoreMembers = ( - organizationKey /*: string */, - members /*: Array */, - stateChanges /*: MembersState */ -) => ({ - type: actions.RECEIVE_MORE_MEMBERS, - organization: organizationKey, - members, - stateChanges -}); - -export const addMember = (organizationKey /*: string */, member /*: Member */) => ({ - type: actions.ADD_MEMBER, - organization: organizationKey, - member -}); - -export const removeMember = (organizationKey /*: string */, member /*: Member */) => ({ - type: actions.REMOVE_MEMBER, - organization: organizationKey, - member -}); - -export const updateState = (organizationKey /*: string */, stateChanges /*: MembersState */) => ({ - type: actions.UPDATE_STATE, - organization: organizationKey, - stateChanges -}); diff --git a/server/sonar-web/src/main/js/store/organizationsMembers/reducer.js b/server/sonar-web/src/main/js/store/organizationsMembers/reducer.js deleted file mode 100644 index 6e89f760491..00000000000 --- a/server/sonar-web/src/main/js/store/organizationsMembers/reducer.js +++ /dev/null @@ -1,91 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 { uniq } from 'lodash'; -import { actions } from './actions'; - -export const getOrganizationMembersLogins = (state, organization) => { - if (organization && state[organization]) { - return state[organization].members || []; - } - return []; -}; - -export const getOrganizationMembersState = (state, organization) => - organization && state[organization] ? state[organization] : {}; - -const organizationMembers = (state = {}, action = {}) => { - const members = state.members || []; - switch (action.type) { - case actions.UPDATE_STATE: - return { ...state, ...action.stateChanges }; - case actions.RECEIVE_MEMBERS: - return { - ...state, - ...action.stateChanges, - members: action.members.map(member => member.login) - }; - case actions.RECEIVE_MORE_MEMBERS: - return { - ...state, - ...action.stateChanges, - members: uniq(members.concat(action.members.map(member => member.login))) - }; - case actions.ADD_MEMBER: { - const withNew = [...members, action.member.login].sort(); - return { - ...state, - total: state.total + 1, - members: withNew - }; - } - case actions.REMOVE_MEMBER: { - const withoutDeleted = state.members.filter(login => login !== action.member.login); - if (withoutDeleted.length === state.members.length) { - return state; - } - return { - ...state, - total: state.total - 1, - members: withoutDeleted - }; - } - default: - return state; - } -}; - -const organizationsMembers = (state = {}, action = {}) => { - const organization = state[action.organization] || {}; - switch (action.type) { - case actions.UPDATE_STATE: - case actions.RECEIVE_MEMBERS: - case actions.RECEIVE_MORE_MEMBERS: - case actions.ADD_MEMBER: - case actions.REMOVE_MEMBER: - return { - ...state, - [action.organization]: organizationMembers(organization, action) - }; - default: - return state; - } -}; - -export default organizationsMembers; diff --git a/server/sonar-web/src/main/js/store/rootActions.js b/server/sonar-web/src/main/js/store/rootActions.js index 52289a5ccd1..c49e3921c6c 100644 --- a/server/sonar-web/src/main/js/store/rootActions.js +++ b/server/sonar-web/src/main/js/store/rootActions.js @@ -32,12 +32,6 @@ import { parseError } from '../helpers/request'; export const onFail = dispatch => error => parseError(error).then(message => dispatch(addGlobalErrorMessage(message))); -export const fetchAppState = () => dispatch => - getGlobalNavigation().then(appState => { - dispatch(setAppState(appState)); - return appState; - }, onFail(dispatch)); - export const fetchLanguages = () => dispatch => getLanguages().then(languages => dispatch(receiveLanguages(languages)), onFail(dispatch)); diff --git a/server/sonar-web/src/main/js/store/rootReducer.js b/server/sonar-web/src/main/js/store/rootReducer.js index 8bec4555692..16d038d1e89 100644 --- a/server/sonar-web/src/main/js/store/rootReducer.js +++ b/server/sonar-web/src/main/js/store/rootReducer.js @@ -23,7 +23,6 @@ import users, * as fromUsers from './users/reducer'; import languages, * as fromLanguages from './languages/reducer'; import metrics, * as fromMetrics from './metrics/reducer'; import organizations, * as fromOrganizations from './organizations/duck'; -import organizationsMembers, * as fromOrganizationsMembers from './organizationsMembers/reducer'; import globalMessages, * as fromGlobalMessages from './globalMessages/duck'; import permissionsApp, * as fromPermissionsApp from '../apps/permissions/shared/store/rootReducer'; import projectAdminApp, * as fromProjectAdminApp from '../apps/project-admin/store/rootReducer'; @@ -35,7 +34,6 @@ export default combineReducers({ languages, metrics, organizations, - organizationsMembers, users, // apps @@ -51,25 +49,12 @@ export const getGlobalMessages = state => export const getLanguages = state => fromLanguages.getLanguages(state.languages); -export const getLanguageByKey = (state, key) => - fromLanguages.getLanguageByKey(state.languages, key); - export const getCurrentUser = state => fromUsers.getCurrentUser(state.users); -export const getUserLogins = state => fromUsers.getUserLogins(state.users); - -export const getUserByLogin = (state, login) => fromUsers.getUserByLogin(state.users, login); - export const getUsersByLogins = (state, logins) => fromUsers.getUsersByLogins(state.users, logins); -export const getUsers = state => fromUsers.getUsers(state.users); - -export const getMarketplaceState = state => state.marketplace; - export const getMetrics = state => fromMetrics.getMetrics(state.metrics); -export const getMetricByKey = (state, key) => fromMetrics.getMetricByKey(state.metrics, key); - export const getMetricsKey = state => fromMetrics.getMetricsKey(state.metrics); export const getOrganizationByKey = (state, key) => @@ -83,12 +68,6 @@ export const getMyOrganizations = state => export const areThereCustomOrganizations = state => getAppState(state).organizationsEnabled; -export const getOrganizationMembersLogins = (state, organization) => - fromOrganizationsMembers.getOrganizationMembersLogins(state.organizationsMembers, organization); - -export const getOrganizationMembersState = (state, organization) => - fromOrganizationsMembers.getOrganizationMembersState(state.organizationsMembers, organization); - export const getPermissionsAppUsers = state => fromPermissionsApp.getUsers(state.permissionsApp); export const getPermissionsAppGroups = state => fromPermissionsApp.getGroups(state.permissionsApp); @@ -131,14 +110,5 @@ export const getSettingsAppValidationMessage = (state, key) => export const getSettingsAppEncryptionState = state => fromSettingsApp.getEncryptionState(state.settingsApp); -export const getSettingsAppGlobalMessages = state => - fromSettingsApp.getGlobalMessages(state.settingsApp); - -export const getProjectAdminComponentByKey = (state, componentKey) => - fromProjectAdminApp.getComponentByKey(state.projectAdminApp, componentKey); - export const getProjectAdminProjectModules = (state, projectKey) => fromProjectAdminApp.getProjectModules(state.projectAdminApp, projectKey); - -export const getProjectAdminGlobalMessages = state => - fromProjectAdminApp.getGlobalMessages(state.projectAdminApp); diff --git a/server/sonar-web/src/main/js/store/users/actions.ts b/server/sonar-web/src/main/js/store/users/actions.ts index 46c31b7c472..9c9a146e2a4 100644 --- a/server/sonar-web/src/main/js/store/users/actions.ts +++ b/server/sonar-web/src/main/js/store/users/actions.ts @@ -22,7 +22,6 @@ import * as api from '../../api/users'; import { CurrentUser, HomePage } from '../../app/types'; export const RECEIVE_CURRENT_USER = 'RECEIVE_CURRENT_USER'; -export const RECEIVE_USER = 'RECEIVE_USER'; export const SKIP_ONBOARDING = 'SKIP_ONBOARDING'; export const SET_HOMEPAGE = 'SET_HOMEPAGE'; @@ -31,11 +30,6 @@ export const receiveCurrentUser = (user: CurrentUser) => ({ user }); -export const receiveUser = (user: any) => ({ - type: RECEIVE_USER, - user -}); - export const skipOnboarding = () => ({ type: SKIP_ONBOARDING }); export const fetchCurrentUser = () => (dispatch: Dispatch) => { diff --git a/server/sonar-web/src/main/js/store/users/reducer.ts b/server/sonar-web/src/main/js/store/users/reducer.ts index 5f4fddfba20..a6e79db6d97 100644 --- a/server/sonar-web/src/main/js/store/users/reducer.ts +++ b/server/sonar-web/src/main/js/store/users/reducer.ts @@ -18,9 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { combineReducers } from 'redux'; -import { uniq, keyBy } from 'lodash'; -import { RECEIVE_CURRENT_USER, RECEIVE_USER, SKIP_ONBOARDING, SET_HOMEPAGE } from './actions'; -import { actions as membersActions } from '../organizationsMembers/actions'; +import { uniq } from 'lodash'; +import { RECEIVE_CURRENT_USER, SKIP_ONBOARDING, SET_HOMEPAGE } from './actions'; import { CurrentUser } from '../../app/types'; interface UsersByLogin { @@ -28,35 +27,20 @@ interface UsersByLogin { } const usersByLogin = (state: UsersByLogin = {}, action: any = {}) => { - switch (action.type) { - case RECEIVE_CURRENT_USER: - case RECEIVE_USER: - return { ...state, [action.user.login]: action.user }; - case membersActions.RECEIVE_MEMBERS: - case membersActions.RECEIVE_MORE_MEMBERS: - return { ...state, ...keyBy(action.members, 'login') }; - case membersActions.ADD_MEMBER: - return { ...state, [action.member.login]: action.member }; - default: - return state; + if (action.type === RECEIVE_CURRENT_USER) { + return { ...state, [action.user.login]: action.user }; + } else { + return state; } }; type UserLogins = string[]; const userLogins = (state: UserLogins = [], action: any = {}) => { - switch (action.type) { - case RECEIVE_CURRENT_USER: - case RECEIVE_USER: - return uniq([...state, action.user.login]); - case membersActions.RECEIVE_MEMBERS: - case membersActions.RECEIVE_MORE_MEMBERS: - return uniq([...state, action.members.map((member: any) => member.login)]); - case membersActions.ADD_MEMBER: { - return uniq([...state, action.member.login]).sort(); - } - default: - return state; + if (action.type === RECEIVE_CURRENT_USER) { + return uniq([...state, action.user.login]); + } else { + return state; } }; @@ -82,8 +66,6 @@ interface State { export default combineReducers({ usersByLogin, userLogins, currentUser }); export const getCurrentUser = (state: State) => state.currentUser!; -export const getUserLogins = (state: State) => state.userLogins; export const getUserByLogin = (state: State, login: string) => state.usersByLogin[login]; export const getUsersByLogins = (state: State, logins: string[]) => logins.map(login => getUserByLogin(state, login)); -export const getUsers = (state: State) => getUsersByLogins(state, getUserLogins(state)); diff --git a/server/sonar-web/src/main/js/store/utils/generalReducers.js b/server/sonar-web/src/main/js/store/utils/generalReducers.js deleted file mode 100644 index 037761bfddf..00000000000 --- a/server/sonar-web/src/main/js/store/utils/generalReducers.js +++ /dev/null @@ -1,107 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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. - */ -// Author: Christoffer Niska -// https://gist.github.com/crisu83/42ecffccad9d04c74605fbc75c9dc9d1 -import { uniq } from 'lodash'; - -/** - * Creates a reducer that manages a single value. - * - * @param {function(state, action)} shouldUpdate - * @param {function(state, action)} shouldReset - * @param {function(state, action)} getValue - * @param {*} defaultValue - * @returns {function(state, action)} - */ -export const createValue = ( - shouldUpdate = () => true, - shouldReset = () => false, - getValue = (state, action) => action.payload, - defaultValue = null -) => (state = defaultValue, action = {}) => { - if (shouldReset(state, action)) { - return defaultValue; - } - if (shouldUpdate(state, action)) { - return getValue(state, action); - } - return state; -}; - -/** - * Creates a reducer that manages a map. - * - * @param {function(state, action)} shouldUpdate - * @param {function(state, action)} shouldReset - * @param {function(state, action)} getValues - * @returns {function(state, action)} - */ -export const createMap = ( - shouldUpdate = () => true, - shouldReset = () => false, - getValues = (state, action) => action.payload -) => - createValue( - shouldUpdate, - shouldReset, - (state, action) => ({ ...state, ...getValues(state, action) }), - {} - ); - -/** - * Creates a reducer that manages a set. - * - * @param {function(state, action)} shouldUpdate - * @param {function(state, action)} shouldReset - * @param {function(state, action)} getValues - * @returns {function(state, action)} - */ -export const createSet = ( - shouldUpdate = () => true, - shouldReset = () => false, - getValues = (state, action) => action.payload -) => - createValue( - shouldUpdate, - shouldReset, - (state, action) => uniq([...state, ...getValues(state, action)]), - [] - ); - -/** - * Creates a reducer that manages a flag. - * - * @param {function(state, action)} shouldTurnOn - * @param {function(state, action)} shouldTurnOff - * @param {bool} defaultValue - * @returns {function(state, action)} - */ -export const createFlag = (shouldTurnOn, shouldTurnOff, defaultValue = false) => ( - state = defaultValue, - action = {} -) => { - if (shouldTurnOn(state, action)) { - return true; - } - if (shouldTurnOff(state, action)) { - return false; - } - return state; -}; -- cgit v1.2.3