From facddc67e65ce303e40e494d921a53446277cd43 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Wed, 22 Aug 2018 13:49:25 +0200 Subject: [PATCH] drop organization members from redux store (#637) --- .../src/main/js/api/organizations.ts | 17 +- .../AddMemberForm.tsx | 27 +- .../ManageMemberGroupsForm.tsx | 12 +- .../MembersList.tsx | 13 +- .../MembersListHeader.tsx | 8 +- .../MembersListItem.tsx | 14 +- .../MembersPageHeader.tsx | 4 +- .../OrganizationMembers.tsx | 232 ++++++++++++++++++ .../OrganizationMembersContainer.tsx} | 30 ++- .../RemoveMemberForm.tsx | 8 +- .../__tests__/AddMemberForm-test.tsx | 54 +++- .../__tests__/ManageMemberGroupsForm-test.tsx | 2 +- .../__tests__/MembersList-test.tsx | 13 + .../__tests__/MembersListHeader-test.tsx | 0 .../__tests__/MembersListItem-test.tsx | 39 +++ .../__tests__/MembersPageHeader-test.tsx | 0 .../__tests__/OrganizationMembers-test.tsx | 151 ++++++++++++ .../__tests__/RemoveMemberForm-test.tsx | 2 +- .../__snapshots__/AddMemberForm-test.tsx.snap | 24 +- .../ManageMemberGroupsForm-test.tsx.snap | 0 .../__snapshots__/MembersList-test.tsx.snap | 8 + .../MembersListHeader-test.tsx.snap | 0 .../MembersListItem-test.tsx.snap | 0 .../MembersPageHeader-test.tsx.snap | 0 .../OrganizationMembers-test.tsx.snap | 176 +++++++++++++ .../RemoveMemberForm-test.tsx.snap | 0 .../src/main/js/apps/organizations/actions.ts | 107 +------- .../components/OrganizationMembers.tsx | 119 --------- .../OrganizationMembersContainer.tsx | 89 ------- .../__tests__/OrganizationMembers-test.tsx | 67 ----- .../OrganizationMembers-test.tsx.snap | 132 ---------- .../src/main/js/apps/organizations/routes.ts | 2 +- .../components/AppContainer.js | 3 +- .../apps/project-admin/store/rootReducer.js | 8 +- .../src/main/js/store/appState/duck.ts | 4 - .../src/main/js/store/globalMessages/duck.js | 4 +- .../src/main/js/store/metrics/reducer.js | 1 - .../js/store/organizationsMembers/actions.js | 86 ------- .../js/store/organizationsMembers/reducer.js | 91 ------- .../src/main/js/store/rootActions.js | 6 - .../src/main/js/store/rootReducer.js | 30 --- .../src/main/js/store/users/actions.ts | 6 - .../src/main/js/store/users/reducer.ts | 38 +-- .../main/js/store/utils/generalReducers.js | 107 -------- 44 files changed, 775 insertions(+), 959 deletions(-) rename server/sonar-web/src/main/js/apps/{organizations/components/forms => organizationMembers}/AddMemberForm.tsx (82%) rename server/sonar-web/src/main/js/apps/{organizations/components/forms => organizationMembers}/ManageMemberGroupsForm.tsx (91%) rename server/sonar-web/src/main/js/apps/{organizations/components => organizationMembers}/MembersList.tsx (81%) rename server/sonar-web/src/main/js/apps/{organizations/components => organizationMembers}/MembersListHeader.tsx (87%) rename server/sonar-web/src/main/js/apps/{organizations/components => organizationMembers}/MembersListItem.tsx (90%) rename server/sonar-web/src/main/js/apps/{organizations/components => organizationMembers}/MembersPageHeader.tsx (93%) create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx rename server/sonar-web/src/main/js/{store/organizations/utils.ts => apps/organizationMembers/OrganizationMembersContainer.tsx} (57%) rename server/sonar-web/src/main/js/apps/{organizations/components/forms => organizationMembers}/RemoveMemberForm.tsx (88%) rename server/sonar-web/src/main/js/apps/{organizations/components/forms => organizationMembers}/__tests__/AddMemberForm-test.tsx (54%) rename server/sonar-web/src/main/js/apps/{organizations/components/forms => organizationMembers}/__tests__/ManageMemberGroupsForm-test.tsx (98%) rename server/sonar-web/src/main/js/apps/{organizations/components => organizationMembers}/__tests__/MembersList-test.tsx (83%) rename server/sonar-web/src/main/js/apps/{organizations/components => organizationMembers}/__tests__/MembersListHeader-test.tsx (100%) rename server/sonar-web/src/main/js/apps/{organizations/components => organizationMembers}/__tests__/MembersListItem-test.tsx (65%) rename server/sonar-web/src/main/js/apps/{organizations/components => organizationMembers}/__tests__/MembersPageHeader-test.tsx (100%) create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/OrganizationMembers-test.tsx rename server/sonar-web/src/main/js/apps/{organizations/components/forms => organizationMembers}/__tests__/RemoveMemberForm-test.tsx (96%) rename server/sonar-web/src/main/js/apps/{organizations/components/forms => organizationMembers}/__tests__/__snapshots__/AddMemberForm-test.tsx.snap (83%) rename server/sonar-web/src/main/js/apps/{organizations/components/forms => organizationMembers}/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap (100%) rename server/sonar-web/src/main/js/apps/{organizations/components => organizationMembers}/__tests__/__snapshots__/MembersList-test.tsx.snap (92%) rename server/sonar-web/src/main/js/apps/{organizations/components => organizationMembers}/__tests__/__snapshots__/MembersListHeader-test.tsx.snap (100%) rename server/sonar-web/src/main/js/apps/{organizations/components => organizationMembers}/__tests__/__snapshots__/MembersListItem-test.tsx.snap (100%) rename server/sonar-web/src/main/js/apps/{organizations/components => organizationMembers}/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap (100%) create mode 100644 server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap rename server/sonar-web/src/main/js/apps/{organizations/components/forms => organizationMembers}/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap (100%) 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__/OrganizationMembers-test.tsx 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/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/organizations/components/forms/AddMemberForm.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/AddMemberForm.tsx similarity index 82% rename from server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.tsx rename to server/sonar-web/src/main/js/apps/organizationMembers/AddMemberForm.tsx index 00408077132..2617234955e 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/AddMemberForm.tsx @@ -18,12 +18,12 @@ * 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'; +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; @@ -103,14 +103,13 @@ export default class AddMemberForm extends React.PureComponent { } render() { - const buttonComponent = ( - + return ( + <> + + {this.state.open && this.renderModal()} + ); - 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/organizationMembers/ManageMemberGroupsForm.tsx similarity index 91% rename from server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.tsx rename to server/sonar-web/src/main/js/apps/organizationMembers/ManageMemberGroupsForm.tsx index c9c963b3c1b..6d92e7fce8b 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/ManageMemberGroupsForm.tsx @@ -19,12 +19,12 @@ */ 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'; +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; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersList.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersList.tsx similarity index 81% rename from server/sonar-web/src/main/js/apps/organizations/components/MembersList.tsx rename to server/sonar-web/src/main/js/apps/organizationMembers/MembersList.tsx index 641486606b5..676478bbbff 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/MembersList.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/MembersList.tsx @@ -18,8 +18,10 @@ * 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 { Group, Organization, OrganizationMember } from '../../app/types'; +import { translate } from '../../helpers/l10n'; interface Props { members: OrganizationMember[]; @@ -35,11 +37,18 @@ interface Props { 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 (
- {this.props.members.map(member => ( + {sortedMembers.map(member => ( void; @@ -35,7 +35,7 @@ export default function MembersListHeader({ handleSearch, total }: Props) { onChange={handleSearch} placeholder={translate('search.search_for_users')} /> - {total != null && ( + {total !== undefined && ( {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/organizationMembers/MembersListItem.tsx similarity index 90% rename from server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.tsx rename to server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx index 99d71d9a4fa..43b2535d68f 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx @@ -18,16 +18,16 @@ * 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 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'; +} from '../../components/controls/ActionsDropdown'; +import { Group, Organization, OrganizationMember } from '../../app/types'; interface Props { member: OrganizationMember; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersPageHeader.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx similarity index 93% rename from server/sonar-web/src/main/js/apps/organizations/components/MembersPageHeader.tsx rename to server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx index 9481d010480..171dfc48aa8 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/MembersPageHeader.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx @@ -20,8 +20,8 @@ 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'; +import { translate } from '../../helpers/l10n'; +import DeferredSpinner from '../../components/common/DeferredSpinner'; interface Props { children?: React.ReactNode; 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/store/organizations/utils.ts b/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembersContainer.tsx similarity index 57% rename from server/sonar-web/src/main/js/store/organizations/utils.ts rename to server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembersContainer.tsx index 5b3957e1f6c..6f13da8a3c2 100644 --- a/server/sonar-web/src/main/js/store/organizations/utils.ts +++ b/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembersContainer.tsx @@ -17,20 +17,24 @@ * 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'; +import { connect } from 'react-redux'; +import OrganizationMembers from './OrganizationMembers'; +import { Organization } from '../../app/types'; +import { getOrganizationByKey } from '../../store/rootReducer'; -export function getOrganization(key: string) { - const store = getStore(); - const state = store.getState(); - return getOrganizationByKey(state, key); +interface OwnProps { + params: { organizationKey: string }; } -export function areThereCustomOrganizations() { - const store = getStore(); - const state = store.getState(); - return customOrganizations(state); +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/organizations/components/forms/RemoveMemberForm.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/RemoveMemberForm.tsx similarity index 88% rename from server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.tsx rename to server/sonar-web/src/main/js/apps/organizationMembers/RemoveMemberForm.tsx index c6e0d603dd2..636ae892a31 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/RemoveMemberForm.tsx @@ -18,10 +18,10 @@ * 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'; +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; 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/organizationMembers/__tests__/AddMemberForm-test.tsx similarity index 54% rename from server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.tsx rename to server/sonar-web/src/main/js/apps/organizationMembers/__tests__/AddMemberForm-test.tsx index 3406cd7361d..2908edeb1c9 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/AddMemberForm-test.tsx @@ -19,8 +19,13 @@ */ import * as React from 'react'; import { shallow } from 'enzyme'; -import { click } from '../../../../../helpers/testUtils'; +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']; @@ -34,9 +39,7 @@ it('should render and open the modal', () => { ); 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(); + expect(wrapper).toMatchSnapshot(); }); it('should correctly handle user interactions', () => { @@ -52,3 +55,46 @@ it('should correctly handle user interactions', () => { (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/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/ManageMemberGroupsForm-test.tsx similarity index 98% rename from server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.tsx rename to server/sonar-web/src/main/js/apps/organizationMembers/__tests__/ManageMemberGroupsForm-test.tsx index 32b0839c275..8a011e697f7 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/ManageMemberGroupsForm-test.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { shallow } from 'enzyme'; -import { mockEvent } from '../../../../../helpers/testUtils'; +import { mockEvent } from '../../../helpers/testUtils'; import ManageMemberGroupsForm from '../ManageMemberGroupsForm'; const member = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersList-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersList-test.tsx similarity index 83% rename from server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersList-test.tsx rename to server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersList-test.tsx index 0910f62995c..514ac814a16 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersList-test.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersList-test.tsx @@ -39,3 +39,16 @@ it('should render a list of members of an organization', () => { ); expect(wrapper).toMatchSnapshot(); }); + +it('should render "no results"', () => { + 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/organizationMembers/__tests__/MembersListHeader-test.tsx similarity index 100% rename from server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.tsx rename to server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListHeader-test.tsx diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListItem-test.tsx similarity index 65% rename from server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.tsx rename to server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListItem-test.tsx index 9838194849f..18c91ecec61 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListItem-test.tsx @@ -20,6 +20,7 @@ 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 }; @@ -63,3 +64,41 @@ it('should groups at 0 if the groupCount field is not defined (just added user)' ); 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/organizations/components/__tests__/MembersPageHeader-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersPageHeader-test.tsx similarity index 100% rename from server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersPageHeader-test.tsx rename to server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersPageHeader-test.tsx 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/organizations/components/forms/__tests__/RemoveMemberForm-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/RemoveMemberForm-test.tsx similarity index 96% rename from server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.tsx rename to server/sonar-web/src/main/js/apps/organizationMembers/__tests__/RemoveMemberForm-test.tsx index 92fbdb9723d..48c45996da9 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/RemoveMemberForm-test.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { shallow } from 'enzyme'; -import { mockEvent } from '../../../../../helpers/testUtils'; +import { mockEvent } from '../../../helpers/testUtils'; import RemoveMemberForm from '../RemoveMemberForm'; const member = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }; 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/organizationMembers/__tests__/__snapshots__/AddMemberForm-test.tsx.snap similarity index 83% rename from server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.tsx.snap rename to server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/AddMemberForm-test.tsx.snap index d480a393f5b..ca0444d7328 100644 --- 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/organizationMembers/__tests__/__snapshots__/AddMemberForm-test.tsx.snap @@ -1,23 +1,27 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should render and open the modal 1`] = ` - + + + `; exports[`should render and open the modal 2`] = ` -Array [ + , +
- , -] + + `; 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/organizationMembers/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap similarity index 100% rename from server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap rename to server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap 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/organizationMembers/__tests__/__snapshots__/MembersList-test.tsx.snap similarity index 92% rename from server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersList-test.tsx.snap rename to server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersList-test.tsx.snap index 57ff0c52ea6..9635b1e2685 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersList-test.tsx.snap @@ -1,5 +1,13 @@ // 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`] = `
+ + + +
+ + +
+
+ + + + + +
+`; + +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/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap similarity index 100% rename from server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap rename to server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap 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/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__/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__/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/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/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; -}; -- 2.39.5