]> source.dussan.org Git - sonarqube.git/commitdiff
Migrate remaining of organization app to TS
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 6 Jul 2018 09:40:29 +0000 (11:40 +0200)
committerSonarTech <sonartech@sonarsource.com>
Wed, 11 Jul 2018 18:21:22 +0000 (20:21 +0200)
96 files changed:
server/sonar-web/src/main/js/api/organizations.ts
server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.js [deleted file]
server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/styles/init/lists.css
server/sonar-web/src/main/js/app/types.ts
server/sonar-web/src/main/js/apps/account/organizations/CreateOrganizationForm.tsx
server/sonar-web/src/main/js/apps/explore/ExploreProjects.tsx
server/sonar-web/src/main/js/apps/organizations/actions.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/actions.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/MembersList.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/MembersList.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/OrganizationAccessContainer.tsx
server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx
server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersList-test.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersList-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationAccessContainer-test.tsx
server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEdit-test.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEdit-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationPage-test.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationPage-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersList-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersList-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListHeader-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListHeader-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationPage-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationPage-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenu.tsx [deleted file]
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenuContainer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMenu-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMenuContainer-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenu-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenuContainer-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx
server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx
server/sonar-web/src/main/js/apps/projects/components/ProjectCard.tsx
server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsList-test.tsx
server/sonar-web/src/main/js/apps/projects/utils.ts
server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewOrganizationForm.tsx
server/sonar-web/src/main/js/store/organizations/__tests__/__snapshots__/duck-test.js.snap [deleted file]
server/sonar-web/src/main/js/store/organizations/__tests__/__snapshots__/duck-test.ts.snap [new file with mode: 0644]
server/sonar-web/src/main/js/store/organizations/__tests__/duck-test.js [deleted file]
server/sonar-web/src/main/js/store/organizations/__tests__/duck-test.ts [new file with mode: 0644]
server/sonar-web/src/main/js/store/organizations/duck.ts
server/sonar-web/src/main/js/store/organizations/utils.js [deleted file]
server/sonar-web/src/main/js/store/organizations/utils.ts [new file with mode: 0644]

index 95918a609b341fb8b2181408019b32e745d6deda..ffa4cb24fb291b941b70d7219c63774fbf6834f5 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { getJSON, post, postJSON, RequestData } from '../helpers/request';
+import { getJSON, post, postJSON } from '../helpers/request';
 import throwGlobalError from '../app/utils/throwGlobalError';
-import { LightOrganization, Paging } from '../app/types';
+import { Organization, OrganizationBase, Paging } from '../app/types';
 
 export function getOrganizations(data: {
   organizations?: string;
   member?: boolean;
 }): Promise<{
-  organizations: LightOrganization[];
+  organizations: Organization[];
   paging: Paging;
 }> {
   return getJSON('/api/organizations/search', data);
 }
 
-export function getOrganization(key: string): Promise<any> {
-  return getOrganizations({ organizations: key })
-    .then(r => r.organizations.find((o: any) => o.key === key))
-    .catch(throwGlobalError);
+export function getOrganization(key: string): Promise<Organization | undefined> {
+  return getJSON('/api/organizations/search', { organizations: key }).then(
+    r => r.organizations.find((o: Organization) => o.key === key),
+    throwGlobalError
+  );
 }
 
 interface GetOrganizationNavigation {
@@ -50,11 +51,11 @@ export function getOrganizationNavigation(key: string): Promise<GetOrganizationN
   return getJSON('/api/navigation/organization', { organization: key }).then(r => r.organization);
 }
 
-export function createOrganization(data: RequestData): Promise<any> {
+export function createOrganization(data: OrganizationBase): Promise<Organization> {
   return postJSON('/api/organizations/create', data).then(r => r.organization, throwGlobalError);
 }
 
-export function updateOrganization(key: string, changes: RequestData): Promise<void> {
+export function updateOrganization(key: string, changes: OrganizationBase): Promise<void> {
   return post('/api/organizations/update', { key, ...changes });
 }
 
diff --git a/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.js b/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.js
deleted file mode 100644 (file)
index f93fc31..0000000
+++ /dev/null
@@ -1,75 +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
-import React from 'react';
-import { connect } from 'react-redux';
-import ExtensionContainer from './ExtensionContainer';
-import ExtensionNotFound from './ExtensionNotFound';
-import { getOrganizationByKey } from '../../../store/rootReducer';
-import { fetchOrganization } from '../../../apps/organizations/actions';
-/*:: import type { Organization } from '../../../app/types'; */
-
-/*::
-type Props = {
-  fetchOrganization: string => void,
-  location: {},
-  organization: Organization,
-  params: {
-    extensionKey: string,
-    organizationKey: string,
-    pluginKey: string
-  }
-};
-*/
-
-class OrganizationPageExtension extends React.PureComponent {
-  /*:: props: Props; */
-
-  refreshOrganization = () => this.props.fetchOrganization(this.props.organization.key);
-
-  render() {
-    const { extensionKey, pluginKey } = this.props.params;
-    const { organization } = this.props;
-
-    let pages = organization.pages || [];
-    if (organization.canAdmin && organization.adminPages) {
-      pages = pages.concat(organization.adminPages);
-    }
-
-    const extension = pages.find(p => p.key === `${pluginKey}/${extensionKey}`);
-    return extension ? (
-      <ExtensionContainer
-        extension={extension}
-        location={this.props.location}
-        options={{ organization, refreshOrganization: this.refreshOrganization }}
-      />
-    ) : (
-      <ExtensionNotFound />
-    );
-  }
-}
-
-const mapStateToProps = (state, ownProps /*: Props */) => ({
-  organization: getOrganizationByKey(state, ownProps.params.organizationKey)
-});
-
-const mapDispatchToProps = { fetchOrganization };
-
-export default connect(mapStateToProps, mapDispatchToProps)(OrganizationPageExtension);
diff --git a/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.tsx b/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.tsx
new file mode 100644 (file)
index 0000000..3069976
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * 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 { connect } from 'react-redux';
+import ExtensionContainer from './ExtensionContainer';
+import ExtensionNotFound from './ExtensionNotFound';
+import { getOrganizationByKey } from '../../../store/rootReducer';
+import { fetchOrganization } from '../../../apps/organizations/actions';
+import { Organization } from '../../../app/types';
+
+interface StateToProps {
+  organization?: Organization;
+}
+
+interface DispatchProps {
+  fetchOrganization: (organizationKey: string) => void;
+}
+
+interface OwnProps {
+  location: {};
+  params: {
+    extensionKey: string;
+    organizationKey: string;
+    pluginKey: string;
+  };
+}
+
+type Props = OwnProps & StateToProps & DispatchProps;
+
+class OrganizationPageExtension extends React.PureComponent<Props> {
+  refreshOrganization = () =>
+    this.props.organization && this.props.fetchOrganization(this.props.organization.key);
+
+  render() {
+    const { extensionKey, pluginKey } = this.props.params;
+    const { organization } = this.props;
+
+    if (!organization) {
+      return null;
+    }
+
+    let pages = organization.pages || [];
+    if (organization.canAdmin && organization.adminPages) {
+      pages = pages.concat(organization.adminPages);
+    }
+
+    const extension = pages.find(p => p.key === `${pluginKey}/${extensionKey}`);
+    return extension ? (
+      <ExtensionContainer
+        extension={extension}
+        location={this.props.location}
+        options={{ organization, refreshOrganization: this.refreshOrganization }}
+      />
+    ) : (
+      <ExtensionNotFound />
+    );
+  }
+}
+
+const mapStateToProps = (state: any, ownProps: OwnProps) => ({
+  organization: getOrganizationByKey(state, ownProps.params.organizationKey)
+});
+
+const mapDispatchToProps = { fetchOrganization };
+
+export default connect<StateToProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)(
+  OrganizationPageExtension
+);
index 3ebd22f84e8d484b8160929fe50376936b1960a8..f8cc8a5489ee79322eed627465ff455b6592ecb2 100644 (file)
@@ -66,11 +66,11 @@ ol.list-styled {
   outline: none;
 }
 
-.list-item-checkable-link[disabled] {
+.list-item-checkable-link.disabled {
   opacity: 0.7;
 }
 
-.list-item-checkable-link[disabled] a::before {
+.list-item-checkable-link.disabled a::before {
   background-color: var(--gray80);
   border-color: var(--gray80);
 }
index 6d359757c55d789605ee64f4e8717612449c8979..bddfe372c4a96324131d5ed57c49f6fb106825c5 100644 (file)
@@ -337,26 +337,36 @@ export interface Notification {
   projectName?: string;
   type: string;
 }
-export interface LightOrganization {
-  avatar?: string;
-  description?: string;
-  guarded?: boolean;
-  isAdmin?: boolean;
-  key: string;
-  name: string;
-  subscription?: OrganizationSubscription;
-  url?: string;
-}
 
-export interface Organization extends LightOrganization {
-  adminPages?: { key: string; name: string }[];
+export interface Organization extends OrganizationBase {
+  adminPages?: Extension[];
   canAdmin?: boolean;
   canDelete?: boolean;
   canProvisionProjects?: boolean;
   canUpdateProjectsVisibilityToPrivate?: boolean;
+  guarded?: boolean;
+  isAdmin?: boolean;
   isDefault?: boolean;
-  pages?: { key: string; name: string }[];
+  key: string;
+  pages?: Extension[];
   projectVisibility?: Visibility;
+  subscription?: OrganizationSubscription;
+}
+
+export interface OrganizationBase {
+  avatar?: string;
+  description?: string;
+  key?: string;
+  name: string;
+  url?: string;
+}
+
+export interface OrganizationMember {
+  login: string;
+  name: string;
+  avatar?: string;
+  email?: string;
+  groupCount?: number;
 }
 
 export enum OrganizationSubscription {
index acc43e6de4c0a2697a293442002738bad656b8e4..9416f11a790b6d4583407230129d916383c4ea60 100644 (file)
@@ -22,14 +22,14 @@ import { debounce } from 'lodash';
 import { connect } from 'react-redux';
 import * as PropTypes from 'prop-types';
 import { createOrganization } from '../../organizations/actions';
-import { Organization } from '../../../app/types';
+import { Organization, OrganizationBase } from '../../../app/types';
 import Modal from '../../../components/controls/Modal';
 import DocTooltip from '../../../components/docs/DocTooltip';
 import { translate } from '../../../helpers/l10n';
 import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons';
 
 interface DispatchProps {
-  createOrganization: (fields: Partial<Organization>) => Promise<{ key: string }>;
+  createOrganization: (fields: OrganizationBase) => Promise<Organization>;
 }
 
 interface Props extends DispatchProps {
index ff1789ee68c6a98d6ad682cf9b631a5bbf511e50..ec8376ede5fa4208a99822c51be8265eba06bd0f 100644 (file)
@@ -26,5 +26,12 @@ interface Props {
 }
 
 export default function ExploreProjects(props: Props) {
-  return <AllProjectsContainer isFavorite={false} storageOptionsSuffix="explore" {...props} />;
+  return (
+    <AllProjectsContainer
+      isFavorite={false}
+      organization={undefined}
+      storageOptionsSuffix="explore"
+      {...props}
+    />
+  );
 }
diff --git a/server/sonar-web/src/main/js/apps/organizations/actions.js b/server/sonar-web/src/main/js/apps/organizations/actions.js
deleted file mode 100644 (file)
index 2de045b..0000000
+++ /dev/null
@@ -1,191 +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
-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 type { Organization } from '../../app/types'; */
-/*:: import type { Member } from '../../store/organizationsMembers/actions'; */
-
-const PAGE_SIZE = 50;
-
-const onRejected = (dispatch /*: Function */) => (error /*: Object */) => {
-  onFail(dispatch)(error);
-  return Promise.reject();
-};
-
-const onMembersFail = (organization /*: string */, dispatch /*: Function */) => (
-  error /*: Object */
-) => {
-  onFail(dispatch)(error);
-  dispatch(membersActions.updateState(organization, { loading: false }));
-};
-
-export const fetchOrganization = (key /*: string */) => (dispatch /*: Function */) => {
-  const onFulfilled = ([organization, navigation]) => {
-    if (organization) {
-      const organizationWithPermissions = { ...organization, ...navigation };
-      dispatch(actions.receiveOrganizations([organizationWithPermissions]));
-    }
-  };
-
-  return Promise.all([api.getOrganization(key), api.getOrganizationNavigation(key)]).then(
-    onFulfilled,
-    onFail(dispatch)
-  );
-};
-
-export const fetchOrganizationGroups = (organization /*: string */) => (
-  dispatch /*: Function */
-) => {
-  return searchUsersGroups({ organization }).then(response => {
-    dispatch(actions.receiveOrganizationGroups(organization, response.groups));
-  }, onFail(dispatch));
-};
-
-export const createOrganization = (fields /*: Object */) => (dispatch /*: Function */) => {
-  const onFulfilled = (organization /*: Organization */) => {
-    dispatch(actions.createOrganization(organization));
-    dispatch(
-      addGlobalSuccessMessage(translateWithParameters('organization.created', organization.name))
-    );
-    return organization;
-  };
-
-  return api.createOrganization(fields).then(onFulfilled, onRejected(dispatch));
-};
-
-export const updateOrganization = (key /*: string */, changes /*: {} */) => (
-  dispatch /*: Function */
-) => {
-  const onFulfilled = () => {
-    dispatch(actions.updateOrganization(key, changes));
-    dispatch(addGlobalSuccessMessage(translate('organization.updated')));
-  };
-
-  return api.updateOrganization(key, changes).then(onFulfilled, onFail(dispatch));
-};
-
-export const deleteOrganization = (key /*: string */) => (dispatch /*: Function */) => {
-  const onFulfilled = () => {
-    dispatch(actions.deleteOrganization(key));
-    dispatch(addGlobalSuccessMessage(translate('organization.deleted')));
-  };
-
-  return api.deleteOrganization(key).then(onFulfilled, onFail(dispatch));
-};
-
-const fetchMembers = (
-  dispatch /*: Function */,
-  receiveAction /*: Function */,
-  key /*: string */,
-  query /*: ?string */,
-  page /*: ?number */
-) => {
-  dispatch(membersActions.updateState(key, { loading: true }));
-  const data /*: Object */ = {
-    organization: key,
-    ps: PAGE_SIZE
-  };
-  if (page != null) {
-    data.p = page;
-  }
-  if (query) {
-    data.q = query;
-  }
-  return api.searchMembers(data).then(response => {
-    dispatch(
-      receiveAction(key, response.users, {
-        loading: false,
-        total: response.paging.total,
-        pageIndex: response.paging.pageIndex,
-        query: query || null
-      })
-    );
-  }, onMembersFail(key, dispatch));
-};
-
-export const fetchOrganizationMembers = (key /*: string */, query /*: ?string */) => (
-  dispatch /*: Function */
-) => fetchMembers(dispatch, membersActions.receiveMembers, key, query);
-
-export const fetchMoreOrganizationMembers = (key /*: string */, query /*: ?string */) => (
-  dispatch /*: Function */,
-  getState /*: Function */
-) =>
-  fetchMembers(
-    dispatch,
-    membersActions.receiveMoreMembers,
-    key,
-    query,
-    getOrganizationMembersState(getState(), key).pageIndex + 1
-  );
-
-export const addOrganizationMember = (key /*: string */, member /*: Member */) => (
-  dispatch /*: Function */
-) => {
-  return api
-    .addMember({ login: member.login, organization: key })
-    .then(user => dispatch(membersActions.addMember(key, user)), onFail(dispatch));
-};
-
-export const removeOrganizationMember = (key /*: string */, member /*: Member */) => (
-  dispatch /*: Function */
-) => {
-  dispatch(membersActions.removeMember(key, member));
-  return api.removeMember({ login: member.login, organization: key }).catch((
-    error /*: Object */
-  ) => {
-    onFail(dispatch)(error);
-    dispatch(membersActions.addMember(key, member));
-  });
-};
-
-export const updateOrganizationMemberGroups = (
-  organization /*: Organization */,
-  member /*: Member */,
-  add /*: Array<string> */,
-  remove /*: Array<string> */
-) => (dispatch /*: Function */) => {
-  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/actions.ts b/server/sonar-web/src/main/js/apps/organizations/actions.ts
new file mode 100644 (file)
index 0000000..7f5d889
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * 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 { 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;
+
+const onRejected = (dispatch: Dispatch<any>) => (error: any) => {
+  onFail(dispatch)(error);
+  return Promise.reject(error);
+};
+
+export const fetchOrganization = (key: string) => (dispatch: Dispatch<any>) => {
+  return Promise.all([api.getOrganization(key), api.getOrganizationNavigation(key)]).then(
+    ([organization, navigation]) => {
+      if (organization) {
+        const organizationWithPermissions = { ...organization, ...navigation };
+        dispatch(actions.receiveOrganizations([organizationWithPermissions]));
+      }
+    },
+    onFail(dispatch)
+  );
+};
+
+export const fetchOrganizationGroups = (organization: string) => (dispatch: Dispatch<any>) => {
+  return searchUsersGroups({ organization }).then(response => {
+    dispatch(actions.receiveOrganizationGroups(organization, response.groups));
+  }, onFail(dispatch));
+};
+
+export const createOrganization = (organization: OrganizationBase) => (dispatch: Dispatch<any>) => {
+  return api.createOrganization(organization).then((organization: Organization) => {
+    dispatch(actions.createOrganization(organization));
+    dispatch(
+      addGlobalSuccessMessage(translateWithParameters('organization.created', organization.name))
+    );
+    return organization;
+  }, onRejected(dispatch));
+};
+
+export const updateOrganization = (key: string, changes: OrganizationBase) => (
+  dispatch: Dispatch<any>
+) => {
+  return api.updateOrganization(key, changes).then(() => {
+    dispatch(actions.updateOrganization(key, changes));
+    dispatch(addGlobalSuccessMessage(translate('organization.updated')));
+  }, onFail(dispatch));
+};
+
+export const deleteOrganization = (key: string) => (dispatch: Dispatch<any>) => {
+  return api.deleteOrganization(key).then(() => {
+    dispatch(actions.deleteOrganization(key));
+    dispatch(addGlobalSuccessMessage(translate('organization.deleted')));
+  }, onFail(dispatch));
+};
+
+const fetchMembers = (
+  data: {
+    organization: string;
+    p?: number;
+    ps?: number;
+    q?: string;
+  },
+  dispatch: Dispatch<any>,
+  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<any>
+) => fetchMembers({ organization: key, q: query }, dispatch, membersActions.receiveMembers);
+
+export const fetchMoreOrganizationMembers = (key: string, query?: string) => (
+  dispatch: Dispatch<any>,
+  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<any>
+) => {
+  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<any>
+) => {
+  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<any>) => {
+  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.js b/server/sonar-web/src/main/js/apps/organizations/components/MembersList.js
deleted file mode 100644 (file)
index 4ed74e4..0000000
+++ /dev/null
@@ -1,59 +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
-import React from 'react';
-import MembersListItem from './MembersListItem';
-/*:: import type { Member } from '../../../store/organizationsMembers/actions'; */
-/*:: import type { Organization, Group } from '../../../app/types'; */
-
-/*::
-type Props = {
-  members: Array<Member>,
-  organizationGroups: Array<Group>,
-  organization: Organization,
-  removeMember: Member => void,
-  updateMemberGroups: (member: Member, add: Array<string>, remove: Array<string>) => void
-};
-*/
-
-export default class MembersList extends React.PureComponent {
-  /*:: props: Props; */
-
-  render() {
-    return (
-      <div className="boxed-group boxed-group-inner">
-        <table className="data zebra">
-          <tbody>
-            {this.props.members.map(member => (
-              <MembersListItem
-                key={member.login}
-                member={member}
-                organizationGroups={this.props.organizationGroups}
-                organization={this.props.organization}
-                removeMember={this.props.removeMember}
-                updateMemberGroups={this.props.updateMemberGroups}
-              />
-            ))}
-          </tbody>
-        </table>
-      </div>
-    );
-  }
-}
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
new file mode 100644 (file)
index 0000000..6414866
--- /dev/null
@@ -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 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<string>,
+    remove: Array<string>
+  ) => void;
+}
+
+export default class MembersList extends React.PureComponent<Props> {
+  render() {
+    return (
+      <div className="boxed-group boxed-group-inner">
+        <table className="data zebra">
+          <tbody>
+            {this.props.members.map(member => (
+              <MembersListItem
+                key={member.login}
+                member={member}
+                organization={this.props.organization}
+                organizationGroups={this.props.organizationGroups}
+                removeMember={this.props.removeMember}
+                updateMemberGroups={this.props.updateMemberGroups}
+              />
+            ))}
+          </tbody>
+        </table>
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.js b/server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.js
deleted file mode 100644 (file)
index ed3106a..0000000
+++ /dev/null
@@ -1,48 +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
-import React from 'react';
-import SearchBox from '../../../components/controls/SearchBox';
-import { formatMeasure } from '../../../helpers/measures';
-import { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = {
-  handleSearch: (query?: string) => void,
-  total?: number
-};
-*/
-
-export default function MembersListHeader({ handleSearch, total } /*: Props */) {
-  return (
-    <div className="panel panel-vertical bordered-bottom spacer-bottom">
-      <SearchBox
-        minLength={2}
-        onChange={handleSearch}
-        placeholder={translate('search.search_for_users')}
-      />
-      {total != null && (
-        <span className="pull-right little-spacer-top">
-          <strong>{formatMeasure(total, 'INT')}</strong> {translate('organization.members.members')}
-        </span>
-      )}
-    </div>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.tsx b/server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.tsx
new file mode 100644 (file)
index 0000000..b703131
--- /dev/null
@@ -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 (
+    <div className="panel panel-vertical bordered-bottom spacer-bottom">
+      <SearchBox
+        minLength={2}
+        onChange={handleSearch}
+        placeholder={translate('search.search_for_users')}
+      />
+      {total != null && (
+        <span className="pull-right little-spacer-top">
+          <strong>{formatMeasure(total, 'INT')}</strong> {translate('organization.members.members')}
+        </span>
+      )}
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js b/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js
deleted file mode 100644 (file)
index b17f5ee..0000000
+++ /dev/null
@@ -1,140 +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
-import 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 type { Member } from '../../../store/organizationsMembers/actions'; */
-/*:: import type { Organization, Group } from '../../../app/types'; */
-
-/*::
-type Props = {
-  member: Member,
-  organization: Organization,
-  organizationGroups: Array<Group>,
-  removeMember: Member => void,
-  updateMemberGroups: (member: Member, add: Array<string>, remove: Array<string>) => void
-};
-
-type State = {
-  removeMemberForm: bool,
-  manageGroupsForm: bool
-}
-*/
-
-const AVATAR_SIZE /*: number */ = 36;
-
-export default class MembersListItem extends React.PureComponent {
-  mounted /*: bool */ = false;
-  /*:: props: Props; */
-  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 (
-      <tr>
-        <td className="thin nowrap">
-          <Avatar hash={member.avatar} name={member.name} size={AVATAR_SIZE} />
-        </td>
-        <td className="nowrap text-middle">
-          <strong>{member.name}</strong>
-          <span className="note little-spacer-left">{member.login}</span>
-        </td>
-        {organization.canAdmin && (
-          <td className="text-right text-middle">
-            {translateWithParameters(
-              'organization.members.x_groups',
-              formatMeasure(member.groupCount || 0, 'INT')
-            )}
-          </td>
-        )}
-        {organization.canAdmin && (
-          <React.Fragment>
-            <td className="nowrap text-middle text-right">
-              <ActionsDropdown>
-                <ActionsDropdownItem onClick={this.handleManageGroupsClick}>
-                  {translate('organization.members.manage_groups')}
-                </ActionsDropdownItem>
-                <ActionsDropdownDivider />
-                <ActionsDropdownItem destructive={true} onClick={this.handleRemoveMemberClick}>
-                  {translate('organization.members.remove')}
-                </ActionsDropdownItem>
-              </ActionsDropdown>
-            </td>
-
-            {this.state.manageGroupsForm && (
-              <ManageMemberGroupsForm
-                member={this.props.member}
-                onClose={this.closeManageGroupsForm}
-                organization={this.props.organization}
-                organizationGroups={this.props.organizationGroups}
-                updateMemberGroups={this.props.updateMemberGroups}
-              />
-            )}
-
-            {this.state.removeMemberForm && (
-              <RemoveMemberForm
-                member={this.props.member}
-                onClose={this.closeRemoveMemberForm}
-                organization={this.props.organization}
-                removeMember={this.props.removeMember}
-              />
-            )}
-          </React.Fragment>
-        )}
-      </tr>
-    );
-  }
-}
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
new file mode 100644 (file)
index 0000000..99d71d9
--- /dev/null
@@ -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 './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<Props, State> {
+  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 (
+      <tr>
+        <td className="thin nowrap">
+          <Avatar hash={member.avatar} name={member.name} size={AVATAR_SIZE} />
+        </td>
+        <td className="nowrap text-middle">
+          <strong>{member.name}</strong>
+          <span className="note little-spacer-left">{member.login}</span>
+        </td>
+        {organization.canAdmin && (
+          <td className="text-right text-middle">
+            {translateWithParameters(
+              'organization.members.x_groups',
+              formatMeasure(member.groupCount || 0, 'INT')
+            )}
+          </td>
+        )}
+        {organization.canAdmin && (
+          <React.Fragment>
+            <td className="nowrap text-middle text-right">
+              <ActionsDropdown>
+                <ActionsDropdownItem onClick={this.handleManageGroupsClick}>
+                  {translate('organization.members.manage_groups')}
+                </ActionsDropdownItem>
+                <ActionsDropdownDivider />
+                <ActionsDropdownItem destructive={true} onClick={this.handleRemoveMemberClick}>
+                  {translate('organization.members.remove')}
+                </ActionsDropdownItem>
+              </ActionsDropdown>
+            </td>
+
+            {this.state.manageGroupsForm && (
+              <ManageMemberGroupsForm
+                member={this.props.member}
+                onClose={this.closeManageGroupsForm}
+                organization={this.props.organization}
+                organizationGroups={this.props.organizationGroups}
+                updateMemberGroups={this.props.updateMemberGroups}
+              />
+            )}
+
+            {this.state.removeMemberForm && (
+              <RemoveMemberForm
+                member={this.props.member}
+                onClose={this.closeRemoveMemberForm}
+                organization={this.props.organization}
+                removeMember={this.props.removeMember}
+              />
+            )}
+          </React.Fragment>
+        )}
+      </tr>
+    );
+  }
+}
index aab0246bd051cfeeae68c25da5acfa6249c85427..2b5e9f301d43a055a9034beec22868ed3e5fea59 100644 (file)
@@ -24,10 +24,12 @@ import { getOrganizationByKey, getCurrentUser } from '../../../store/rootReducer
 import handleRequiredAuthorization from '../../../app/utils/handleRequiredAuthorization';
 import { Organization, CurrentUser, isLoggedIn } from '../../../app/types';
 import { isCurrentUserMemberOf, hasPrivateAccess } from '../../../helpers/organizations';
+import { getMyOrganizations } from '../../../store/organizations/duck';
 
 interface StateToProps {
   currentUser: CurrentUser;
   organization?: Organization;
+  userOrganizations: Organization[];
 }
 
 interface OwnProps extends RouterState {
@@ -66,7 +68,8 @@ export class OrganizationAccess extends React.PureComponent<Props> {
 
 const mapStateToProps = (state: any, ownProps: OwnProps) => ({
   currentUser: getCurrentUser(state),
-  organization: getOrganizationByKey(state, ownProps.params.organizationKey)
+  organization: getOrganizationByKey(state, ownProps.params.organizationKey),
+  userOrganizations: getMyOrganizations(state)
 });
 
 const OrganizationAccessContainer = connect<StateToProps, {}, OwnProps>(mapStateToProps)(
@@ -76,7 +79,9 @@ const OrganizationAccessContainer = connect<StateToProps, {}, OwnProps>(mapState
 export function OrganizationPrivateAccess(props: OwnProps) {
   return (
     <OrganizationAccessContainer
-      hasAccess={({ organization }: StateToProps) => hasPrivateAccess(organization)}
+      hasAccess={({ currentUser, organization, userOrganizations }: StateToProps) =>
+        hasPrivateAccess(currentUser, organization, userOrganizations)
+      }
       {...props}
     />
   );
@@ -85,7 +90,9 @@ export function OrganizationPrivateAccess(props: OwnProps) {
 export function OrganizationMembersAccess(props: OwnProps) {
   return (
     <OrganizationAccessContainer
-      hasAccess={({ organization }: StateToProps) => isCurrentUserMemberOf(organization)}
+      hasAccess={({ currentUser, organization, userOrganizations }: StateToProps) =>
+        isCurrentUserMemberOf(currentUser, organization, userOrganizations)
+      }
       {...props}
     />
   );
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js
deleted file mode 100644 (file)
index b782906..0000000
+++ /dev/null
@@ -1,200 +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
-import React from 'react';
-import Helmet from 'react-helmet';
-import { connect } from 'react-redux';
-import { debounce } from 'lodash';
-import { translate } from '../../../helpers/l10n';
-import { updateOrganization } from '../actions';
-import { SubmitButton } from '../../../components/ui/buttons';
-/*:: import type { Organization } from '../../../app/types'; */
-
-/*::
-type Props = {
-  organization: Organization,
-  updateOrganization: (string, Object) => Promise<*>
-};
-*/
-
-class OrganizationEdit extends React.PureComponent {
-  /*:: mounted: boolean; */
-
-  /*:: props: Props; */
-
-  /*:: state: {
-    loading: boolean,
-    avatar: string,
-    avatarImage: string,
-    description: string,
-    name: string,
-    url: string
-  };
-*/
-
-  constructor(props /*: Props */) {
-    super(props);
-    this.state = {
-      loading: false,
-
-      avatar: props.organization.avatar || '',
-      avatarImage: props.organization.avatar || '',
-      description: props.organization.description || '',
-      name: props.organization.name,
-      url: props.organization.url || ''
-    };
-    this.changeAvatarImage = debounce(this.changeAvatarImage, 500);
-  }
-
-  componentDidMount() {
-    this.mounted = true;
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  handleAvatarInputChange = (e /*: Object */) => {
-    const { value } = e.target;
-    this.setState({ avatar: value });
-    this.changeAvatarImage(value);
-  };
-
-  changeAvatarImage = (value /*: string */) => {
-    this.setState({ avatarImage: value });
-  };
-
-  handleSubmit = (e /*: Object */) => {
-    e.preventDefault();
-    const changes = {
-      avatar: this.state.avatar,
-      description: this.state.description,
-      name: this.state.name,
-      url: this.state.url
-    };
-    this.setState({ loading: true });
-    this.props.updateOrganization(this.props.organization.key, changes).then(() => {
-      if (this.mounted) {
-        this.setState({ loading: false });
-      }
-    });
-  };
-
-  render() {
-    const title = translate('organization.edit');
-    return (
-      <div className="page page-limited">
-        <Helmet title={title} />
-
-        <header className="page-header">
-          <h1 className="page-title">{title}</h1>
-        </header>
-
-        <div className="boxed-group boxed-group-inner">
-          <form onSubmit={this.handleSubmit}>
-            <div className="modal-field">
-              <label htmlFor="organization-name">
-                {translate('organization.name')}
-                <em className="mandatory">*</em>
-              </label>
-              <input
-                disabled={this.state.loading}
-                id="organization-name"
-                maxLength="64"
-                name="name"
-                onChange={e => this.setState({ name: e.target.value })}
-                required={true}
-                type="text"
-                value={this.state.name}
-              />
-              <div className="modal-field-description">
-                {translate('organization.name.description')}
-              </div>
-            </div>
-            <div className="modal-field">
-              <label htmlFor="organization-avatar">{translate('organization.avatar')}</label>
-              <input
-                disabled={this.state.loading}
-                id="organization-avatar"
-                maxLength="256"
-                name="avatar"
-                onChange={this.handleAvatarInputChange}
-                type="text"
-                value={this.state.avatar}
-              />
-              <div className="modal-field-description">
-                {translate('organization.avatar.description')}
-              </div>
-              {!!this.state.avatarImage && (
-                <div className="spacer-top spacer-bottom">
-                  <div className="little-spacer-bottom">
-                    {translate('organization.avatar.preview')}
-                    {':'}
-                  </div>
-                  <img alt="" height={30} src={this.state.avatarImage} />
-                </div>
-              )}
-            </div>
-            <div className="modal-field">
-              <label htmlFor="organization-description">{translate('description')}</label>
-              <textarea
-                disabled={this.state.loading}
-                id="organization-description"
-                maxLength="256"
-                name="description"
-                onChange={e => this.setState({ description: e.target.value })}
-                rows="3"
-                value={this.state.description}
-              />
-              <div className="modal-field-description">
-                {translate('organization.description.description')}
-              </div>
-            </div>
-            <div className="modal-field">
-              <label htmlFor="organization-url">{translate('organization.url')}</label>
-              <input
-                disabled={this.state.loading}
-                id="organization-url"
-                maxLength="256"
-                name="url"
-                onChange={e => this.setState({ url: e.target.value })}
-                type="text"
-                value={this.state.url}
-              />
-              <div className="modal-field-description">
-                {translate('organization.url.description')}
-              </div>
-            </div>
-            <div className="modal-field">
-              <SubmitButton disabled={this.state.loading}>{translate('save')}</SubmitButton>
-              {this.state.loading && <i className="spinner spacer-left" />}
-            </div>
-          </form>
-        </div>
-      </div>
-    );
-  }
-}
-
-const mapDispatchToProps = { updateOrganization };
-
-export default connect(null, mapDispatchToProps)(OrganizationEdit);
-
-export const UnconnectedOrganizationEdit = OrganizationEdit;
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.tsx
new file mode 100644 (file)
index 0000000..af96e5e
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * 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 { connect } from 'react-redux';
+import { debounce } from 'lodash';
+import { translate } from '../../../helpers/l10n';
+import { updateOrganization } from '../actions';
+import { SubmitButton } from '../../../components/ui/buttons';
+import { Organization, OrganizationBase } from '../../../app/types';
+
+interface DispatchProps {
+  updateOrganization: (organization: string, changes: OrganizationBase) => Promise<any>;
+}
+
+interface OwnProps {
+  organization: Organization;
+}
+
+type Props = OwnProps & DispatchProps;
+
+interface State {
+  loading: boolean;
+  avatar: string;
+  avatarImage: string;
+  description: string;
+  name: string;
+  url: string;
+}
+
+export class OrganizationEdit extends React.PureComponent<Props, State> {
+  mounted = false;
+
+  constructor(props: Props) {
+    super(props);
+    this.state = {
+      loading: false,
+      avatar: props.organization.avatar || '',
+      avatarImage: props.organization.avatar || '',
+      description: props.organization.description || '',
+      name: props.organization.name,
+      url: props.organization.url || ''
+    };
+    this.changeAvatarImage = debounce(this.changeAvatarImage, 500);
+  }
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  handleAvatarInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    const { value } = event.target;
+    this.setState({ avatar: value });
+    this.changeAvatarImage(value);
+  };
+
+  changeAvatarImage = (value: string) => {
+    this.setState({ avatarImage: value });
+  };
+
+  handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
+    event.preventDefault();
+    const changes = {
+      avatar: this.state.avatar,
+      description: this.state.description,
+      name: this.state.name,
+      url: this.state.url
+    };
+    this.setState({ loading: true });
+    this.props
+      .updateOrganization(this.props.organization.key, changes)
+      .then(this.stopLoading, this.stopLoading);
+  };
+
+  stopLoading = () => {
+    if (this.mounted) {
+      this.setState({ loading: false });
+    }
+  };
+
+  render() {
+    const title = translate('organization.edit');
+    return (
+      <div className="page page-limited">
+        <Helmet title={title} />
+
+        <header className="page-header">
+          <h1 className="page-title">{title}</h1>
+        </header>
+
+        <div className="boxed-group boxed-group-inner">
+          <form onSubmit={this.handleSubmit}>
+            <div className="modal-field">
+              <label htmlFor="organization-name">
+                {translate('organization.name')}
+                <em className="mandatory">*</em>
+              </label>
+              <input
+                disabled={this.state.loading}
+                id="organization-name"
+                maxLength={64}
+                name="name"
+                onChange={e => this.setState({ name: e.target.value })}
+                required={true}
+                type="text"
+                value={this.state.name}
+              />
+              <div className="modal-field-description">
+                {translate('organization.name.description')}
+              </div>
+            </div>
+            <div className="modal-field">
+              <label htmlFor="organization-avatar">{translate('organization.avatar')}</label>
+              <input
+                disabled={this.state.loading}
+                id="organization-avatar"
+                maxLength={256}
+                name="avatar"
+                onChange={this.handleAvatarInputChange}
+                type="text"
+                value={this.state.avatar}
+              />
+              <div className="modal-field-description">
+                {translate('organization.avatar.description')}
+              </div>
+              {!!this.state.avatarImage && (
+                <div className="spacer-top spacer-bottom">
+                  <div className="little-spacer-bottom">
+                    {translate('organization.avatar.preview')}
+                    {':'}
+                  </div>
+                  <img alt="" height={30} src={this.state.avatarImage} />
+                </div>
+              )}
+            </div>
+            <div className="modal-field">
+              <label htmlFor="organization-description">{translate('description')}</label>
+              <textarea
+                disabled={this.state.loading}
+                id="organization-description"
+                maxLength={256}
+                name="description"
+                onChange={e => this.setState({ description: e.target.value })}
+                rows={3}
+                value={this.state.description}
+              />
+              <div className="modal-field-description">
+                {translate('organization.description.description')}
+              </div>
+            </div>
+            <div className="modal-field">
+              <label htmlFor="organization-url">{translate('organization.url')}</label>
+              <input
+                disabled={this.state.loading}
+                id="organization-url"
+                maxLength={256}
+                name="url"
+                onChange={e => this.setState({ url: e.target.value })}
+                type="text"
+                value={this.state.url}
+              />
+              <div className="modal-field-description">
+                {translate('organization.url.description')}
+              </div>
+            </div>
+            <div className="modal-field">
+              <SubmitButton disabled={this.state.loading}>{translate('save')}</SubmitButton>
+              {this.state.loading && <i className="spinner spacer-left" />}
+            </div>
+          </form>
+        </div>
+      </div>
+    );
+  }
+}
+
+const mapDispatchToProps = { updateOrganization: updateOrganization as any };
+
+export default connect<{}, DispatchProps, OwnProps>(null, mapDispatchToProps)(OrganizationEdit);
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.js
deleted file mode 100644 (file)
index 95fd1e0..0000000
+++ /dev/null
@@ -1,60 +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
-import React from 'react';
-import Checkbox from '../../../components/controls/Checkbox';
-/*:: import type { Group } from '../../../app/types'; */
-
-/*::
-type Props = {
-  group: Group,
-  checked: boolean,
-  onCheck: (string, boolean) => void
-};
-*/
-
-export default class OrganizationGroupCheckbox extends React.PureComponent {
-  /*:: props: Props; */
-
-  onCheck = (checked /*: boolean */) => {
-    const { group } = this.props;
-    if (!group.default) {
-      this.props.onCheck(group.name, checked);
-    }
-  };
-
-  toggleCheck = () => {
-    this.onCheck(!this.props.checked);
-  };
-
-  render() {
-    const { group } = this.props;
-    return (
-      <li
-        className="capitalize list-item-checkable-link"
-        onClick={this.toggleCheck}
-        tabIndex={0}
-        role="listitem"
-        disabled={group.default}>
-        <Checkbox checked={this.props.checked} onCheck={this.onCheck} /> {group.name}
-      </li>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.tsx
new file mode 100644 (file)
index 0000000..a1209ec
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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 * as classNames from 'classnames';
+import Checkbox from '../../../components/controls/Checkbox';
+import { Group } from '../../../app/types';
+
+interface Props {
+  group: Group;
+  checked: boolean;
+  onCheck: (name: string, checked: boolean) => void;
+}
+
+export default class OrganizationGroupCheckbox extends React.PureComponent<Props> {
+  onCheck = (checked: boolean) => {
+    const { group } = this.props;
+    if (!group.default) {
+      this.props.onCheck(group.name, checked);
+    }
+  };
+
+  toggleCheck = () => {
+    this.onCheck(!this.props.checked);
+  };
+
+  render() {
+    const { group } = this.props;
+    return (
+      <li
+        className={classNames('capitalize list-item-checkable-link', { disabled: group.default })}
+        onClick={this.toggleCheck}>
+        <Checkbox checked={this.props.checked} onCheck={this.onCheck} /> {group.name}
+      </li>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js
deleted file mode 100644 (file)
index fdafac5..0000000
+++ /dev/null
@@ -1,126 +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
-import 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 type { Organization, Group } from '../../../app/types'; */
-/*:: import type { Member } from '../../../store/organizationsMembers/actions'; */
-
-/*::
-type Props = {
-  members: Array<Member>,
-  memberLogins: Array<string>,
-  organizationGroups: Array<Group>,
-  status: { loading?: boolean, total?: number, pageIndex?: number, query?: string },
-  organization: Organization,
-  fetchOrganizationMembers: (organizationKey: string, query?: string) => void,
-  fetchMoreOrganizationMembers: (organizationKey: string, query?: string) => void,
-  fetchOrganizationGroups: (organizationKey: string) => void,
-  addOrganizationMember: (organizationKey: string, member: Member) => void,
-  removeOrganizationMember: (organizationKey: string, member: Member) => void,
-  updateOrganizationMemberGroups: (
-    organization: Organization,
-    member: Member,
-    add: Array<string>,
-    remove: Array<string>
-  ) => void
-};
-*/
-
-export default class OrganizationMembers extends React.PureComponent {
-  /*:: props: Props; */
-
-  componentDidMount() {
-    this.handleSearchMembers();
-    if (this.props.organization.canAdmin) {
-      this.props.fetchOrganizationGroups(this.props.organization.key);
-    }
-  }
-
-  handleSearchMembers = (query /*: string | void */) => {
-    this.props.fetchOrganizationMembers(this.props.organization.key, query);
-  };
-
-  handleLoadMoreMembers = () => {
-    this.props.fetchMoreOrganizationMembers(this.props.organization.key, this.props.status.query);
-  };
-
-  addMember = (member /*: Member */) => {
-    this.props.addOrganizationMember(this.props.organization.key, member);
-  };
-
-  removeMember = (member /*: Member */) => {
-    this.props.removeOrganizationMember(this.props.organization.key, member);
-  };
-
-  updateMemberGroups = (
-    member /*: Member */,
-    add /*: Array<string> */,
-    remove /*: Array<string> */
-  ) => {
-    this.props.updateOrganizationMemberGroups(this.props.organization, member, add, remove);
-  };
-
-  render() {
-    const { organization, status, members } = this.props;
-    return (
-      <div className="page page-limited">
-        <Helmet title={translate('organization.members.page')} />
-        <Suggestions suggestions="organization_members" />
-        <MembersPageHeader loading={status.loading}>
-          {organization.canAdmin && (
-            <div className="page-actions">
-              <AddMemberForm
-                addMember={this.addMember}
-                memberLogins={this.props.memberLogins}
-                organization={organization}
-              />
-              <DocTooltip className="spacer-left" doc="organizations/add-organization-member" />
-            </div>
-          )}
-        </MembersPageHeader>
-        <MembersListHeader handleSearch={this.handleSearchMembers} total={status.total} />
-        <MembersList
-          members={members}
-          organization={organization}
-          organizationGroups={this.props.organizationGroups}
-          removeMember={this.removeMember}
-          updateMemberGroups={this.updateMemberGroups}
-        />
-        {status.total != null && (
-          <ListFooter
-            count={members.length}
-            loadMore={this.handleLoadMoreMembers}
-            ready={!status.loading}
-            total={status.total}
-          />
-        )}
-      </div>
-    );
-  }
-}
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
new file mode 100644 (file)
index 0000000..68096c9
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * 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<Props> {
+  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 (
+      <div className="page page-limited">
+        <Helmet title={translate('organization.members.page')} />
+        <Suggestions suggestions="organization_members" />
+        <MembersPageHeader loading={Boolean(status.loading)}>
+          {organization.canAdmin && (
+            <div className="page-actions">
+              <AddMemberForm
+                addMember={this.addMember}
+                memberLogins={this.props.memberLogins}
+                organization={organization}
+              />
+              <DocTooltip className="spacer-left" doc="organizations/add-organization-member" />
+            </div>
+          )}
+        </MembersPageHeader>
+        <MembersListHeader handleSearch={this.handleSearchMembers} total={status.total} />
+        <MembersList
+          members={members}
+          organization={organization}
+          organizationGroups={this.props.organizationGroups}
+          removeMember={this.removeMember}
+          updateMemberGroups={this.updateMemberGroups}
+        />
+        {status.total != null && (
+          <ListFooter
+            count={members.length}
+            loadMore={this.handleLoadMoreMembers}
+            ready={!status.loading}
+            total={status.total}
+          />
+        )}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.js
deleted file mode 100644 (file)
index 203757e..0000000
+++ /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 { 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';
-
-const mapStateToProps = (state, ownProps) => {
-  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)
-  };
-};
-
-export default connect(mapStateToProps, {
-  fetchOrganizationMembers,
-  fetchMoreOrganizationMembers,
-  fetchOrganizationGroups,
-  addOrganizationMember,
-  removeOrganizationMember,
-  updateOrganizationMemberGroups
-})(OrganizationMembers);
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
new file mode 100644 (file)
index 0000000..cbc1d9d
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)(
+  OrganizationMembers
+);
index 09ae20f81513d49df6439a3a8a6adbd250dab84f..ce54a3eea92390ef0af6809d4caa961354dd5c83 100644 (file)
 import * as React from 'react';
 import AllProjectsContainer from '../../projects/components/AllProjectsContainer';
 import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import { Organization } from '../../../app/types';
 
 interface Props {
   location: { pathname: string; query: { [x: string]: string } };
-  organization: { key: string };
+  organization: Organization;
 }
 
 export default function OrganizationProjects(props: Props) {
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersList-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersList-test.js
deleted file mode 100644 (file)
index 6c527e1..0000000
+++ /dev/null
@@ -1,33 +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 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(<MembersList organization={organization} members={members} />);
-  expect(wrapper).toMatchSnapshot();
-});
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
new file mode 100644 (file)
index 0000000..0910f62
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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(
+    <MembersList
+      members={members}
+      organization={organization}
+      organizationGroups={[]}
+      removeMember={jest.fn()}
+      updateMemberGroups={jest.fn()}
+    />
+  );
+  expect(wrapper).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.js
deleted file mode 100644 (file)
index a4c3be4..0000000
+++ /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 React from 'react';
-import { shallow } from 'enzyme';
-import MembersListHeader from '../MembersListHeader';
-
-it('should render without the total', () => {
-  const wrapper = shallow(<MembersListHeader handleSearch={jest.fn()} />);
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should render with the total', () => {
-  const wrapper = shallow(<MembersListHeader handleSearch={jest.fn()} total={8} />);
-  expect(wrapper).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.tsx
new file mode 100644 (file)
index 0000000..ffc4241
--- /dev/null
@@ -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(<MembersListHeader handleSearch={jest.fn()} />);
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('should render with the total', () => {
+  const wrapper = shallow(<MembersListHeader handleSearch={jest.fn()} total={8} />);
+  expect(wrapper).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.js
deleted file mode 100644 (file)
index 9e2455d..0000000
+++ /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 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(<MembersListItem organization={organization} member={admin} />);
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should render actions and groups for admin', () => {
-  const wrapper = shallow(
-    <MembersListItem organization={{ ...organization, canAdmin: true }} member={admin} />
-  );
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should groups at 0 if the groupCount field is not defined (just added user)', () => {
-  const wrapper = shallow(
-    <MembersListItem organization={{ ...organization, canAdmin: true }} member={john} />
-  );
-  expect(wrapper).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.tsx
new file mode 100644 (file)
index 0000000..9838194
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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(
+    <MembersListItem
+      member={admin}
+      organization={organization}
+      organizationGroups={[]}
+      removeMember={jest.fn()}
+      updateMemberGroups={jest.fn()}
+    />
+  );
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('should render actions and groups for admin', () => {
+  const wrapper = shallow(
+    <MembersListItem
+      member={admin}
+      organization={{ ...organization, canAdmin: true }}
+      organizationGroups={[]}
+      removeMember={jest.fn()}
+      updateMemberGroups={jest.fn()}
+    />
+  );
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('should groups at 0 if the groupCount field is not defined (just added user)', () => {
+  const wrapper = shallow(
+    <MembersListItem
+      member={john}
+      organization={{ ...organization, canAdmin: true }}
+      organizationGroups={[]}
+      removeMember={jest.fn()}
+      updateMemberGroups={jest.fn()}
+    />
+  );
+  expect(wrapper).toMatchSnapshot();
+});
index 17b1fd80bb1b8e2b4e7a7bef8c092c4fe6696f7c..8fcb64c11f34df9db4bd90e394e99046426021fc 100644 (file)
@@ -55,7 +55,8 @@ describe('component', () => {
           currentUser={loggedInUser}
           hasAccess={() => true}
           location={locationMock}
-          organization={adminOrganization}>
+          organization={adminOrganization}
+          userOrganizations={[]}>
           <div>hello</div>
         </OrganizationAccess>
       )
@@ -69,7 +70,8 @@ describe('component', () => {
           currentUser={loggedInUser}
           hasAccess={() => false}
           location={locationMock}
-          organization={adminOrganization}>
+          organization={adminOrganization}
+          userOrganizations={[]}>
           <div>hello</div>
         </OrganizationAccess>
       ).type()
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEdit-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEdit-test.js
deleted file mode 100644 (file)
index cd3c2b0..0000000
+++ /dev/null
@@ -1,40 +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 React from 'react';
-import { shallow } from 'enzyme';
-import { UnconnectedOrganizationEdit } from '../OrganizationEdit';
-
-it('smoke test', () => {
-  const organization = { key: 'foo', name: 'Foo' };
-  const wrapper = shallow(<UnconnectedOrganizationEdit organization={organization} />);
-  expect(wrapper).toMatchSnapshot();
-
-  wrapper.setState({
-    avatar: 'foo-avatar',
-    avatarImage: 'foo-avatar-image',
-    description: 'foo-description',
-    name: 'New Foo',
-    url: 'foo-url'
-  });
-  expect(wrapper).toMatchSnapshot();
-
-  wrapper.setState({ loading: true });
-  expect(wrapper).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEdit-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEdit-test.tsx
new file mode 100644 (file)
index 0000000..fb4efaf
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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 { OrganizationEdit } from '../OrganizationEdit';
+
+it('smoke test', () => {
+  const organization = { key: 'foo', name: 'Foo' };
+  const wrapper = shallow(
+    <OrganizationEdit organization={organization} updateOrganization={jest.fn()} />
+  );
+  expect(wrapper).toMatchSnapshot();
+
+  wrapper.setState({
+    avatar: 'foo-avatar',
+    avatarImage: 'foo-avatar-image',
+    description: 'foo-description',
+    name: 'New Foo',
+    url: 'foo-url'
+  });
+  expect(wrapper).toMatchSnapshot();
+
+  wrapper.setState({ loading: true });
+  expect(wrapper).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.js
deleted file mode 100644 (file)
index a092526..0000000
+++ /dev/null
@@ -1,62 +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 React from 'react';
-import { shallow } from 'enzyme';
-import OrganizationGroupCheckbox from '../OrganizationGroupCheckbox';
-
-const group = {
-  id: '7',
-  name: 'professionals',
-  description: '',
-  membersCount: 12,
-  default: false
-};
-
-it('should render unchecked', () => {
-  const wrapper = shallow(
-    <OrganizationGroupCheckbox group={group} checked={false} onCheck={jest.fn()} />
-  );
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should be able to toggle check', () => {
-  const onCheck = jest.fn((group, checked) => wrapper.setProps({ checked }));
-  const wrapper = shallow(
-    <OrganizationGroupCheckbox group={group} checked={true} onCheck={onCheck} />
-  );
-  expect(wrapper).toMatchSnapshot();
-  wrapper.instance().toggleCheck();
-  expect(onCheck.mock.calls).toMatchSnapshot();
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should disabled default groups', () => {
-  const onCheck = jest.fn((group, checked) => wrapper.setProps({ checked }));
-  const wrapper = shallow(
-    <OrganizationGroupCheckbox
-      group={{ ...group, default: true }}
-      checked={true}
-      onCheck={onCheck}
-    />
-  );
-  expect(wrapper).toMatchSnapshot();
-  wrapper.instance().toggleCheck();
-  expect(onCheck.mock.calls.length).toBe(0);
-});
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.tsx
new file mode 100644 (file)
index 0000000..2cc4d06
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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 OrganizationGroupCheckbox from '../OrganizationGroupCheckbox';
+
+const group = {
+  id: 7,
+  name: 'professionals',
+  description: '',
+  membersCount: 12,
+  default: false
+};
+
+it('should render unchecked', () => {
+  const wrapper = shallow(
+    <OrganizationGroupCheckbox checked={false} group={group} onCheck={jest.fn()} />
+  );
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('should be able to toggle check', () => {
+  const onCheck = jest.fn().mockImplementation((_group, checked) => wrapper.setProps({ checked }));
+  const wrapper = shallow(
+    <OrganizationGroupCheckbox checked={true} group={group} onCheck={onCheck} />
+  );
+  expect(wrapper).toMatchSnapshot();
+  (wrapper.instance() as OrganizationGroupCheckbox).toggleCheck();
+  expect(onCheck.mock.calls).toMatchSnapshot();
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('should disabled default groups', () => {
+  const onCheck = jest.fn().mockImplementation((_group, checked) => wrapper.setProps({ checked }));
+  const wrapper = shallow(
+    <OrganizationGroupCheckbox
+      checked={true}
+      group={{ ...group, default: true }}
+      onCheck={onCheck}
+    />
+  );
+  expect(wrapper).toMatchSnapshot();
+  (wrapper.instance() as OrganizationGroupCheckbox).toggleCheck();
+  expect(onCheck.mock.calls.length).toBe(0);
+});
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.js
deleted file mode 100644 (file)
index f7cd219..0000000
+++ /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 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(
-    <OrganizationMembers
-      organization={organization}
-      members={members}
-      status={status}
-      fetchOrganizationMembers={jest.fn()}
-      fetchOrganizationGroups={jest.fn()}
-      fetchMoreOrganizationMembers={jest.fn()}
-    />
-  );
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should render actions for admin', () => {
-  const wrapper = shallow(
-    <OrganizationMembers
-      organization={{ ...organization, canAdmin: true }}
-      members={members}
-      status={{ ...status, loading: true }}
-      fetchOrganizationMembers={jest.fn()}
-      fetchOrganizationGroups={jest.fn()}
-      fetchMoreOrganizationMembers={jest.fn()}
-    />
-  );
-  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
new file mode 100644 (file)
index 0000000..de18ce7
--- /dev/null
@@ -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 { 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(
+    <OrganizationMembers
+      addOrganizationMember={jest.fn()}
+      fetchMoreOrganizationMembers={jest.fn()}
+      fetchOrganizationGroups={jest.fn()}
+      fetchOrganizationMembers={jest.fn()}
+      memberLogins={[]}
+      members={members}
+      organization={organization}
+      organizationGroups={[]}
+      removeOrganizationMember={jest.fn()}
+      status={status}
+      updateOrganizationMemberGroups={jest.fn()}
+    />
+  );
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('should render actions for admin', () => {
+  const wrapper = shallow(
+    <OrganizationMembers
+      addOrganizationMember={jest.fn()}
+      fetchMoreOrganizationMembers={jest.fn()}
+      fetchOrganizationGroups={jest.fn()}
+      fetchOrganizationMembers={jest.fn()}
+      memberLogins={[]}
+      members={members}
+      organization={{ ...organization, canAdmin: true }}
+      organizationGroups={[]}
+      removeOrganizationMember={jest.fn()}
+      status={{ ...status, loading: true }}
+      updateOrganizationMemberGroups={jest.fn()}
+    />
+  );
+  expect(wrapper).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationPage-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationPage-test.js
deleted file mode 100644 (file)
index ad76461..0000000
+++ /dev/null
@@ -1,59 +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 React from 'react';
-import { shallow } from 'enzyme';
-import { OrganizationPage } from '../OrganizationPage';
-
-const fetchOrganization = () => Promise.resolve();
-
-it('smoke test', () => {
-  const wrapper = shallow(
-    <OrganizationPage fetchOrganization={fetchOrganization} params={{ organizationKey: 'foo' }}>
-      <div>hello</div>
-    </OrganizationPage>
-  );
-  expect(wrapper.type()).toBeNull();
-
-  const organization = { key: 'foo', name: 'Foo', isDefault: false, canAdmin: false };
-  wrapper.setProps({ organization });
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('not found', () => {
-  const wrapper = shallow(
-    <OrganizationPage fetchOrganization={fetchOrganization} params={{ organizationKey: 'foo' }}>
-      <div>hello</div>
-    </OrganizationPage>
-  );
-  wrapper.setState({ loading: false });
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should correctly update when the organization changes', () => {
-  const fetchOrganization = jest.fn(() => Promise.resolve());
-  const wrapper = shallow(
-    <OrganizationPage params={{ organizationKey: 'foo' }} fetchOrganization={fetchOrganization}>
-      <div>hello</div>
-    </OrganizationPage>
-  );
-  wrapper.setProps({ params: { organizationKey: 'bar' } });
-  expect(fetchOrganization).toHaveBeenCalledTimes(2);
-  expect(fetchOrganization.mock.calls).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationPage-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationPage-test.tsx
new file mode 100644 (file)
index 0000000..e1230f3
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * 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 { OrganizationPage } from '../OrganizationPage';
+
+const fetchOrganization = () => Promise.resolve();
+
+it('smoke test', () => {
+  const wrapper = shallow(
+    <OrganizationPage
+      fetchOrganization={fetchOrganization}
+      location={{ pathname: 'foo' }}
+      params={{ organizationKey: 'foo' }}>
+      <div>hello</div>
+    </OrganizationPage>
+  );
+  expect(wrapper.type()).toBeNull();
+
+  const organization = { key: 'foo', name: 'Foo', isDefault: false, canAdmin: false };
+  wrapper.setProps({ organization });
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('not found', () => {
+  const wrapper = shallow(
+    <OrganizationPage
+      fetchOrganization={fetchOrganization}
+      location={{ pathname: 'foo' }}
+      params={{ organizationKey: 'foo' }}>
+      <div>hello</div>
+    </OrganizationPage>
+  );
+  wrapper.setState({ loading: false });
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('should correctly update when the organization changes', () => {
+  const fetchOrganization = jest.fn(() => Promise.resolve());
+  const wrapper = shallow(
+    <OrganizationPage
+      fetchOrganization={fetchOrganization}
+      location={{ pathname: 'foo' }}
+      params={{ organizationKey: 'foo' }}>
+      <div>hello</div>
+    </OrganizationPage>
+  );
+  wrapper.setProps({ params: { organizationKey: 'bar' } });
+  expect(fetchOrganization).toHaveBeenCalledTimes(2);
+  expect(fetchOrganization.mock.calls).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersList-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersList-test.js.snap
deleted file mode 100644 (file)
index fc8f227..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render a list of members of an organization 1`] = `
-<div
-  className="boxed-group boxed-group-inner"
->
-  <table
-    className="data zebra"
-  >
-    <tbody>
-      <MembersListItem
-        key="admin"
-        member={
-          Object {
-            "avatar": "",
-            "groupCount": 3,
-            "login": "admin",
-            "name": "Admin Istrator",
-          }
-        }
-        organization={
-          Object {
-            "key": "foo",
-            "name": "Foo",
-          }
-        }
-      />
-      <MembersListItem
-        key="john"
-        member={
-          Object {
-            "avatar": "7daf6c79d4802916d83f6266e24850af",
-            "groupCount": 1,
-            "login": "john",
-            "name": "John Doe",
-          }
-        }
-        organization={
-          Object {
-            "key": "foo",
-            "name": "Foo",
-          }
-        }
-      />
-    </tbody>
-  </table>
-</div>
-`;
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
new file mode 100644 (file)
index 0000000..57ff0c5
--- /dev/null
@@ -0,0 +1,54 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render a list of members of an organization 1`] = `
+<div
+  className="boxed-group boxed-group-inner"
+>
+  <table
+    className="data zebra"
+  >
+    <tbody>
+      <MembersListItem
+        key="admin"
+        member={
+          Object {
+            "avatar": "",
+            "groupCount": 3,
+            "login": "admin",
+            "name": "Admin Istrator",
+          }
+        }
+        organization={
+          Object {
+            "key": "foo",
+            "name": "Foo",
+          }
+        }
+        organizationGroups={Array []}
+        removeMember={[MockFunction]}
+        updateMemberGroups={[MockFunction]}
+      />
+      <MembersListItem
+        key="john"
+        member={
+          Object {
+            "avatar": "7daf6c79d4802916d83f6266e24850af",
+            "groupCount": 1,
+            "login": "john",
+            "name": "John Doe",
+          }
+        }
+        organization={
+          Object {
+            "key": "foo",
+            "name": "Foo",
+          }
+        }
+        organizationGroups={Array []}
+        removeMember={[MockFunction]}
+        updateMemberGroups={[MockFunction]}
+      />
+    </tbody>
+  </table>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListHeader-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListHeader-test.js.snap
deleted file mode 100644 (file)
index 7bfe272..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render with the total 1`] = `
-<div
-  className="panel panel-vertical bordered-bottom spacer-bottom"
->
-  <SearchBox
-    minLength={2}
-    onChange={[MockFunction]}
-    placeholder="search.search_for_users"
-  />
-  <span
-    className="pull-right little-spacer-top"
-  >
-    <strong>
-      8
-    </strong>
-     
-    organization.members.members
-  </span>
-</div>
-`;
-
-exports[`should render without the total 1`] = `
-<div
-  className="panel panel-vertical bordered-bottom spacer-bottom"
->
-  <SearchBox
-    minLength={2}
-    onChange={[MockFunction]}
-    placeholder="search.search_for_users"
-  />
-</div>
-`;
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
new file mode 100644 (file)
index 0000000..7bfe272
--- /dev/null
@@ -0,0 +1,34 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render with the total 1`] = `
+<div
+  className="panel panel-vertical bordered-bottom spacer-bottom"
+>
+  <SearchBox
+    minLength={2}
+    onChange={[MockFunction]}
+    placeholder="search.search_for_users"
+  />
+  <span
+    className="pull-right little-spacer-top"
+  >
+    <strong>
+      8
+    </strong>
+     
+    organization.members.members
+  </span>
+</div>
+`;
+
+exports[`should render without the total 1`] = `
+<div
+  className="panel panel-vertical bordered-bottom spacer-bottom"
+>
+  <SearchBox
+    minLength={2}
+    onChange={[MockFunction]}
+    placeholder="search.search_for_users"
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap
deleted file mode 100644 (file)
index 2f04b60..0000000
+++ /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`] = `
-<tr>
-  <td
-    className="thin nowrap"
-  >
-    <Connect(Avatar)
-      hash="7daf6c79d4802916d83f6266e24850af"
-      name="John Doe"
-      size={36}
-    />
-  </td>
-  <td
-    className="nowrap text-middle"
-  >
-    <strong>
-      John Doe
-    </strong>
-    <span
-      className="note little-spacer-left"
-    >
-      john
-    </span>
-  </td>
-  <td
-    className="text-right text-middle"
-  >
-    organization.members.x_groups.0
-  </td>
-  <React.Fragment>
-    <td
-      className="nowrap text-middle text-right"
-    >
-      <ActionsDropdown>
-        <ActionsDropdownItem
-          onClick={[Function]}
-        >
-          organization.members.manage_groups
-        </ActionsDropdownItem>
-        <ActionsDropdownDivider />
-        <ActionsDropdownItem
-          destructive={true}
-          onClick={[Function]}
-        >
-          organization.members.remove
-        </ActionsDropdownItem>
-      </ActionsDropdown>
-    </td>
-  </React.Fragment>
-</tr>
-`;
-
-exports[`should not render actions and groups for non admin 1`] = `
-<tr>
-  <td
-    className="thin nowrap"
-  >
-    <Connect(Avatar)
-      hash=""
-      name="Admin Istrator"
-      size={36}
-    />
-  </td>
-  <td
-    className="nowrap text-middle"
-  >
-    <strong>
-      Admin Istrator
-    </strong>
-    <span
-      className="note little-spacer-left"
-    >
-      admin
-    </span>
-  </td>
-</tr>
-`;
-
-exports[`should render actions and groups for admin 1`] = `
-<tr>
-  <td
-    className="thin nowrap"
-  >
-    <Connect(Avatar)
-      hash=""
-      name="Admin Istrator"
-      size={36}
-    />
-  </td>
-  <td
-    className="nowrap text-middle"
-  >
-    <strong>
-      Admin Istrator
-    </strong>
-    <span
-      className="note little-spacer-left"
-    >
-      admin
-    </span>
-  </td>
-  <td
-    className="text-right text-middle"
-  >
-    organization.members.x_groups.3
-  </td>
-  <React.Fragment>
-    <td
-      className="nowrap text-middle text-right"
-    >
-      <ActionsDropdown>
-        <ActionsDropdownItem
-          onClick={[Function]}
-        >
-          organization.members.manage_groups
-        </ActionsDropdownItem>
-        <ActionsDropdownDivider />
-        <ActionsDropdownItem
-          destructive={true}
-          onClick={[Function]}
-        >
-          organization.members.remove
-        </ActionsDropdownItem>
-      </ActionsDropdown>
-    </td>
-  </React.Fragment>
-</tr>
-`;
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
new file mode 100644 (file)
index 0000000..2f04b60
--- /dev/null
@@ -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`] = `
+<tr>
+  <td
+    className="thin nowrap"
+  >
+    <Connect(Avatar)
+      hash="7daf6c79d4802916d83f6266e24850af"
+      name="John Doe"
+      size={36}
+    />
+  </td>
+  <td
+    className="nowrap text-middle"
+  >
+    <strong>
+      John Doe
+    </strong>
+    <span
+      className="note little-spacer-left"
+    >
+      john
+    </span>
+  </td>
+  <td
+    className="text-right text-middle"
+  >
+    organization.members.x_groups.0
+  </td>
+  <React.Fragment>
+    <td
+      className="nowrap text-middle text-right"
+    >
+      <ActionsDropdown>
+        <ActionsDropdownItem
+          onClick={[Function]}
+        >
+          organization.members.manage_groups
+        </ActionsDropdownItem>
+        <ActionsDropdownDivider />
+        <ActionsDropdownItem
+          destructive={true}
+          onClick={[Function]}
+        >
+          organization.members.remove
+        </ActionsDropdownItem>
+      </ActionsDropdown>
+    </td>
+  </React.Fragment>
+</tr>
+`;
+
+exports[`should not render actions and groups for non admin 1`] = `
+<tr>
+  <td
+    className="thin nowrap"
+  >
+    <Connect(Avatar)
+      hash=""
+      name="Admin Istrator"
+      size={36}
+    />
+  </td>
+  <td
+    className="nowrap text-middle"
+  >
+    <strong>
+      Admin Istrator
+    </strong>
+    <span
+      className="note little-spacer-left"
+    >
+      admin
+    </span>
+  </td>
+</tr>
+`;
+
+exports[`should render actions and groups for admin 1`] = `
+<tr>
+  <td
+    className="thin nowrap"
+  >
+    <Connect(Avatar)
+      hash=""
+      name="Admin Istrator"
+      size={36}
+    />
+  </td>
+  <td
+    className="nowrap text-middle"
+  >
+    <strong>
+      Admin Istrator
+    </strong>
+    <span
+      className="note little-spacer-left"
+    >
+      admin
+    </span>
+  </td>
+  <td
+    className="text-right text-middle"
+  >
+    organization.members.x_groups.3
+  </td>
+  <React.Fragment>
+    <td
+      className="nowrap text-middle text-right"
+    >
+      <ActionsDropdown>
+        <ActionsDropdownItem
+          onClick={[Function]}
+        >
+          organization.members.manage_groups
+        </ActionsDropdownItem>
+        <ActionsDropdownDivider />
+        <ActionsDropdownItem
+          destructive={true}
+          onClick={[Function]}
+        >
+          organization.members.remove
+        </ActionsDropdownItem>
+      </ActionsDropdown>
+    </td>
+  </React.Fragment>
+</tr>
+`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.js.snap
deleted file mode 100644 (file)
index 0abba69..0000000
+++ /dev/null
@@ -1,442 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`smoke test 1`] = `
-<div
-  className="page page-limited"
->
-  <HelmetWrapper
-    defer={true}
-    encodeSpecialCharacters={true}
-    title="organization.edit"
-  />
-  <header
-    className="page-header"
-  >
-    <h1
-      className="page-title"
-    >
-      organization.edit
-    </h1>
-  </header>
-  <div
-    className="boxed-group boxed-group-inner"
-  >
-    <form
-      onSubmit={[Function]}
-    >
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="organization-name"
-        >
-          organization.name
-          <em
-            className="mandatory"
-          >
-            *
-          </em>
-        </label>
-        <input
-          disabled={false}
-          id="organization-name"
-          maxLength="64"
-          name="name"
-          onChange={[Function]}
-          required={true}
-          type="text"
-          value="Foo"
-        />
-        <div
-          className="modal-field-description"
-        >
-          organization.name.description
-        </div>
-      </div>
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="organization-avatar"
-        >
-          organization.avatar
-        </label>
-        <input
-          disabled={false}
-          id="organization-avatar"
-          maxLength="256"
-          name="avatar"
-          onChange={[Function]}
-          type="text"
-          value=""
-        />
-        <div
-          className="modal-field-description"
-        >
-          organization.avatar.description
-        </div>
-      </div>
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="organization-description"
-        >
-          description
-        </label>
-        <textarea
-          disabled={false}
-          id="organization-description"
-          maxLength="256"
-          name="description"
-          onChange={[Function]}
-          rows="3"
-          value=""
-        />
-        <div
-          className="modal-field-description"
-        >
-          organization.description.description
-        </div>
-      </div>
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="organization-url"
-        >
-          organization.url
-        </label>
-        <input
-          disabled={false}
-          id="organization-url"
-          maxLength="256"
-          name="url"
-          onChange={[Function]}
-          type="text"
-          value=""
-        />
-        <div
-          className="modal-field-description"
-        >
-          organization.url.description
-        </div>
-      </div>
-      <div
-        className="modal-field"
-      >
-        <SubmitButton
-          disabled={false}
-        >
-          save
-        </SubmitButton>
-      </div>
-    </form>
-  </div>
-</div>
-`;
-
-exports[`smoke test 2`] = `
-<div
-  className="page page-limited"
->
-  <HelmetWrapper
-    defer={true}
-    encodeSpecialCharacters={true}
-    title="organization.edit"
-  />
-  <header
-    className="page-header"
-  >
-    <h1
-      className="page-title"
-    >
-      organization.edit
-    </h1>
-  </header>
-  <div
-    className="boxed-group boxed-group-inner"
-  >
-    <form
-      onSubmit={[Function]}
-    >
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="organization-name"
-        >
-          organization.name
-          <em
-            className="mandatory"
-          >
-            *
-          </em>
-        </label>
-        <input
-          disabled={false}
-          id="organization-name"
-          maxLength="64"
-          name="name"
-          onChange={[Function]}
-          required={true}
-          type="text"
-          value="New Foo"
-        />
-        <div
-          className="modal-field-description"
-        >
-          organization.name.description
-        </div>
-      </div>
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="organization-avatar"
-        >
-          organization.avatar
-        </label>
-        <input
-          disabled={false}
-          id="organization-avatar"
-          maxLength="256"
-          name="avatar"
-          onChange={[Function]}
-          type="text"
-          value="foo-avatar"
-        />
-        <div
-          className="modal-field-description"
-        >
-          organization.avatar.description
-        </div>
-        <div
-          className="spacer-top spacer-bottom"
-        >
-          <div
-            className="little-spacer-bottom"
-          >
-            organization.avatar.preview
-            :
-          </div>
-          <img
-            alt=""
-            height={30}
-            src="foo-avatar-image"
-          />
-        </div>
-      </div>
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="organization-description"
-        >
-          description
-        </label>
-        <textarea
-          disabled={false}
-          id="organization-description"
-          maxLength="256"
-          name="description"
-          onChange={[Function]}
-          rows="3"
-          value="foo-description"
-        />
-        <div
-          className="modal-field-description"
-        >
-          organization.description.description
-        </div>
-      </div>
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="organization-url"
-        >
-          organization.url
-        </label>
-        <input
-          disabled={false}
-          id="organization-url"
-          maxLength="256"
-          name="url"
-          onChange={[Function]}
-          type="text"
-          value="foo-url"
-        />
-        <div
-          className="modal-field-description"
-        >
-          organization.url.description
-        </div>
-      </div>
-      <div
-        className="modal-field"
-      >
-        <SubmitButton
-          disabled={false}
-        >
-          save
-        </SubmitButton>
-      </div>
-    </form>
-  </div>
-</div>
-`;
-
-exports[`smoke test 3`] = `
-<div
-  className="page page-limited"
->
-  <HelmetWrapper
-    defer={true}
-    encodeSpecialCharacters={true}
-    title="organization.edit"
-  />
-  <header
-    className="page-header"
-  >
-    <h1
-      className="page-title"
-    >
-      organization.edit
-    </h1>
-  </header>
-  <div
-    className="boxed-group boxed-group-inner"
-  >
-    <form
-      onSubmit={[Function]}
-    >
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="organization-name"
-        >
-          organization.name
-          <em
-            className="mandatory"
-          >
-            *
-          </em>
-        </label>
-        <input
-          disabled={true}
-          id="organization-name"
-          maxLength="64"
-          name="name"
-          onChange={[Function]}
-          required={true}
-          type="text"
-          value="New Foo"
-        />
-        <div
-          className="modal-field-description"
-        >
-          organization.name.description
-        </div>
-      </div>
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="organization-avatar"
-        >
-          organization.avatar
-        </label>
-        <input
-          disabled={true}
-          id="organization-avatar"
-          maxLength="256"
-          name="avatar"
-          onChange={[Function]}
-          type="text"
-          value="foo-avatar"
-        />
-        <div
-          className="modal-field-description"
-        >
-          organization.avatar.description
-        </div>
-        <div
-          className="spacer-top spacer-bottom"
-        >
-          <div
-            className="little-spacer-bottom"
-          >
-            organization.avatar.preview
-            :
-          </div>
-          <img
-            alt=""
-            height={30}
-            src="foo-avatar-image"
-          />
-        </div>
-      </div>
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="organization-description"
-        >
-          description
-        </label>
-        <textarea
-          disabled={true}
-          id="organization-description"
-          maxLength="256"
-          name="description"
-          onChange={[Function]}
-          rows="3"
-          value="foo-description"
-        />
-        <div
-          className="modal-field-description"
-        >
-          organization.description.description
-        </div>
-      </div>
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="organization-url"
-        >
-          organization.url
-        </label>
-        <input
-          disabled={true}
-          id="organization-url"
-          maxLength="256"
-          name="url"
-          onChange={[Function]}
-          type="text"
-          value="foo-url"
-        />
-        <div
-          className="modal-field-description"
-        >
-          organization.url.description
-        </div>
-      </div>
-      <div
-        className="modal-field"
-      >
-        <SubmitButton
-          disabled={true}
-        >
-          save
-        </SubmitButton>
-        <i
-          className="spinner spacer-left"
-        />
-      </div>
-    </form>
-  </div>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.tsx.snap
new file mode 100644 (file)
index 0000000..054e031
--- /dev/null
@@ -0,0 +1,442 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`smoke test 1`] = `
+<div
+  className="page page-limited"
+>
+  <HelmetWrapper
+    defer={true}
+    encodeSpecialCharacters={true}
+    title="organization.edit"
+  />
+  <header
+    className="page-header"
+  >
+    <h1
+      className="page-title"
+    >
+      organization.edit
+    </h1>
+  </header>
+  <div
+    className="boxed-group boxed-group-inner"
+  >
+    <form
+      onSubmit={[Function]}
+    >
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="organization-name"
+        >
+          organization.name
+          <em
+            className="mandatory"
+          >
+            *
+          </em>
+        </label>
+        <input
+          disabled={false}
+          id="organization-name"
+          maxLength={64}
+          name="name"
+          onChange={[Function]}
+          required={true}
+          type="text"
+          value="Foo"
+        />
+        <div
+          className="modal-field-description"
+        >
+          organization.name.description
+        </div>
+      </div>
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="organization-avatar"
+        >
+          organization.avatar
+        </label>
+        <input
+          disabled={false}
+          id="organization-avatar"
+          maxLength={256}
+          name="avatar"
+          onChange={[Function]}
+          type="text"
+          value=""
+        />
+        <div
+          className="modal-field-description"
+        >
+          organization.avatar.description
+        </div>
+      </div>
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="organization-description"
+        >
+          description
+        </label>
+        <textarea
+          disabled={false}
+          id="organization-description"
+          maxLength={256}
+          name="description"
+          onChange={[Function]}
+          rows={3}
+          value=""
+        />
+        <div
+          className="modal-field-description"
+        >
+          organization.description.description
+        </div>
+      </div>
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="organization-url"
+        >
+          organization.url
+        </label>
+        <input
+          disabled={false}
+          id="organization-url"
+          maxLength={256}
+          name="url"
+          onChange={[Function]}
+          type="text"
+          value=""
+        />
+        <div
+          className="modal-field-description"
+        >
+          organization.url.description
+        </div>
+      </div>
+      <div
+        className="modal-field"
+      >
+        <SubmitButton
+          disabled={false}
+        >
+          save
+        </SubmitButton>
+      </div>
+    </form>
+  </div>
+</div>
+`;
+
+exports[`smoke test 2`] = `
+<div
+  className="page page-limited"
+>
+  <HelmetWrapper
+    defer={true}
+    encodeSpecialCharacters={true}
+    title="organization.edit"
+  />
+  <header
+    className="page-header"
+  >
+    <h1
+      className="page-title"
+    >
+      organization.edit
+    </h1>
+  </header>
+  <div
+    className="boxed-group boxed-group-inner"
+  >
+    <form
+      onSubmit={[Function]}
+    >
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="organization-name"
+        >
+          organization.name
+          <em
+            className="mandatory"
+          >
+            *
+          </em>
+        </label>
+        <input
+          disabled={false}
+          id="organization-name"
+          maxLength={64}
+          name="name"
+          onChange={[Function]}
+          required={true}
+          type="text"
+          value="New Foo"
+        />
+        <div
+          className="modal-field-description"
+        >
+          organization.name.description
+        </div>
+      </div>
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="organization-avatar"
+        >
+          organization.avatar
+        </label>
+        <input
+          disabled={false}
+          id="organization-avatar"
+          maxLength={256}
+          name="avatar"
+          onChange={[Function]}
+          type="text"
+          value="foo-avatar"
+        />
+        <div
+          className="modal-field-description"
+        >
+          organization.avatar.description
+        </div>
+        <div
+          className="spacer-top spacer-bottom"
+        >
+          <div
+            className="little-spacer-bottom"
+          >
+            organization.avatar.preview
+            :
+          </div>
+          <img
+            alt=""
+            height={30}
+            src="foo-avatar-image"
+          />
+        </div>
+      </div>
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="organization-description"
+        >
+          description
+        </label>
+        <textarea
+          disabled={false}
+          id="organization-description"
+          maxLength={256}
+          name="description"
+          onChange={[Function]}
+          rows={3}
+          value="foo-description"
+        />
+        <div
+          className="modal-field-description"
+        >
+          organization.description.description
+        </div>
+      </div>
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="organization-url"
+        >
+          organization.url
+        </label>
+        <input
+          disabled={false}
+          id="organization-url"
+          maxLength={256}
+          name="url"
+          onChange={[Function]}
+          type="text"
+          value="foo-url"
+        />
+        <div
+          className="modal-field-description"
+        >
+          organization.url.description
+        </div>
+      </div>
+      <div
+        className="modal-field"
+      >
+        <SubmitButton
+          disabled={false}
+        >
+          save
+        </SubmitButton>
+      </div>
+    </form>
+  </div>
+</div>
+`;
+
+exports[`smoke test 3`] = `
+<div
+  className="page page-limited"
+>
+  <HelmetWrapper
+    defer={true}
+    encodeSpecialCharacters={true}
+    title="organization.edit"
+  />
+  <header
+    className="page-header"
+  >
+    <h1
+      className="page-title"
+    >
+      organization.edit
+    </h1>
+  </header>
+  <div
+    className="boxed-group boxed-group-inner"
+  >
+    <form
+      onSubmit={[Function]}
+    >
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="organization-name"
+        >
+          organization.name
+          <em
+            className="mandatory"
+          >
+            *
+          </em>
+        </label>
+        <input
+          disabled={true}
+          id="organization-name"
+          maxLength={64}
+          name="name"
+          onChange={[Function]}
+          required={true}
+          type="text"
+          value="New Foo"
+        />
+        <div
+          className="modal-field-description"
+        >
+          organization.name.description
+        </div>
+      </div>
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="organization-avatar"
+        >
+          organization.avatar
+        </label>
+        <input
+          disabled={true}
+          id="organization-avatar"
+          maxLength={256}
+          name="avatar"
+          onChange={[Function]}
+          type="text"
+          value="foo-avatar"
+        />
+        <div
+          className="modal-field-description"
+        >
+          organization.avatar.description
+        </div>
+        <div
+          className="spacer-top spacer-bottom"
+        >
+          <div
+            className="little-spacer-bottom"
+          >
+            organization.avatar.preview
+            :
+          </div>
+          <img
+            alt=""
+            height={30}
+            src="foo-avatar-image"
+          />
+        </div>
+      </div>
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="organization-description"
+        >
+          description
+        </label>
+        <textarea
+          disabled={true}
+          id="organization-description"
+          maxLength={256}
+          name="description"
+          onChange={[Function]}
+          rows={3}
+          value="foo-description"
+        />
+        <div
+          className="modal-field-description"
+        >
+          organization.description.description
+        </div>
+      </div>
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="organization-url"
+        >
+          organization.url
+        </label>
+        <input
+          disabled={true}
+          id="organization-url"
+          maxLength={256}
+          name="url"
+          onChange={[Function]}
+          type="text"
+          value="foo-url"
+        />
+        <div
+          className="modal-field-description"
+        >
+          organization.url.description
+        </div>
+      </div>
+      <div
+        className="modal-field"
+      >
+        <SubmitButton
+          disabled={true}
+        >
+          save
+        </SubmitButton>
+        <i
+          className="spinner spacer-left"
+        />
+      </div>
+    </form>
+  </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.js.snap
deleted file mode 100644 (file)
index e5126eb..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should be able to toggle check 1`] = `
-<li
-  className="capitalize list-item-checkable-link"
-  disabled={false}
-  onClick={[Function]}
-  role="listitem"
-  tabIndex={0}
->
-  <Checkbox
-    checked={true}
-    onCheck={[Function]}
-    thirdState={false}
-  />
-   
-  professionals
-</li>
-`;
-
-exports[`should be able to toggle check 2`] = `
-Array [
-  Array [
-    "professionals",
-    false,
-  ],
-]
-`;
-
-exports[`should be able to toggle check 3`] = `
-<li
-  className="capitalize list-item-checkable-link"
-  disabled={false}
-  onClick={[Function]}
-  role="listitem"
-  tabIndex={0}
->
-  <Checkbox
-    checked={false}
-    onCheck={[Function]}
-    thirdState={false}
-  />
-   
-  professionals
-</li>
-`;
-
-exports[`should disabled default groups 1`] = `
-<li
-  className="capitalize list-item-checkable-link"
-  disabled={true}
-  onClick={[Function]}
-  role="listitem"
-  tabIndex={0}
->
-  <Checkbox
-    checked={true}
-    onCheck={[Function]}
-    thirdState={false}
-  />
-   
-  professionals
-</li>
-`;
-
-exports[`should render unchecked 1`] = `
-<li
-  className="capitalize list-item-checkable-link"
-  disabled={false}
-  onClick={[Function]}
-  role="listitem"
-  tabIndex={0}
->
-  <Checkbox
-    checked={false}
-    onCheck={[Function]}
-    thirdState={false}
-  />
-   
-  professionals
-</li>
-`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.tsx.snap
new file mode 100644 (file)
index 0000000..e97cc7b
--- /dev/null
@@ -0,0 +1,70 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should be able to toggle check 1`] = `
+<li
+  className="capitalize list-item-checkable-link"
+  onClick={[Function]}
+>
+  <Checkbox
+    checked={true}
+    onCheck={[Function]}
+    thirdState={false}
+  />
+   
+  professionals
+</li>
+`;
+
+exports[`should be able to toggle check 2`] = `
+Array [
+  Array [
+    "professionals",
+    false,
+  ],
+]
+`;
+
+exports[`should be able to toggle check 3`] = `
+<li
+  className="capitalize list-item-checkable-link"
+  onClick={[Function]}
+>
+  <Checkbox
+    checked={false}
+    onCheck={[Function]}
+    thirdState={false}
+  />
+   
+  professionals
+</li>
+`;
+
+exports[`should disabled default groups 1`] = `
+<li
+  className="capitalize list-item-checkable-link disabled"
+  onClick={[Function]}
+>
+  <Checkbox
+    checked={true}
+    onCheck={[Function]}
+    thirdState={false}
+  />
+   
+  professionals
+</li>
+`;
+
+exports[`should render unchecked 1`] = `
+<li
+  className="capitalize list-item-checkable-link"
+  onClick={[Function]}
+>
+  <Checkbox
+    checked={false}
+    onCheck={[Function]}
+    thirdState={false}
+  />
+   
+  professionals
+</li>
+`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap
deleted file mode 100644 (file)
index 6fb9690..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should not render actions for non admin 1`] = `
-<div
-  className="page page-limited"
->
-  <HelmetWrapper
-    defer={true}
-    encodeSpecialCharacters={true}
-    title="organization.members.page"
-  />
-  <Suggestions
-    suggestions="organization_members"
-  />
-  <MembersPageHeader />
-  <MembersListHeader
-    handleSearch={[Function]}
-    total={2}
-  />
-  <MembersList
-    members={
-      Array [
-        Object {
-          "avatar": "",
-          "groupCount": 3,
-          "login": "admin",
-          "name": "Admin Istrator",
-        },
-        Object {
-          "avatar": "7daf6c79d4802916d83f6266e24850af",
-          "groupCount": 1,
-          "login": "john",
-          "name": "John Doe",
-        },
-      ]
-    }
-    organization={
-      Object {
-        "key": "foo",
-        "name": "Foo",
-      }
-    }
-    removeMember={[Function]}
-    updateMemberGroups={[Function]}
-  />
-  <ListFooter
-    count={2}
-    loadMore={[Function]}
-    ready={true}
-    total={2}
-  />
-</div>
-`;
-
-exports[`should render actions for admin 1`] = `
-<div
-  className="page page-limited"
->
-  <HelmetWrapper
-    defer={true}
-    encodeSpecialCharacters={true}
-    title="organization.members.page"
-  />
-  <Suggestions
-    suggestions="organization_members"
-  />
-  <MembersPageHeader
-    loading={true}
-  >
-    <div
-      className="page-actions"
-    >
-      <AddMemberForm
-        addMember={[Function]}
-        organization={
-          Object {
-            "canAdmin": true,
-            "key": "foo",
-            "name": "Foo",
-          }
-        }
-      />
-      <DocTooltip
-        className="spacer-left"
-        doc="organizations/add-organization-member"
-      />
-    </div>
-  </MembersPageHeader>
-  <MembersListHeader
-    handleSearch={[Function]}
-    total={2}
-  />
-  <MembersList
-    members={
-      Array [
-        Object {
-          "avatar": "",
-          "groupCount": 3,
-          "login": "admin",
-          "name": "Admin Istrator",
-        },
-        Object {
-          "avatar": "7daf6c79d4802916d83f6266e24850af",
-          "groupCount": 1,
-          "login": "john",
-          "name": "John Doe",
-        },
-      ]
-    }
-    organization={
-      Object {
-        "canAdmin": true,
-        "key": "foo",
-        "name": "Foo",
-      }
-    }
-    removeMember={[Function]}
-    updateMemberGroups={[Function]}
-  />
-  <ListFooter
-    count={2}
-    loadMore={[Function]}
-    ready={false}
-    total={2}
-  />
-</div>
-`;
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
new file mode 100644 (file)
index 0000000..778362a
--- /dev/null
@@ -0,0 +1,132 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should not render actions for non admin 1`] = `
+<div
+  className="page page-limited"
+>
+  <HelmetWrapper
+    defer={true}
+    encodeSpecialCharacters={true}
+    title="organization.members.page"
+  />
+  <Suggestions
+    suggestions="organization_members"
+  />
+  <MembersPageHeader
+    loading={false}
+  />
+  <MembersListHeader
+    handleSearch={[Function]}
+    total={2}
+  />
+  <MembersList
+    members={
+      Array [
+        Object {
+          "avatar": "",
+          "groupCount": 3,
+          "login": "admin",
+          "name": "Admin Istrator",
+        },
+        Object {
+          "avatar": "7daf6c79d4802916d83f6266e24850af",
+          "groupCount": 1,
+          "login": "john",
+          "name": "John Doe",
+        },
+      ]
+    }
+    organization={
+      Object {
+        "key": "foo",
+        "name": "Foo",
+      }
+    }
+    organizationGroups={Array []}
+    removeMember={[Function]}
+    updateMemberGroups={[Function]}
+  />
+  <ListFooter
+    count={2}
+    loadMore={[Function]}
+    ready={true}
+    total={2}
+  />
+</div>
+`;
+
+exports[`should render actions for admin 1`] = `
+<div
+  className="page page-limited"
+>
+  <HelmetWrapper
+    defer={true}
+    encodeSpecialCharacters={true}
+    title="organization.members.page"
+  />
+  <Suggestions
+    suggestions="organization_members"
+  />
+  <MembersPageHeader
+    loading={true}
+  >
+    <div
+      className="page-actions"
+    >
+      <AddMemberForm
+        addMember={[Function]}
+        memberLogins={Array []}
+        organization={
+          Object {
+            "canAdmin": true,
+            "key": "foo",
+            "name": "Foo",
+          }
+        }
+      />
+      <DocTooltip
+        className="spacer-left"
+        doc="organizations/add-organization-member"
+      />
+    </div>
+  </MembersPageHeader>
+  <MembersListHeader
+    handleSearch={[Function]}
+    total={2}
+  />
+  <MembersList
+    members={
+      Array [
+        Object {
+          "avatar": "",
+          "groupCount": 3,
+          "login": "admin",
+          "name": "Admin Istrator",
+        },
+        Object {
+          "avatar": "7daf6c79d4802916d83f6266e24850af",
+          "groupCount": 1,
+          "login": "john",
+          "name": "John Doe",
+        },
+      ]
+    }
+    organization={
+      Object {
+        "canAdmin": true,
+        "key": "foo",
+        "name": "Foo",
+      }
+    }
+    organizationGroups={Array []}
+    removeMember={[Function]}
+    updateMemberGroups={[Function]}
+  />
+  <ListFooter
+    count={2}
+    loadMore={[Function]}
+    ready={false}
+    total={2}
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationPage-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationPage-test.js.snap
deleted file mode 100644 (file)
index 260e3f3..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`not found 1`] = `<NotFound />`;
-
-exports[`should correctly update when the organization changes 1`] = `
-Array [
-  Array [
-    "foo",
-  ],
-  Array [
-    "bar",
-  ],
-]
-`;
-
-exports[`smoke test 1`] = `
-<div>
-  <HelmetWrapper
-    defaultTitle="Foo"
-    defer={true}
-    encodeSpecialCharacters={true}
-    titleTemplate="%s - Foo"
-  />
-  <Suggestions
-    suggestions="organization_space"
-  />
-  <OrganizationNavigation
-    organization={
-      Object {
-        "canAdmin": false,
-        "isDefault": false,
-        "key": "foo",
-        "name": "Foo",
-      }
-    }
-  />
-  <div>
-    hello
-  </div>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationPage-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationPage-test.tsx.snap
new file mode 100644 (file)
index 0000000..9802e06
--- /dev/null
@@ -0,0 +1,46 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`not found 1`] = `<NotFound />`;
+
+exports[`should correctly update when the organization changes 1`] = `
+Array [
+  Array [
+    "foo",
+  ],
+  Array [
+    "bar",
+  ],
+]
+`;
+
+exports[`smoke test 1`] = `
+<div>
+  <HelmetWrapper
+    defaultTitle="Foo"
+    defer={true}
+    encodeSpecialCharacters={true}
+    titleTemplate="%s - Foo"
+  />
+  <Suggestions
+    suggestions="organization_space"
+  />
+  <OrganizationNavigation
+    location={
+      Object {
+        "pathname": "foo",
+      }
+    }
+    organization={
+      Object {
+        "canAdmin": false,
+        "isDefault": false,
+        "key": "foo",
+        "name": "Foo",
+      }
+    }
+  />
+  <div>
+    hello
+  </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.js
deleted file mode 100644 (file)
index 1fe1b0d..0000000
+++ /dev/null
@@ -1,124 +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
-import 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 type { Organization } from '../../../../app/types'; */
-/*:: import type { Member } from '../../../../store/organizationsMembers/actions'; */
-
-/*::
-type Props = {
-  addMember: (member: Member) => void,
-  organization: Organization,
-  memberLogins: Array<string>
-};
-*/
-
-/*::
-type State = {
-  open: boolean,
-  selectedMember?: Member
-};
-*/
-
-export default class AddMemberForm extends React.PureComponent {
-  /*:: props: Props; */
-
-  state /*: State */ = {
-    open: false
-  };
-
-  openForm = () => {
-    this.setState({ open: true });
-  };
-
-  closeForm = () => {
-    this.setState({ open: false, selectedMember: undefined });
-  };
-
-  handleSearch = (query /*: ?string */, ps /*: number */) => {
-    const data = { organization: this.props.organization.key, ps, selected: 'deselected' };
-    if (!query) {
-      return searchMembers(data);
-    }
-    return searchMembers({ ...data, q: query });
-  };
-
-  handleSubmit = (e /*: Object */) => {
-    e.preventDefault();
-    if (this.state.selectedMember) {
-      this.props.addMember(this.state.selectedMember);
-      this.closeForm();
-    }
-  };
-
-  selectedMemberChange = (member /*: Member */) => {
-    this.setState({ selectedMember: member });
-  };
-
-  renderModal() {
-    const header = translate('users.add');
-    return (
-      <Modal contentLabel={header} key="add-member-modal" onRequestClose={this.closeForm}>
-        <header className="modal-head">
-          <h2>{header}</h2>
-        </header>
-        <form onSubmit={this.handleSubmit}>
-          <div className="modal-body">
-            <div className="modal-large-field">
-              <label>{translate('users.search_description')}</label>
-              <UsersSelectSearch
-                autoFocus={true}
-                excludedUsers={this.props.memberLogins}
-                handleValueChange={this.selectedMemberChange}
-                searchUsers={this.handleSearch}
-                selectedUser={this.state.selectedMember}
-              />
-            </div>
-          </div>
-          <footer className="modal-foot">
-            <div>
-              <SubmitButton disabled={!this.state.selectedMember}>
-                {translate('organization.members.add_to_members')}
-              </SubmitButton>
-              <ResetButtonLink onClick={this.closeForm}>{translate('cancel')}</ResetButtonLink>
-            </div>
-          </footer>
-        </form>
-      </Modal>
-    );
-  }
-
-  render() {
-    const buttonComponent = (
-      <Button key="add-member-button" onClick={this.openForm}>
-        {translate('organization.members.add')}
-      </Button>
-    );
-    if (this.state.open) {
-      return [buttonComponent, this.renderModal()];
-    }
-    return buttonComponent;
-  }
-}
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
new file mode 100644 (file)
index 0000000..0040807
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * 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<Props, State> {
+  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<HTMLFormElement>) => {
+    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 (
+      <Modal contentLabel={header} key="add-member-modal" onRequestClose={this.closeForm}>
+        <header className="modal-head">
+          <h2>{header}</h2>
+        </header>
+        <form onSubmit={this.handleSubmit}>
+          <div className="modal-body">
+            <div className="modal-large-field">
+              <label>{translate('users.search_description')}</label>
+              <UsersSelectSearch
+                autoFocus={true}
+                excludedUsers={this.props.memberLogins}
+                handleValueChange={this.selectedMemberChange}
+                searchUsers={this.handleSearch}
+                selectedUser={this.state.selectedMember}
+              />
+            </div>
+          </div>
+          <footer className="modal-foot">
+            <div>
+              <SubmitButton disabled={!this.state.selectedMember}>
+                {translate('organization.members.add_to_members')}
+              </SubmitButton>
+              <ResetButtonLink onClick={this.closeForm}>{translate('cancel')}</ResetButtonLink>
+            </div>
+          </footer>
+        </form>
+      </Modal>
+    );
+  }
+
+  render() {
+    const buttonComponent = (
+      <Button key="add-member-button" onClick={this.openForm}>
+        {translate('organization.members.add')}
+      </Button>
+    );
+    if (this.state.open) {
+      return [buttonComponent, this.renderModal()];
+    }
+    return buttonComponent;
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js
deleted file mode 100644 (file)
index 563bf17..0000000
+++ /dev/null
@@ -1,153 +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
-import React from 'react';
-import { keyBy, pickBy } from 'lodash';
-import { getUserGroups } 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 type { Member } from '../../../../store/organizationsMembers/actions'; */
-/*:: import type { Organization, Group } from '../../../../app/types'; */
-
-/*::
-type Props = {
-  onClose: () => void;
-  member: Member,
-  organization: Organization,
-  organizationGroups: Array<Group>,
-  updateMemberGroups: (member: Member, add: Array<string>, remove: Array<string>) => void
-};
-*/
-
-/*::
-type State = {
-  userGroups?: {},
-  loading?: boolean
-};
-*/
-
-export default class ManageMemberGroupsForm extends React.PureComponent {
-  /*:: mounted: boolean */
-  /*:: props: Props; */
-  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 = (e /*: Object */) => {
-    e.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 (
-      <Modal contentLabel={header} onRequestClose={this.props.onClose}>
-        <header className="modal-head">
-          <h2>{header}</h2>
-        </header>
-        <form onSubmit={this.handleSubmit}>
-          <div className="modal-body">
-            <strong>
-              {translateWithParameters(
-                'organization.members.members_groups',
-                this.props.member.name
-              )}
-            </strong>{' '}
-            {this.state.loading && <i className="spinner" />}
-            {!this.state.loading && (
-              <ul className="list-spaced">
-                {this.props.organizationGroups.map(group => (
-                  <OrganizationGroupCheckbox
-                    checked={this.isGroupSelected(group.name)}
-                    group={group}
-                    key={group.id}
-                    onCheck={this.onCheck}
-                  />
-                ))}
-              </ul>
-            )}
-          </div>
-          <footer className="modal-foot">
-            <div>
-              <SubmitButton>{translate('save')}</SubmitButton>
-              <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink>
-            </div>
-          </footer>
-        </form>
-      </Modal>
-    );
-  }
-}
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
new file mode 100644 (file)
index 0000000..bef77e6
--- /dev/null
@@ -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 '../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<Props, State> {
+  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<HTMLFormElement>) => {
+    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 (
+      <Modal contentLabel={header} onRequestClose={this.props.onClose}>
+        <header className="modal-head">
+          <h2>{header}</h2>
+        </header>
+        <form onSubmit={this.handleSubmit}>
+          <div className="modal-body">
+            <strong>
+              {translateWithParameters(
+                'organization.members.members_groups',
+                this.props.member.name
+              )}
+            </strong>{' '}
+            {this.state.loading && <i className="spinner" />}
+            {!this.state.loading && (
+              <ul className="list-spaced">
+                {this.props.organizationGroups.map(group => (
+                  <OrganizationGroupCheckbox
+                    checked={this.isGroupSelected(group.name)}
+                    group={group}
+                    key={group.id}
+                    onCheck={this.onCheck}
+                  />
+                ))}
+              </ul>
+            )}
+          </div>
+          <footer className="modal-foot">
+            <div>
+              <SubmitButton>{translate('save')}</SubmitButton>
+              <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink>
+            </div>
+          </footer>
+        </form>
+      </Modal>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js
deleted file mode 100644 (file)
index b33c53f..0000000
+++ /dev/null
@@ -1,72 +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
-import React from 'react';
-import Modal from '../../../../components/controls/Modal';
-import { translate, translateWithParameters } from '../../../../helpers/l10n';
-import { SubmitButton, ResetButtonLink } from '../../../../components/ui/buttons';
-/*:: import type { Member } from '../../../../store/organizationsMembers/actions'; */
-/*:: import type { Organization } from '../../../../app/types'; */
-
-/*::
-type Props = {
-  onClose: () => void;
-  member: Member,
-  organization: Organization,
-  removeMember: (member: Member) => void
-};
-*/
-
-export default class RemoveMemberForm extends React.PureComponent {
-  /*:: props: Props; */
-  handleSubmit = (e /*: Object */) => {
-    e.preventDefault();
-    this.props.removeMember(this.props.member);
-    this.props.onClose();
-  };
-
-  render() {
-    const header = translate('users.remove');
-    return (
-      <Modal contentLabel={header} key="remove-member-modal" onRequestClose={this.props.onClose}>
-        <header className="modal-head">
-          <h2>{header}</h2>
-        </header>
-        <form onSubmit={this.handleSubmit}>
-          <div className="modal-body">
-            {translateWithParameters(
-              'organization.members.remove_x',
-              this.props.member.name,
-              this.props.organization.name
-            )}
-          </div>
-          <footer className="modal-foot">
-            <div>
-              <SubmitButton autoFocus={true} className="button-red">
-                {translate('remove')}
-              </SubmitButton>
-              <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink>
-            </div>
-          </footer>
-        </form>
-      </Modal>
-    );
-  }
-}
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
new file mode 100644 (file)
index 0000000..c6e0d60
--- /dev/null
@@ -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<Props> {
+  handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
+    event.preventDefault();
+    this.props.removeMember(this.props.member);
+    this.props.onClose();
+  };
+
+  render() {
+    const header = translate('users.remove');
+    return (
+      <Modal contentLabel={header} key="remove-member-modal" onRequestClose={this.props.onClose}>
+        <header className="modal-head">
+          <h2>{header}</h2>
+        </header>
+        <form onSubmit={this.handleSubmit}>
+          <div className="modal-body">
+            {translateWithParameters(
+              'organization.members.remove_x',
+              this.props.member.name,
+              this.props.organization.name
+            )}
+          </div>
+          <footer className="modal-foot">
+            <div>
+              <SubmitButton autoFocus={true} className="button-red">
+                {translate('remove')}
+              </SubmitButton>
+              <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink>
+            </div>
+          </footer>
+        </form>
+      </Modal>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js
deleted file mode 100644 (file)
index 308ff1f..0000000
+++ /dev/null
@@ -1,42 +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 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(<AddMemberForm addMember={jest.fn()} memberLogins={memberLogins} />);
-  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(<AddMemberForm addMember={jest.fn()} memberLogins={memberLogins} />);
-  click(wrapper.find('Button'));
-  expect(wrapper.state('open')).toBeTruthy();
-  wrapper.instance().closeForm();
-  expect(wrapper.state('open')).toBeFalsy();
-});
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
new file mode 100644 (file)
index 0000000..3406cd7
--- /dev/null
@@ -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 { click } from '../../../../../helpers/testUtils';
+import AddMemberForm from '../AddMemberForm';
+
+const memberLogins = ['admin'];
+
+it('should render and open the modal', () => {
+  const wrapper = shallow(
+    <AddMemberForm
+      addMember={jest.fn()}
+      memberLogins={memberLogins}
+      organization={{ key: 'foo', name: 'Foo' }}
+    />
+  );
+  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(
+    <AddMemberForm
+      addMember={jest.fn()}
+      memberLogins={memberLogins}
+      organization={{ key: 'foo', name: 'Foo' }}
+    />
+  );
+  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.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.js
deleted file mode 100644 (file)
index 579a42f..0000000
+++ /dev/null
@@ -1,98 +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 React from 'react';
-import { shallow } from 'enzyme';
-import { click, 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(
-    <ManageMemberGroupsForm
-      member={member}
-      onClose={jest.fn()}
-      organization={organization}
-      organizationGroups={organizationGroups}
-      updateMemberGroups={updateFunc}
-    />,
-    { disableLifecycleMethods: true }
-  );
-  const instance = wrapper.instance();
-  wrapper.setState({ loading: false, userGroups });
-  return { wrapper, instance };
-}
-
-it('should render', () => {
-  const wrapper = shallow(
-    <ManageMemberGroupsForm
-      member={member}
-      organization={organization}
-      organizationGroups={organizationGroups}
-      updateMemberGroups={jest.fn()}
-    />
-  );
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should correctly select the groups', () => {
-  const form = getMountedForm();
-  expect(form.instance.isGroupSelected(11)).toBeTruthy();
-  expect(form.instance.isGroupSelected(7)).toBeFalsy();
-  form.instance.onCheck(11, false);
-  form.instance.onCheck(7, true);
-  expect(form.wrapper.state('userGroups')).toMatchSnapshot();
-  expect(form.instance.isGroupSelected(11)).toBeFalsy();
-  expect(form.instance.isGroupSelected(7)).toBeTruthy();
-});
-
-it('should correctly handle the submit event and close the modal', () => {
-  const updateMemberGroups = jest.fn();
-  const form = getMountedForm(updateMemberGroups);
-  form.instance.onCheck(11, false);
-  form.instance.onCheck(7, true);
-  form.instance.handleSubmit(mockEvent);
-  expect(updateMemberGroups.mock.calls).toMatchSnapshot();
-  expect(form.wrapper.state()).toMatchSnapshot();
-});
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
new file mode 100644 (file)
index 0000000..32b0839
--- /dev/null
@@ -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(
+    <ManageMemberGroupsForm
+      member={member}
+      onClose={jest.fn()}
+      organization={organization}
+      organizationGroups={organizationGroups}
+      updateMemberGroups={updateFunc}
+    />,
+    { disableLifecycleMethods: true }
+  );
+  const instance = wrapper.instance();
+  wrapper.setState({ loading: false, userGroups });
+  return { wrapper, instance };
+}
+
+it('should render', () => {
+  const wrapper = shallow(
+    <ManageMemberGroupsForm
+      member={member}
+      onClose={jest.fn()}
+      organization={organization}
+      organizationGroups={organizationGroups}
+      updateMemberGroups={jest.fn()}
+    />
+  );
+  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.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js
deleted file mode 100644 (file)
index fca12f0..0000000
+++ /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 React from 'react';
-import { shallow } from 'enzyme';
-import { click, mockEvent } from '../../../../../helpers/testUtils';
-import RemoveMemberForm from '../RemoveMemberForm';
-
-const member = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 };
-const organization = { name: 'MyOrg' };
-
-it('should render ', () => {
-  const wrapper = shallow(
-    <RemoveMemberForm member={member} organization={organization} removeMember={jest.fn()} />
-  );
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should correctly handle user interactions', () => {
-  const removeMember = jest.fn();
-  const wrapper = shallow(
-    <RemoveMemberForm
-      member={member}
-      onClose={jest.fn()}
-      organization={organization}
-      removeMember={removeMember}
-    />
-  );
-  wrapper.instance().handleSubmit(mockEvent);
-  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__/RemoveMemberForm-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.tsx
new file mode 100644 (file)
index 0000000..92fbdb9
--- /dev/null
@@ -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(
+    <RemoveMemberForm
+      member={member}
+      onClose={jest.fn()}
+      organization={organization}
+      removeMember={jest.fn()}
+    />
+  );
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('should correctly handle user interactions', () => {
+  const removeMember = jest.fn();
+  const wrapper = shallow(
+    <RemoveMemberForm
+      member={member}
+      onClose={jest.fn()}
+      organization={organization}
+      removeMember={removeMember}
+    />
+  );
+  (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.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.js.snap
deleted file mode 100644 (file)
index d480a39..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render and open the modal 1`] = `
-<Button
-  key="add-member-button"
-  onClick={[Function]}
->
-  organization.members.add
-</Button>
-`;
-
-exports[`should render and open the modal 2`] = `
-Array [
-  <Button
-    onClick={[Function]}
-  >
-    organization.members.add
-  </Button>,
-  <Modal
-    contentLabel="users.add"
-    onRequestClose={[Function]}
-  >
-    <header
-      className="modal-head"
-    >
-      <h2>
-        users.add
-      </h2>
-    </header>
-    <form
-      onSubmit={[Function]}
-    >
-      <div
-        className="modal-body"
-      >
-        <div
-          className="modal-large-field"
-        >
-          <label>
-            users.search_description
-          </label>
-          <UsersSelectSearch
-            autoFocus={true}
-            excludedUsers={
-              Array [
-                "admin",
-              ]
-            }
-            handleValueChange={[Function]}
-            searchUsers={[Function]}
-          />
-        </div>
-      </div>
-      <footer
-        className="modal-foot"
-      >
-        <div>
-          <SubmitButton
-            disabled={true}
-          >
-            organization.members.add_to_members
-          </SubmitButton>
-          <ResetButtonLink
-            onClick={[Function]}
-          >
-            cancel
-          </ResetButtonLink>
-        </div>
-      </footer>
-    </form>
-  </Modal>,
-]
-`;
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
new file mode 100644 (file)
index 0000000..d480a39
--- /dev/null
@@ -0,0 +1,73 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render and open the modal 1`] = `
+<Button
+  key="add-member-button"
+  onClick={[Function]}
+>
+  organization.members.add
+</Button>
+`;
+
+exports[`should render and open the modal 2`] = `
+Array [
+  <Button
+    onClick={[Function]}
+  >
+    organization.members.add
+  </Button>,
+  <Modal
+    contentLabel="users.add"
+    onRequestClose={[Function]}
+  >
+    <header
+      className="modal-head"
+    >
+      <h2>
+        users.add
+      </h2>
+    </header>
+    <form
+      onSubmit={[Function]}
+    >
+      <div
+        className="modal-body"
+      >
+        <div
+          className="modal-large-field"
+        >
+          <label>
+            users.search_description
+          </label>
+          <UsersSelectSearch
+            autoFocus={true}
+            excludedUsers={
+              Array [
+                "admin",
+              ]
+            }
+            handleValueChange={[Function]}
+            searchUsers={[Function]}
+          />
+        </div>
+      </div>
+      <footer
+        className="modal-foot"
+      >
+        <div>
+          <SubmitButton
+            disabled={true}
+          >
+            organization.members.add_to_members
+          </SubmitButton>
+          <ResetButtonLink
+            onClick={[Function]}
+          >
+            cancel
+          </ResetButtonLink>
+        </div>
+      </footer>
+    </form>
+  </Modal>,
+]
+`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap
deleted file mode 100644 (file)
index 43f0bce..0000000
+++ /dev/null
@@ -1,94 +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`] = `
-<Modal
-  contentLabel="organization.members.manage_groups"
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      organization.members.manage_groups
-    </h2>
-  </header>
-  <form
-    onSubmit={[Function]}
-  >
-    <div
-      className="modal-body"
-    >
-      <strong>
-        organization.members.members_groups.Admin Istrator
-      </strong>
-       
-      <i
-        className="spinner"
-      />
-    </div>
-    <footer
-      className="modal-foot"
-    >
-      <div>
-        <SubmitButton>
-          save
-        </SubmitButton>
-        <ResetButtonLink>
-          cancel
-        </ResetButtonLink>
-      </div>
-    </footer>
-  </form>
-</Modal>
-`;
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
new file mode 100644 (file)
index 0000000..63f4e47
--- /dev/null
@@ -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`] = `
+<Modal
+  contentLabel="organization.members.manage_groups"
+  onRequestClose={[MockFunction]}
+>
+  <header
+    className="modal-head"
+  >
+    <h2>
+      organization.members.manage_groups
+    </h2>
+  </header>
+  <form
+    onSubmit={[Function]}
+  >
+    <div
+      className="modal-body"
+    >
+      <strong>
+        organization.members.members_groups.Admin Istrator
+      </strong>
+       
+      <i
+        className="spinner"
+      />
+    </div>
+    <footer
+      className="modal-foot"
+    >
+      <div>
+        <SubmitButton>
+          save
+        </SubmitButton>
+        <ResetButtonLink
+          onClick={[MockFunction]}
+        >
+          cancel
+        </ResetButtonLink>
+      </div>
+    </footer>
+  </form>
+</Modal>
+`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.js.snap
deleted file mode 100644 (file)
index ef1c3bf..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render  1`] = `
-<Modal
-  contentLabel="users.remove"
-  key="remove-member-modal"
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      users.remove
-    </h2>
-  </header>
-  <form
-    onSubmit={[Function]}
-  >
-    <div
-      className="modal-body"
-    >
-      organization.members.remove_x.Admin Istrator.MyOrg
-    </div>
-    <footer
-      className="modal-foot"
-    >
-      <div>
-        <SubmitButton
-          autoFocus={true}
-          className="button-red"
-        >
-          remove
-        </SubmitButton>
-        <ResetButtonLink>
-          cancel
-        </ResetButtonLink>
-      </div>
-    </footer>
-  </form>
-</Modal>
-`;
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
new file mode 100644 (file)
index 0000000..c4a8afe
--- /dev/null
@@ -0,0 +1,43 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render  1`] = `
+<Modal
+  contentLabel="users.remove"
+  key="remove-member-modal"
+  onRequestClose={[MockFunction]}
+>
+  <header
+    className="modal-head"
+  >
+    <h2>
+      users.remove
+    </h2>
+  </header>
+  <form
+    onSubmit={[Function]}
+  >
+    <div
+      className="modal-body"
+    >
+      organization.members.remove_x.Admin Istrator.MyOrg
+    </div>
+    <footer
+      className="modal-foot"
+    >
+      <div>
+        <SubmitButton
+          autoFocus={true}
+          className="button-red"
+        >
+          remove
+        </SubmitButton>
+        <ResetButtonLink
+          onClick={[MockFunction]}
+        >
+          cancel
+        </ResetButtonLink>
+      </div>
+    </footer>
+  </form>
+</Modal>
+`;
index 3ee87fc0f36ae894361dc0e158690029e6ad683f..112d2ef75cef6d2258f64f389f48004cdf1a2701 100644 (file)
@@ -20,7 +20,7 @@
 import * as React from 'react';
 import OrganizationNavigationHeaderContainer from './OrganizationNavigationHeaderContainer';
 import OrganizationNavigationMeta from './OrganizationNavigationMeta';
-import OrganizationNavigationMenu from './OrganizationNavigationMenu';
+import OrganizationNavigationMenuContainer from './OrganizationNavigationMenuContainer';
 import * as theme from '../../../app/theme';
 import ContextNavBar from '../../../components/nav/ContextNavBar';
 import { Organization } from '../../../app/types';
@@ -37,7 +37,7 @@ export default function OrganizationNavigation({ location, organization }: Props
         <OrganizationNavigationHeaderContainer organization={organization} />
         <OrganizationNavigationMeta organization={organization} />
       </div>
-      <OrganizationNavigationMenu location={location} organization={organization} />
+      <OrganizationNavigationMenuContainer location={location} organization={organization} />
     </ContextNavBar>
   );
 }
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenu.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenu.tsx
deleted file mode 100644 (file)
index e63d8b4..0000000
+++ /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 * as React from 'react';
-import { Link } from 'react-router';
-import OrganizationNavigationExtensions from './OrganizationNavigationExtensions';
-import OrganizationNavigationAdministration from './OrganizationNavigationAdministration';
-import { Organization } from '../../../app/types';
-import NavBarTabs from '../../../components/nav/NavBarTabs';
-import { translate } from '../../../helpers/l10n';
-import { getQualityGatesUrl } from '../../../helpers/urls';
-import { hasPrivateAccess, isCurrentUserMemberOf } from '../../../helpers/organizations';
-
-interface Props {
-  location: { pathname: string };
-  organization: Organization;
-}
-
-export default function OrganizationNavigationMenu({ location, organization }: Props) {
-  return (
-    <NavBarTabs className="navbar-context-tabs">
-      <li>
-        <Link activeClassName="active" to={`/organizations/${organization.key}/projects`}>
-          {translate('projects.page')}
-        </Link>
-      </li>
-      <li>
-        <Link
-          activeClassName="active"
-          to={{
-            pathname: `/organizations/${organization.key}/issues`,
-            query: { resolved: 'false' }
-          }}>
-          {translate('issues.page')}
-        </Link>
-      </li>
-      {hasPrivateAccess(organization) && (
-        <>
-          <li>
-            <Link
-              activeClassName="active"
-              to={`/organizations/${organization.key}/quality_profiles`}>
-              {translate('quality_profiles.page')}
-            </Link>
-          </li>
-          <li>
-            <Link activeClassName="active" to={`/organizations/${organization.key}/rules`}>
-              {translate('coding_rules.page')}
-            </Link>
-          </li>
-          <li>
-            <Link activeClassName="active" to={getQualityGatesUrl(organization.key)}>
-              {translate('quality_gates.page')}
-            </Link>
-          </li>
-        </>
-      )}
-
-      {isCurrentUserMemberOf(organization) && (
-        <li>
-          <Link activeClassName="active" to={`/organizations/${organization.key}/members`}>
-            {translate('organization.members.page')}
-          </Link>
-        </li>
-      )}
-
-      <OrganizationNavigationExtensions location={location} organization={organization} />
-      {organization.canAdmin && (
-        <OrganizationNavigationAdministration location={location} organization={organization} />
-      )}
-    </NavBarTabs>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenuContainer.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenuContainer.tsx
new file mode 100644 (file)
index 0000000..1fda201
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * 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 { connect } from 'react-redux';
+import { Link } from 'react-router';
+import OrganizationNavigationExtensions from './OrganizationNavigationExtensions';
+import OrganizationNavigationAdministration from './OrganizationNavigationAdministration';
+import { Organization, CurrentUser } from '../../../app/types';
+import NavBarTabs from '../../../components/nav/NavBarTabs';
+import { translate } from '../../../helpers/l10n';
+import { getQualityGatesUrl } from '../../../helpers/urls';
+import { hasPrivateAccess, isCurrentUserMemberOf } from '../../../helpers/organizations';
+import { getCurrentUser, getMyOrganizations } from '../../../store/rootReducer';
+
+interface StateToProps {
+  currentUser: CurrentUser;
+  userOrganizations: Organization[];
+}
+
+interface OwnProps {
+  location: { pathname: string };
+  organization: Organization;
+}
+
+type Props = OwnProps & StateToProps;
+
+export function OrganizationNavigationMenu({
+  currentUser,
+  location,
+  organization,
+  userOrganizations
+}: Props) {
+  return (
+    <NavBarTabs className="navbar-context-tabs">
+      <li>
+        <Link activeClassName="active" to={`/organizations/${organization.key}/projects`}>
+          {translate('projects.page')}
+        </Link>
+      </li>
+      <li>
+        <Link
+          activeClassName="active"
+          to={{
+            pathname: `/organizations/${organization.key}/issues`,
+            query: { resolved: 'false' }
+          }}>
+          {translate('issues.page')}
+        </Link>
+      </li>
+      {hasPrivateAccess(currentUser, organization, userOrganizations) && (
+        <>
+          <li>
+            <Link
+              activeClassName="active"
+              to={`/organizations/${organization.key}/quality_profiles`}>
+              {translate('quality_profiles.page')}
+            </Link>
+          </li>
+          <li>
+            <Link activeClassName="active" to={`/organizations/${organization.key}/rules`}>
+              {translate('coding_rules.page')}
+            </Link>
+          </li>
+          <li>
+            <Link activeClassName="active" to={getQualityGatesUrl(organization.key)}>
+              {translate('quality_gates.page')}
+            </Link>
+          </li>
+        </>
+      )}
+
+      {isCurrentUserMemberOf(currentUser, organization, userOrganizations) && (
+        <li>
+          <Link activeClassName="active" to={`/organizations/${organization.key}/members`}>
+            {translate('organization.members.page')}
+          </Link>
+        </li>
+      )}
+
+      <OrganizationNavigationExtensions location={location} organization={organization} />
+      {organization.canAdmin && (
+        <OrganizationNavigationAdministration location={location} organization={organization} />
+      )}
+    </NavBarTabs>
+  );
+}
+
+const mapStateToProps = (state: any) => ({
+  currentUser: getCurrentUser(state),
+  userOrganizations: getMyOrganizations(state)
+});
+
+export default connect<StateToProps, {}, OwnProps>(mapStateToProps)(OrganizationNavigationMenu);
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMenu-test.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMenu-test.tsx
deleted file mode 100644 (file)
index 251e415..0000000
+++ /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 OrganizationNavigationMenu from '../OrganizationNavigationMenu';
-import { Visibility } from '../../../../app/types';
-import { isCurrentUserMemberOf, hasPrivateAccess } from '../../../../helpers/organizations';
-
-jest.mock('../../../../helpers/organizations', () => ({
-  isCurrentUserMemberOf: jest.fn().mockReturnValue(true),
-  hasPrivateAccess: jest.fn().mockReturnValue(true)
-}));
-
-const organization = {
-  key: 'foo',
-  name: 'Foo',
-  projectVisibility: Visibility.Public
-};
-
-beforeEach(() => {
-  (isCurrentUserMemberOf as jest.Mock<any>).mockClear();
-  (hasPrivateAccess as jest.Mock<any>).mockClear();
-});
-
-it('renders', () => {
-  expect(
-    shallow(<OrganizationNavigationMenu location={{ pathname: '' }} organization={organization} />)
-  ).toMatchSnapshot();
-});
-
-it('renders for admin', () => {
-  expect(
-    shallow(
-      <OrganizationNavigationMenu
-        location={{ pathname: '' }}
-        organization={{ ...organization, canAdmin: true }}
-      />
-    )
-  ).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMenuContainer-test.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMenuContainer-test.tsx
new file mode 100644 (file)
index 0000000..ee37622
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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 { OrganizationNavigationMenu } from '../OrganizationNavigationMenuContainer';
+import { Visibility } from '../../../../app/types';
+import { isCurrentUserMemberOf, hasPrivateAccess } from '../../../../helpers/organizations';
+
+jest.mock('../../../../helpers/organizations', () => ({
+  isCurrentUserMemberOf: jest.fn().mockReturnValue(true),
+  hasPrivateAccess: jest.fn().mockReturnValue(true)
+}));
+
+const organization = {
+  key: 'foo',
+  name: 'Foo',
+  projectVisibility: Visibility.Public
+};
+
+const loggedInUser = {
+  isLoggedIn: true,
+  login: 'luke',
+  name: 'Skywalker',
+  showOnboardingTutorial: false
+};
+
+beforeEach(() => {
+  (isCurrentUserMemberOf as jest.Mock<any>).mockClear();
+  (hasPrivateAccess as jest.Mock<any>).mockClear();
+});
+
+it('renders', () => {
+  expect(
+    shallow(
+      <OrganizationNavigationMenu
+        currentUser={loggedInUser}
+        location={{ pathname: '' }}
+        organization={organization}
+        userOrganizations={[organization]}
+      />
+    )
+  ).toMatchSnapshot();
+});
+
+it('renders for admin', () => {
+  expect(
+    shallow(
+      <OrganizationNavigationMenu
+        currentUser={loggedInUser}
+        location={{ pathname: '' }}
+        organization={{ ...organization, canAdmin: true }}
+        userOrganizations={[organization]}
+      />
+    )
+  ).toMatchSnapshot();
+});
index 820e5dc86119a09bb66fd8d44592b2d9c9e1e165..240404ba19f0ee207981d384fa177f84452c9c91 100644 (file)
@@ -27,7 +27,7 @@ exports[`render 1`] = `
       }
     />
   </div>
-  <OrganizationNavigationMenu
+  <Connect(OrganizationNavigationMenu)
     location={
       Object {
         "pathname": "/organizations/foo",
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenu-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenu-test.tsx.snap
deleted file mode 100644 (file)
index a51bf8c..0000000
+++ /dev/null
@@ -1,205 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders 1`] = `
-<NavBarTabs
-  className="navbar-context-tabs"
->
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/organizations/foo/projects"
-    >
-      projects.page
-    </Link>
-  </li>
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/organizations/foo/issues",
-          "query": Object {
-            "resolved": "false",
-          },
-        }
-      }
-    >
-      issues.page
-    </Link>
-  </li>
-  <React.Fragment>
-    <li>
-      <Link
-        activeClassName="active"
-        onlyActiveOnIndex={false}
-        style={Object {}}
-        to="/organizations/foo/quality_profiles"
-      >
-        quality_profiles.page
-      </Link>
-    </li>
-    <li>
-      <Link
-        activeClassName="active"
-        onlyActiveOnIndex={false}
-        style={Object {}}
-        to="/organizations/foo/rules"
-      >
-        coding_rules.page
-      </Link>
-    </li>
-    <li>
-      <Link
-        activeClassName="active"
-        onlyActiveOnIndex={false}
-        style={Object {}}
-        to={
-          Object {
-            "pathname": "/organizations/foo/quality_gates",
-          }
-        }
-      >
-        quality_gates.page
-      </Link>
-    </li>
-  </React.Fragment>
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/organizations/foo/members"
-    >
-      organization.members.page
-    </Link>
-  </li>
-  <OrganizationNavigationExtensions
-    location={
-      Object {
-        "pathname": "",
-      }
-    }
-    organization={
-      Object {
-        "key": "foo",
-        "name": "Foo",
-        "projectVisibility": "public",
-      }
-    }
-  />
-</NavBarTabs>
-`;
-
-exports[`renders for admin 1`] = `
-<NavBarTabs
-  className="navbar-context-tabs"
->
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/organizations/foo/projects"
-    >
-      projects.page
-    </Link>
-  </li>
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/organizations/foo/issues",
-          "query": Object {
-            "resolved": "false",
-          },
-        }
-      }
-    >
-      issues.page
-    </Link>
-  </li>
-  <React.Fragment>
-    <li>
-      <Link
-        activeClassName="active"
-        onlyActiveOnIndex={false}
-        style={Object {}}
-        to="/organizations/foo/quality_profiles"
-      >
-        quality_profiles.page
-      </Link>
-    </li>
-    <li>
-      <Link
-        activeClassName="active"
-        onlyActiveOnIndex={false}
-        style={Object {}}
-        to="/organizations/foo/rules"
-      >
-        coding_rules.page
-      </Link>
-    </li>
-    <li>
-      <Link
-        activeClassName="active"
-        onlyActiveOnIndex={false}
-        style={Object {}}
-        to={
-          Object {
-            "pathname": "/organizations/foo/quality_gates",
-          }
-        }
-      >
-        quality_gates.page
-      </Link>
-    </li>
-  </React.Fragment>
-  <li>
-    <Link
-      activeClassName="active"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/organizations/foo/members"
-    >
-      organization.members.page
-    </Link>
-  </li>
-  <OrganizationNavigationExtensions
-    location={
-      Object {
-        "pathname": "",
-      }
-    }
-    organization={
-      Object {
-        "canAdmin": true,
-        "key": "foo",
-        "name": "Foo",
-        "projectVisibility": "public",
-      }
-    }
-  />
-  <OrganizationNavigationAdministration
-    location={
-      Object {
-        "pathname": "",
-      }
-    }
-    organization={
-      Object {
-        "canAdmin": true,
-        "key": "foo",
-        "name": "Foo",
-        "projectVisibility": "public",
-      }
-    }
-  />
-</NavBarTabs>
-`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenuContainer-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenuContainer-test.tsx.snap
new file mode 100644 (file)
index 0000000..a51bf8c
--- /dev/null
@@ -0,0 +1,205 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<NavBarTabs
+  className="navbar-context-tabs"
+>
+  <li>
+    <Link
+      activeClassName="active"
+      onlyActiveOnIndex={false}
+      style={Object {}}
+      to="/organizations/foo/projects"
+    >
+      projects.page
+    </Link>
+  </li>
+  <li>
+    <Link
+      activeClassName="active"
+      onlyActiveOnIndex={false}
+      style={Object {}}
+      to={
+        Object {
+          "pathname": "/organizations/foo/issues",
+          "query": Object {
+            "resolved": "false",
+          },
+        }
+      }
+    >
+      issues.page
+    </Link>
+  </li>
+  <React.Fragment>
+    <li>
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to="/organizations/foo/quality_profiles"
+      >
+        quality_profiles.page
+      </Link>
+    </li>
+    <li>
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to="/organizations/foo/rules"
+      >
+        coding_rules.page
+      </Link>
+    </li>
+    <li>
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/organizations/foo/quality_gates",
+          }
+        }
+      >
+        quality_gates.page
+      </Link>
+    </li>
+  </React.Fragment>
+  <li>
+    <Link
+      activeClassName="active"
+      onlyActiveOnIndex={false}
+      style={Object {}}
+      to="/organizations/foo/members"
+    >
+      organization.members.page
+    </Link>
+  </li>
+  <OrganizationNavigationExtensions
+    location={
+      Object {
+        "pathname": "",
+      }
+    }
+    organization={
+      Object {
+        "key": "foo",
+        "name": "Foo",
+        "projectVisibility": "public",
+      }
+    }
+  />
+</NavBarTabs>
+`;
+
+exports[`renders for admin 1`] = `
+<NavBarTabs
+  className="navbar-context-tabs"
+>
+  <li>
+    <Link
+      activeClassName="active"
+      onlyActiveOnIndex={false}
+      style={Object {}}
+      to="/organizations/foo/projects"
+    >
+      projects.page
+    </Link>
+  </li>
+  <li>
+    <Link
+      activeClassName="active"
+      onlyActiveOnIndex={false}
+      style={Object {}}
+      to={
+        Object {
+          "pathname": "/organizations/foo/issues",
+          "query": Object {
+            "resolved": "false",
+          },
+        }
+      }
+    >
+      issues.page
+    </Link>
+  </li>
+  <React.Fragment>
+    <li>
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to="/organizations/foo/quality_profiles"
+      >
+        quality_profiles.page
+      </Link>
+    </li>
+    <li>
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to="/organizations/foo/rules"
+      >
+        coding_rules.page
+      </Link>
+    </li>
+    <li>
+      <Link
+        activeClassName="active"
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to={
+          Object {
+            "pathname": "/organizations/foo/quality_gates",
+          }
+        }
+      >
+        quality_gates.page
+      </Link>
+    </li>
+  </React.Fragment>
+  <li>
+    <Link
+      activeClassName="active"
+      onlyActiveOnIndex={false}
+      style={Object {}}
+      to="/organizations/foo/members"
+    >
+      organization.members.page
+    </Link>
+  </li>
+  <OrganizationNavigationExtensions
+    location={
+      Object {
+        "pathname": "",
+      }
+    }
+    organization={
+      Object {
+        "canAdmin": true,
+        "key": "foo",
+        "name": "Foo",
+        "projectVisibility": "public",
+      }
+    }
+  />
+  <OrganizationNavigationAdministration
+    location={
+      Object {
+        "pathname": "",
+      }
+    }
+    organization={
+      Object {
+        "canAdmin": true,
+        "key": "foo",
+        "name": "Foo",
+        "projectVisibility": "public",
+      }
+    }
+  />
+</NavBarTabs>
+`;
index c015b983357235d1514f69f50f1e37f720184b09..cbb5d82aa9b95dbdebe99504ebda9db8e5aefa47 100644 (file)
@@ -26,7 +26,7 @@ import ProjectsList from './ProjectsList';
 import PageSidebar from './PageSidebar';
 import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import Visualizations from '../visualizations/Visualizations';
-import { CurrentUser, isLoggedIn } from '../../../app/types';
+import { CurrentUser, isLoggedIn, Organization } from '../../../app/types';
 import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication';
 import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
 import ListFooter from '../../../components/controls/ListFooter';
@@ -44,7 +44,7 @@ export interface Props {
   currentUser: CurrentUser;
   isFavorite: boolean;
   location: { pathname: string; query: RawQuery };
-  organization?: { key: string };
+  organization: Organization | undefined;
   organizationsEnabled: boolean;
   storageOptionsSuffix?: string;
 }
index 5de80726a26ce2d4b524b0064e085037e4b36350..cf7600a82b63b60ecef1dead732cbb2dbe4ab548 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { connect } from 'react-redux';
-import { CurrentUser } from '../../../app/types';
+import { CurrentUser, Organization } from '../../../app/types';
 import { lazyLoad } from '../../../components/lazyLoad';
 import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer';
 import { RawQuery } from '../../../helpers/query';
@@ -31,7 +31,7 @@ interface StateProps {
 interface OwnProps {
   isFavorite: boolean;
   location: { pathname: string; query: RawQuery };
-  organization?: { key: string };
+  organization: Organization | undefined;
   storageOptionsSuffix?: string;
 }
 
index 1fffa7f7d3eeb86660c0142f9021d8a0c78eca35..8b4e37b1ec628e40e64ccebdca03dc17b61ddf38 100644 (file)
@@ -114,7 +114,13 @@ export default class DefaultPageSelector extends React.PureComponent<Props, Stat
 
   render() {
     if (isSonarCloud() && isLoggedIn(this.props.currentUser)) {
-      return <AllProjectsContainer isFavorite={true} location={this.props.location} />;
+      return (
+        <AllProjectsContainer
+          isFavorite={true}
+          location={this.props.location}
+          organization={undefined}
+        />
+      );
     }
 
     const { shouldBeRedirected, shouldForceSorting } = this.state;
@@ -124,7 +130,13 @@ export default class DefaultPageSelector extends React.PureComponent<Props, Stat
       shouldBeRedirected !== true &&
       shouldForceSorting === undefined
     ) {
-      return <AllProjectsContainer isFavorite={false} location={this.props.location} />;
+      return (
+        <AllProjectsContainer
+          isFavorite={false}
+          location={this.props.location}
+          organization={undefined}
+        />
+      );
     }
 
     return null;
index d572edb0d5b2c61fabc28844dbc75409c7d2b264..e562aac058e3fcc68267744b88e687881b931786 100644 (file)
@@ -21,10 +21,11 @@ import * as React from 'react';
 import ProjectCardLeak from './ProjectCardLeak';
 import ProjectCardOverall from './ProjectCardOverall';
 import { Project } from '../types';
+import { Organization } from '../../../app/types';
 
 interface Props {
   height: number;
-  organization?: { key: string };
+  organization: Organization | undefined;
   project: Project;
   type?: string;
 }
index a019fca6cbf69c063c7c1fdbedb759eed3376c8e..23cf0206096836d168abf6854e2cae264f2ea79a 100644 (file)
@@ -28,12 +28,13 @@ import EmptyFavoriteSearch from './EmptyFavoriteSearch';
 import EmptySearch from '../../../components/common/EmptySearch';
 import { Project } from '../types';
 import { Query } from '../query';
+import { Organization } from '../../../app/types';
 
 interface Props {
   cardType?: string;
   isFavorite: boolean;
   isFiltered: boolean;
-  organization?: { key: string };
+  organization: Organization | undefined;
   projects: Project[];
   query: Query;
 }
index 7497a286fa95ea1b57aaf39bffd5be66cedb95d9..babf7c999e01c83bb6d14370c11b3c23bb03b5c6 100644 (file)
@@ -171,6 +171,7 @@ function shallowRender(
       currentUser={{ isLoggedIn: true }}
       isFavorite={false}
       location={{ pathname: '/projects', query: {} }}
+      organization={undefined}
       organizationsEnabled={false}
       {...props}
     />,
index a7dcb6ea2bc1a496fb7aaa960fcbeff10dcb28a5..fd28e6c24815b2f510488d4b5a529eaa78154fee 100644 (file)
@@ -37,6 +37,7 @@ function shallowRender(props?: any) {
       cardType="overall"
       isFavorite={false}
       isFiltered={false}
+      organization={undefined}
       projects={[{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }]}
       {...props}
     />
index 419ef569f9f49e7ddfbd2daffc7be58c1da0c31b..fe010d4231609f004e3ee79fa03787854f6a182e 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+/* eslint-disable camelcase */
 import { uniq } from 'lodash';
 import { Query, convertToFilter } from './query';
 import { translate } from '../../helpers/l10n';
index 1a0e5500129feb9172976d37b185b88e22f9b98a..5244898544d7e8647c2f735f6d5b572c75c1b5e3 100644 (file)
@@ -70,11 +70,14 @@ export default class NewOrganizationForm extends React.PureComponent<Props, Stat
   };
 
   validateOrganization = (organization: string) => {
-    getOrganization(organization).then(response => {
-      if (this.mounted) {
-        this.setState({ unique: response == null });
-      }
-    });
+    getOrganization(organization).then(
+      response => {
+        if (this.mounted) {
+          this.setState({ unique: response == null });
+        }
+      },
+      () => {}
+    );
   };
 
   sanitizeOrganization = (organization: string) =>
diff --git a/server/sonar-web/src/main/js/store/organizations/__tests__/__snapshots__/duck-test.js.snap b/server/sonar-web/src/main/js/store/organizations/__tests__/__snapshots__/duck-test.js.snap
deleted file mode 100644 (file)
index c5ad9b7..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Reducer should have initial state 1`] = `
-Object {
-  "byKey": Object {},
-  "groups": Object {},
-  "my": Array [],
-}
-`;
-
-exports[`Reducer should receive organizations 1`] = `
-Object {
-  "byKey": Object {
-    "bar": Object {
-      "key": "bar",
-      "name": "Bar",
-    },
-    "foo": Object {
-      "key": "foo",
-      "name": "Foo",
-    },
-  },
-  "groups": Object {},
-  "my": Array [],
-}
-`;
-
-exports[`Reducer should receive organizations 2`] = `
-Object {
-  "byKey": Object {
-    "bar": Object {
-      "key": "bar",
-      "name": "Bar",
-    },
-    "foo": Object {
-      "key": "foo",
-      "name": "Qwe",
-    },
-  },
-  "groups": Object {},
-  "my": Array [],
-}
-`;
diff --git a/server/sonar-web/src/main/js/store/organizations/__tests__/__snapshots__/duck-test.ts.snap b/server/sonar-web/src/main/js/store/organizations/__tests__/__snapshots__/duck-test.ts.snap
new file mode 100644 (file)
index 0000000..c5ad9b7
--- /dev/null
@@ -0,0 +1,43 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Reducer should have initial state 1`] = `
+Object {
+  "byKey": Object {},
+  "groups": Object {},
+  "my": Array [],
+}
+`;
+
+exports[`Reducer should receive organizations 1`] = `
+Object {
+  "byKey": Object {
+    "bar": Object {
+      "key": "bar",
+      "name": "Bar",
+    },
+    "foo": Object {
+      "key": "foo",
+      "name": "Foo",
+    },
+  },
+  "groups": Object {},
+  "my": Array [],
+}
+`;
+
+exports[`Reducer should receive organizations 2`] = `
+Object {
+  "byKey": Object {
+    "bar": Object {
+      "key": "bar",
+      "name": "Bar",
+    },
+    "foo": Object {
+      "key": "foo",
+      "name": "Qwe",
+    },
+  },
+  "groups": Object {},
+  "my": Array [],
+}
+`;
diff --git a/server/sonar-web/src/main/js/store/organizations/__tests__/duck-test.js b/server/sonar-web/src/main/js/store/organizations/__tests__/duck-test.js
deleted file mode 100644 (file)
index 2180d6a..0000000
+++ /dev/null
@@ -1,61 +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 organizations, { getOrganizationByKey, areThereCustomOrganizations } from '../duck';
-
-describe('Reducer', () => {
-  it('should have initial state', () => {
-    expect(organizations(undefined, {})).toMatchSnapshot();
-  });
-
-  it('should receive organizations', () => {
-    const state0 = { byKey: {} };
-
-    const action1 = {
-      type: 'RECEIVE_ORGANIZATIONS',
-      organizations: [{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }]
-    };
-    const state1 = organizations(state0, action1);
-    expect(state1).toMatchSnapshot();
-
-    const action2 = {
-      type: 'RECEIVE_ORGANIZATIONS',
-      organizations: [{ key: 'foo', name: 'Qwe' }]
-    };
-    const state2 = organizations(state1, action2);
-    expect(state2).toMatchSnapshot();
-  });
-});
-
-describe('Selectors', () => {
-  it('getOrganizationByKey', () => {
-    const foo = { key: 'foo', name: 'Foo' };
-    const state = { byKey: { foo } };
-    expect(getOrganizationByKey(state, 'foo')).toBe(foo);
-    expect(getOrganizationByKey(state, 'bar')).toBeFalsy();
-  });
-
-  it('areThereCustomOrganizations', () => {
-    const foo = { key: 'foo', name: 'Foo' };
-    const bar = { key: 'bar', name: 'Bar' };
-    expect(areThereCustomOrganizations({ byKey: {} }, 'foo')).toBe(false);
-    expect(areThereCustomOrganizations({ byKey: { foo } }, 'foo')).toBe(false);
-    expect(areThereCustomOrganizations({ byKey: { foo, bar } }, 'foo')).toBe(true);
-  });
-});
diff --git a/server/sonar-web/src/main/js/store/organizations/__tests__/duck-test.ts b/server/sonar-web/src/main/js/store/organizations/__tests__/duck-test.ts
new file mode 100644 (file)
index 0000000..e9321e2
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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 organizations, { getOrganizationByKey, areThereCustomOrganizations } from '../duck';
+
+const state0 = { byKey: {}, my: [], groups: {} };
+
+describe('Reducer', () => {
+  it('should have initial state', () => {
+    expect((organizations as any)(undefined, {})).toMatchSnapshot();
+  });
+
+  it('should receive organizations', () => {
+    const action1 = {
+      type: 'RECEIVE_ORGANIZATIONS',
+      organizations: [{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }]
+    };
+    const state1 = organizations(state0, action1);
+    expect(state1).toMatchSnapshot();
+
+    const action2 = {
+      type: 'RECEIVE_ORGANIZATIONS',
+      organizations: [{ key: 'foo', name: 'Qwe' }]
+    };
+    const state2 = organizations(state1, action2);
+    expect(state2).toMatchSnapshot();
+  });
+});
+
+describe('Selectors', () => {
+  it('getOrganizationByKey', () => {
+    const foo = { key: 'foo', name: 'Foo' };
+    const state = { ...state0, byKey: { foo } };
+    expect(getOrganizationByKey(state, 'foo')).toBe(foo);
+    expect(getOrganizationByKey(state, 'bar')).toBeFalsy();
+  });
+
+  it('areThereCustomOrganizations', () => {
+    const foo = { key: 'foo', name: 'Foo' };
+    const bar = { key: 'bar', name: 'Bar' };
+    expect(areThereCustomOrganizations({ ...state0, byKey: {} })).toBe(false);
+    expect(areThereCustomOrganizations({ ...state0, byKey: { foo } })).toBe(false);
+    expect(areThereCustomOrganizations({ ...state0, byKey: { foo, bar } })).toBe(true);
+  });
+});
index 8b6c76e39933fbb01898eabf3809070d60c666d8..5f01ac9526163e28d181814c49d04b559375014c 100644 (file)
@@ -19,7 +19,7 @@
  */
 import { combineReducers } from 'redux';
 import { omit, uniq, without } from 'lodash';
-import { Group, Organization } from '../../app/types';
+import { Group, Organization, OrganizationBase } from '../../app/types';
 
 interface ReceiveOrganizationsAction {
   type: 'RECEIVE_ORGANIZATIONS';
@@ -108,7 +108,10 @@ export function createOrganization(organization: Organization): CreateOrganizati
   };
 }
 
-export function updateOrganization(key: string, changes: {}): UpdateOrganizationAction {
+export function updateOrganization(
+  key: string,
+  changes: OrganizationBase
+): UpdateOrganizationAction {
   return {
     type: 'UPDATE_ORGANIZATION',
     key,
@@ -177,7 +180,7 @@ function groups(state: GroupsState = {}, action: Action) {
   return state;
 }
 
-export default combineReducers({ byKey, my, groups });
+export default combineReducers<State>({ byKey, my, groups });
 
 export function getOrganizationByKey(state: State, key: string): Organization | undefined {
   return state.byKey[key];
diff --git a/server/sonar-web/src/main/js/store/organizations/utils.js b/server/sonar-web/src/main/js/store/organizations/utils.js
deleted file mode 100644 (file)
index adf5ed2..0000000
+++ /dev/null
@@ -1,37 +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
-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/organizations/utils.ts b/server/sonar-web/src/main/js/store/organizations/utils.ts
new file mode 100644 (file)
index 0000000..5b3957e
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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);
+}