diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2018-07-06 11:40:29 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-07-11 20:21:22 +0200 |
commit | 29b0f28f9e4fc05ce662212a55aef45ad0278201 (patch) | |
tree | e167021989b32129e7f7695ff1fc5d43b797a05f /server | |
parent | d34f9f46391d73cb89e9b7ead8e828dc65362829 (diff) | |
download | sonarqube-29b0f28f9e4fc05ce662212a55aef45ad0278201.tar.gz sonarqube-29b0f28f9e4fc05ce662212a55aef45ad0278201.zip |
Migrate remaining of organization app to TS
Diffstat (limited to 'server')
58 files changed, 621 insertions, 478 deletions
diff --git a/server/sonar-web/src/main/js/api/organizations.ts b/server/sonar-web/src/main/js/api/organizations.ts index 95918a609b3..ffa4cb24fb2 100644 --- a/server/sonar-web/src/main/js/api/organizations.ts +++ b/server/sonar-web/src/main/js/api/organizations.ts @@ -17,24 +17,25 @@ * 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.tsx index f93fc31fba3..30699762fc9 100644 --- a/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.js +++ b/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.tsx @@ -17,37 +17,45 @@ * 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 * 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 type { Organization } from '../../../app/types'; */ +import { Organization } from '../../../app/types'; -/*:: -type Props = { - fetchOrganization: string => void, - location: {}, - organization: Organization, +interface StateToProps { + organization?: Organization; +} + +interface DispatchProps { + fetchOrganization: (organizationKey: string) => void; +} + +interface OwnProps { + location: {}; params: { - extensionKey: string, - organizationKey: string, - pluginKey: string - } -}; -*/ + extensionKey: string; + organizationKey: string; + pluginKey: string; + }; +} -class OrganizationPageExtension extends React.PureComponent { - /*:: props: Props; */ +type Props = OwnProps & StateToProps & DispatchProps; - refreshOrganization = () => this.props.fetchOrganization(this.props.organization.key); +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); @@ -66,10 +74,12 @@ class OrganizationPageExtension extends React.PureComponent { } } -const mapStateToProps = (state, ownProps /*: Props */) => ({ +const mapStateToProps = (state: any, ownProps: OwnProps) => ({ organization: getOrganizationByKey(state, ownProps.params.organizationKey) }); const mapDispatchToProps = { fetchOrganization }; -export default connect(mapStateToProps, mapDispatchToProps)(OrganizationPageExtension); +export default connect<StateToProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)( + OrganizationPageExtension +); diff --git a/server/sonar-web/src/main/js/app/styles/init/lists.css b/server/sonar-web/src/main/js/app/styles/init/lists.css index 3ebd22f84e8..f8cc8a5489e 100644 --- a/server/sonar-web/src/main/js/app/styles/init/lists.css +++ b/server/sonar-web/src/main/js/app/styles/init/lists.css @@ -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); } diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 6d359757c55..bddfe372c4a 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -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 { diff --git a/server/sonar-web/src/main/js/apps/account/organizations/CreateOrganizationForm.tsx b/server/sonar-web/src/main/js/apps/account/organizations/CreateOrganizationForm.tsx index acc43e6de4c..9416f11a790 100644 --- a/server/sonar-web/src/main/js/apps/account/organizations/CreateOrganizationForm.tsx +++ b/server/sonar-web/src/main/js/apps/account/organizations/CreateOrganizationForm.tsx @@ -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 { diff --git a/server/sonar-web/src/main/js/apps/explore/ExploreProjects.tsx b/server/sonar-web/src/main/js/apps/explore/ExploreProjects.tsx index ff1789ee68c..ec8376ede5f 100644 --- a/server/sonar-web/src/main/js/apps/explore/ExploreProjects.tsx +++ b/server/sonar-web/src/main/js/apps/explore/ExploreProjects.tsx @@ -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.ts index 2de045b6e89..7f5d8897a97 100644 --- a/server/sonar-web/src/main/js/apps/organizations/actions.js +++ b/server/sonar-web/src/main/js/apps/organizations/actions.ts @@ -17,7 +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. */ -// @flow +import { Dispatch } from 'redux'; import * as api from '../../api/organizations'; import * as actions from '../../store/organizations/duck'; import * as membersActions from '../../store/organizationsMembers/actions'; @@ -27,149 +27,132 @@ 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'; */ +import { Organization, OrganizationMember, OrganizationBase } from '../../app/types'; const PAGE_SIZE = 50; -const onRejected = (dispatch /*: Function */) => (error /*: Object */) => { +const onRejected = (dispatch: Dispatch<any>) => (error: any) => { onFail(dispatch)(error); - return Promise.reject(); + return Promise.reject(error); }; -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])); - } - }; - +export const fetchOrganization = (key: string) => (dispatch: Dispatch<any>) => { return Promise.all([api.getOrganization(key), api.getOrganizationNavigation(key)]).then( - onFulfilled, + ([organization, navigation]) => { + if (organization) { + const organizationWithPermissions = { ...organization, ...navigation }; + dispatch(actions.receiveOrganizations([organizationWithPermissions])); + } + }, onFail(dispatch) ); }; -export const fetchOrganizationGroups = (organization /*: string */) => ( - dispatch /*: Function */ -) => { +export const fetchOrganizationGroups = (organization: string) => (dispatch: Dispatch<any>) => { return searchUsersGroups({ organization }).then(response => { dispatch(actions.receiveOrganizationGroups(organization, response.groups)); }, onFail(dispatch)); }; -export const createOrganization = (fields /*: Object */) => (dispatch /*: Function */) => { - const onFulfilled = (organization /*: Organization */) => { +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; - }; - - return api.createOrganization(fields).then(onFulfilled, onRejected(dispatch)); + }, onRejected(dispatch)); }; -export const updateOrganization = (key /*: string */, changes /*: {} */) => ( - dispatch /*: Function */ +export const updateOrganization = (key: string, changes: OrganizationBase) => ( + dispatch: Dispatch<any> ) => { - const onFulfilled = () => { + return api.updateOrganization(key, changes).then(() => { dispatch(actions.updateOrganization(key, changes)); dispatch(addGlobalSuccessMessage(translate('organization.updated'))); - }; - - return api.updateOrganization(key, changes).then(onFulfilled, onFail(dispatch)); + }, onFail(dispatch)); }; -export const deleteOrganization = (key /*: string */) => (dispatch /*: Function */) => { - const onFulfilled = () => { +export const deleteOrganization = (key: string) => (dispatch: Dispatch<any>) => { + return api.deleteOrganization(key).then(() => { dispatch(actions.deleteOrganization(key)); dispatch(addGlobalSuccessMessage(translate('organization.deleted'))); - }; - - return api.deleteOrganization(key).then(onFulfilled, onFail(dispatch)); + }, onFail(dispatch)); }; const fetchMembers = ( - dispatch /*: Function */, - receiveAction /*: Function */, - key /*: string */, - query /*: ?string */, - page /*: ?number */ + data: { + organization: string; + p?: number; + ps?: number; + q?: string; + }, + dispatch: Dispatch<any>, + receiveAction: Function ) => { - dispatch(membersActions.updateState(key, { loading: true })); - const data /*: Object */ = { - organization: key, - ps: PAGE_SIZE - }; - if (page != null) { - data.p = page; + dispatch(membersActions.updateState(data.organization, { loading: true })); + if (data.ps === undefined) { + data.ps = PAGE_SIZE; } - if (query) { - data.q = query; + if (!data.q) { + data.q = undefined; } - 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)); + 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 /*: Function */ -) => fetchMembers(dispatch, membersActions.receiveMembers, key, query); +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 /*: Function */, - getState /*: Function */ +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, - key, - query, - getOrganizationMembersState(getState(), key).pageIndex + 1 + membersActions.receiveMoreMembers ); -export const addOrganizationMember = (key /*: string */, member /*: Member */) => ( - dispatch /*: Function */ +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 /*: Member */) => ( - dispatch /*: Function */ +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 /*: Object */ - ) => { + 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 /*: Member */, - add /*: Array<string> */, - remove /*: Array<string> */ -) => (dispatch /*: Function */) => { + organization: Organization, + member: OrganizationMember, + add: string[], + remove: string[] +) => (dispatch: Dispatch<any>) => { dispatch( receiveUser({ ...member, 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.tsx index 4ed74e4169e..641486606b5 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/MembersList.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/MembersList.tsx @@ -17,25 +17,23 @@ * 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 * as React from 'react'; import MembersListItem from './MembersListItem'; -/*:: import type { Member } from '../../../store/organizationsMembers/actions'; */ -/*:: import type { Organization, Group } from '../../../app/types'; */ +import { Group, Organization, OrganizationMember } 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; */ +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"> @@ -45,8 +43,8 @@ export default class MembersList extends React.PureComponent { <MembersListItem key={member.login} member={member} - organizationGroups={this.props.organizationGroups} organization={this.props.organization} + organizationGroups={this.props.organizationGroups} removeMember={this.props.removeMember} updateMemberGroups={this.props.updateMemberGroups} /> 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.tsx index ed3106ad23a..b703131b34a 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.tsx @@ -17,20 +17,17 @@ * 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 * as 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 -}; -*/ +interface Props { + handleSearch: (query?: string) => void; + total?: number; +} -export default function MembersListHeader({ handleSearch, total } /*: Props */) { +export default function MembersListHeader({ handleSearch, total }: Props) { return ( <div className="panel panel-vertical bordered-bottom spacer-bottom"> <SearchBox 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.tsx index b17f5ee3991..99d71d9a4fa 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.tsx @@ -17,8 +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. */ -//@flow -import React from 'react'; +import * as React from 'react'; import RemoveMemberForm from './forms/RemoveMemberForm'; import ManageMemberGroupsForm from './forms/ManageMemberGroupsForm'; import Avatar from '../../../components/ui/Avatar'; @@ -28,30 +27,26 @@ import ActionsDropdown, { ActionsDropdownDivider, ActionsDropdownItem } from '../../../components/controls/ActionsDropdown'; -/*:: import type { Member } from '../../../store/organizationsMembers/actions'; */ -/*:: import type { Organization, Group } from '../../../app/types'; */ +import { Group, Organization, OrganizationMember } 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 -}; +interface Props { + member: OrganizationMember; + organization: Organization; + organizationGroups: Group[]; + removeMember: (member: OrganizationMember) => void; + updateMemberGroups: (member: OrganizationMember, add: string[], remove: string[]) => void; +} -type State = { - removeMemberForm: bool, - manageGroupsForm: bool +interface State { + removeMemberForm: boolean; + manageGroupsForm: boolean; } -*/ -const AVATAR_SIZE /*: number */ = 36; +const AVATAR_SIZE = 36; -export default class MembersListItem extends React.PureComponent { - mounted /*: bool */ = false; - /*:: props: Props; */ - state /*: State */ = { removeMemberForm: false, manageGroupsForm: false }; +export default class MembersListItem extends React.PureComponent<Props, State> { + mounted = false; + state: State = { removeMemberForm: false, manageGroupsForm: false }; componentDidMount() { this.mounted = true; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationAccessContainer.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationAccessContainer.tsx index aab0246bd05..2b5e9f301d4 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationAccessContainer.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationAccessContainer.tsx @@ -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.tsx index b78290609b8..af96e5ec02f 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.tsx @@ -17,43 +17,41 @@ * 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 * 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 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 - }; -*/ +import { Organization, OrganizationBase } from '../../../app/types'; + +interface DispatchProps { + updateOrganization: (organization: string, changes: OrganizationBase) => Promise<any>; +} - constructor(props /*: Props */) { +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 || '', @@ -71,18 +69,18 @@ class OrganizationEdit extends React.PureComponent { this.mounted = false; } - handleAvatarInputChange = (e /*: Object */) => { - const { value } = e.target; + handleAvatarInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { + const { value } = event.target; this.setState({ avatar: value }); this.changeAvatarImage(value); }; - changeAvatarImage = (value /*: string */) => { + changeAvatarImage = (value: string) => { this.setState({ avatarImage: value }); }; - handleSubmit = (e /*: Object */) => { - e.preventDefault(); + handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { + event.preventDefault(); const changes = { avatar: this.state.avatar, description: this.state.description, @@ -90,11 +88,15 @@ class OrganizationEdit extends React.PureComponent { url: this.state.url }; this.setState({ loading: true }); - this.props.updateOrganization(this.props.organization.key, changes).then(() => { - if (this.mounted) { - this.setState({ loading: false }); - } - }); + this.props + .updateOrganization(this.props.organization.key, changes) + .then(this.stopLoading, this.stopLoading); + }; + + stopLoading = () => { + if (this.mounted) { + this.setState({ loading: false }); + } }; render() { @@ -117,7 +119,7 @@ class OrganizationEdit extends React.PureComponent { <input disabled={this.state.loading} id="organization-name" - maxLength="64" + maxLength={64} name="name" onChange={e => this.setState({ name: e.target.value })} required={true} @@ -133,7 +135,7 @@ class OrganizationEdit extends React.PureComponent { <input disabled={this.state.loading} id="organization-avatar" - maxLength="256" + maxLength={256} name="avatar" onChange={this.handleAvatarInputChange} type="text" @@ -157,10 +159,10 @@ class OrganizationEdit extends React.PureComponent { <textarea disabled={this.state.loading} id="organization-description" - maxLength="256" + maxLength={256} name="description" onChange={e => this.setState({ description: e.target.value })} - rows="3" + rows={3} value={this.state.description} /> <div className="modal-field-description"> @@ -172,7 +174,7 @@ class OrganizationEdit extends React.PureComponent { <input disabled={this.state.loading} id="organization-url" - maxLength="256" + maxLength={256} name="url" onChange={e => this.setState({ url: e.target.value })} type="text" @@ -193,8 +195,6 @@ class OrganizationEdit extends React.PureComponent { } } -const mapDispatchToProps = { updateOrganization }; - -export default connect(null, mapDispatchToProps)(OrganizationEdit); +const mapDispatchToProps = { updateOrganization: updateOrganization as any }; -export const UnconnectedOrganizationEdit = OrganizationEdit; +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.tsx index 95fd1e085e9..a1209ecbb9c 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.tsx @@ -17,23 +17,19 @@ * 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 * as React from 'react'; +import * as classNames from 'classnames'; import Checkbox from '../../../components/controls/Checkbox'; -/*:: import type { Group } from '../../../app/types'; */ +import { Group } from '../../../app/types'; -/*:: -type Props = { - group: Group, - checked: boolean, - onCheck: (string, boolean) => void -}; -*/ - -export default class OrganizationGroupCheckbox extends React.PureComponent { - /*:: props: Props; */ +interface Props { + group: Group; + checked: boolean; + onCheck: (name: string, checked: boolean) => void; +} - onCheck = (checked /*: boolean */) => { +export default class OrganizationGroupCheckbox extends React.PureComponent<Props> { + onCheck = (checked: boolean) => { const { group } = this.props; if (!group.default) { this.props.onCheck(group.name, checked); @@ -48,11 +44,8 @@ export default class OrganizationGroupCheckbox extends React.PureComponent { const { group } = this.props; return ( <li - className="capitalize list-item-checkable-link" - onClick={this.toggleCheck} - tabIndex={0} - role="listitem" - disabled={group.default}> + 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.tsx index fdafac5ca40..68096c99764 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.tsx @@ -17,8 +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. */ -// @flow -import React from 'react'; +import * as React from 'react'; import Helmet from 'react-helmet'; import MembersPageHeader from './MembersPageHeader'; import MembersListHeader from './MembersListHeader'; @@ -28,33 +27,28 @@ 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'; */ +import { Group, Organization, OrganizationMember } from '../../../app/types'; -/*:: -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, +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: Member, - add: Array<string>, - remove: Array<string> - ) => void -}; -*/ - -export default class OrganizationMembers extends React.PureComponent { - /*:: props: Props; */ + member: OrganizationMember, + add: string[], + remove: string[] + ) => void; +} +export default class OrganizationMembers extends React.PureComponent<Props> { componentDidMount() { this.handleSearchMembers(); if (this.props.organization.canAdmin) { @@ -62,7 +56,7 @@ export default class OrganizationMembers extends React.PureComponent { } } - handleSearchMembers = (query /*: string | void */) => { + handleSearchMembers = (query?: string) => { this.props.fetchOrganizationMembers(this.props.organization.key, query); }; @@ -70,19 +64,15 @@ export default class OrganizationMembers extends React.PureComponent { this.props.fetchMoreOrganizationMembers(this.props.organization.key, this.props.status.query); }; - addMember = (member /*: Member */) => { + addMember = (member: OrganizationMember) => { this.props.addOrganizationMember(this.props.organization.key, member); }; - removeMember = (member /*: Member */) => { + removeMember = (member: OrganizationMember) => { this.props.removeOrganizationMember(this.props.organization.key, member); }; - updateMemberGroups = ( - member /*: Member */, - add /*: Array<string> */, - remove /*: Array<string> */ - ) => { + updateMemberGroups = (member: OrganizationMember, add: string[], remove: string[]) => { this.props.updateOrganizationMemberGroups(this.props.organization, member, add, remove); }; @@ -92,7 +82,7 @@ export default class OrganizationMembers extends React.PureComponent { <div className="page page-limited"> <Helmet title={translate('organization.members.page')} /> <Suggestions suggestions="organization_members" /> - <MembersPageHeader loading={status.loading}> + <MembersPageHeader loading={Boolean(status.loading)}> {organization.canAdmin && ( <div className="page-actions"> <AddMemberForm 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.tsx index 203757e0919..cbc1d9d0d98 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembersContainer.tsx @@ -34,8 +34,35 @@ import { removeOrganizationMember, updateOrganizationMemberGroups } from '../actions'; +import { Organization, OrganizationMember, Group } from '../../../app/types'; -const mapStateToProps = (state, ownProps) => { +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 { @@ -47,11 +74,15 @@ const mapStateToProps = (state, ownProps) => { }; }; -export default connect(mapStateToProps, { - fetchOrganizationMembers, +const mapDispatchToProps = { + addOrganizationMember, fetchMoreOrganizationMembers, fetchOrganizationGroups, - addOrganizationMember, + fetchOrganizationMembers, removeOrganizationMember, updateOrganizationMemberGroups -})(OrganizationMembers); +}; + +export default connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)( + OrganizationMembers +); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx index 09ae20f8151..ce54a3eea92 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx @@ -20,10 +20,11 @@ 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.tsx index 6c527e19c86..0910f62995c 100644 --- 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.tsx @@ -17,7 +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. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import MembersList from '../MembersList'; @@ -28,6 +28,14 @@ const members = [ ]; it('should render a list of members of an organization', () => { - const wrapper = shallow(<MembersList organization={organization} members={members} />); + 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.tsx index a4c3be43fd8..ffc4241d145 100644 --- 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.tsx @@ -17,7 +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. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import MembersListHeader from '../MembersListHeader'; 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.tsx index 9e2455dbcdd..9838194849f 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.tsx @@ -17,7 +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. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import MembersListItem from '../MembersListItem'; @@ -26,20 +26,40 @@ const admin = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 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} />); + 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 organization={{ ...organization, canAdmin: true }} member={admin} /> + <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 organization={{ ...organization, canAdmin: true }} member={john} /> + <MembersListItem + member={john} + organization={{ ...organization, canAdmin: true }} + organizationGroups={[]} + removeMember={jest.fn()} + updateMemberGroups={jest.fn()} + /> ); expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationAccessContainer-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationAccessContainer-test.tsx index 17b1fd80bb1..8fcb64c11f3 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationAccessContainer-test.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationAccessContainer-test.tsx @@ -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.tsx index cd3c2b0a71c..fb4efafe103 100644 --- 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.tsx @@ -17,13 +17,15 @@ * 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 * as React from 'react'; import { shallow } from 'enzyme'; -import { UnconnectedOrganizationEdit } from '../OrganizationEdit'; +import { OrganizationEdit } from '../OrganizationEdit'; it('smoke test', () => { const organization = { key: 'foo', name: 'Foo' }; - const wrapper = shallow(<UnconnectedOrganizationEdit organization={organization} />); + const wrapper = shallow( + <OrganizationEdit organization={organization} updateOrganization={jest.fn()} /> + ); expect(wrapper).toMatchSnapshot(); wrapper.setState({ 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.tsx index a092526f549..2cc4d069333 100644 --- 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.tsx @@ -17,12 +17,12 @@ * 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 * as React from 'react'; import { shallow } from 'enzyme'; import OrganizationGroupCheckbox from '../OrganizationGroupCheckbox'; const group = { - id: '7', + id: 7, name: 'professionals', description: '', membersCount: 12, @@ -31,32 +31,32 @@ const group = { it('should render unchecked', () => { const wrapper = shallow( - <OrganizationGroupCheckbox group={group} checked={false} onCheck={jest.fn()} /> + <OrganizationGroupCheckbox checked={false} group={group} onCheck={jest.fn()} /> ); expect(wrapper).toMatchSnapshot(); }); it('should be able to toggle check', () => { - const onCheck = jest.fn((group, checked) => wrapper.setProps({ checked })); + const onCheck = jest.fn().mockImplementation((_group, checked) => wrapper.setProps({ checked })); const wrapper = shallow( - <OrganizationGroupCheckbox group={group} checked={true} onCheck={onCheck} /> + <OrganizationGroupCheckbox checked={true} group={group} onCheck={onCheck} /> ); expect(wrapper).toMatchSnapshot(); - wrapper.instance().toggleCheck(); + (wrapper.instance() as OrganizationGroupCheckbox).toggleCheck(); expect(onCheck.mock.calls).toMatchSnapshot(); expect(wrapper).toMatchSnapshot(); }); it('should disabled default groups', () => { - const onCheck = jest.fn((group, checked) => wrapper.setProps({ checked })); + const onCheck = jest.fn().mockImplementation((_group, checked) => wrapper.setProps({ checked })); const wrapper = shallow( <OrganizationGroupCheckbox - group={{ ...group, default: true }} checked={true} + group={{ ...group, default: true }} onCheck={onCheck} /> ); expect(wrapper).toMatchSnapshot(); - wrapper.instance().toggleCheck(); + (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.tsx index f7cd21913a0..de18ce76e3b 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.tsx @@ -17,7 +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. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import OrganizationMembers from '../OrganizationMembers'; @@ -31,12 +31,17 @@ const status = { total: members.length }; it('should not render actions for non admin', () => { const wrapper = shallow( <OrganizationMembers - organization={organization} + addOrganizationMember={jest.fn()} + fetchMoreOrganizationMembers={jest.fn()} + fetchOrganizationGroups={jest.fn()} + fetchOrganizationMembers={jest.fn()} + memberLogins={[]} members={members} + organization={organization} + organizationGroups={[]} + removeOrganizationMember={jest.fn()} status={status} - fetchOrganizationMembers={jest.fn()} - fetchOrganizationGroups={jest.fn()} - fetchMoreOrganizationMembers={jest.fn()} + updateOrganizationMemberGroups={jest.fn()} /> ); expect(wrapper).toMatchSnapshot(); @@ -45,12 +50,17 @@ it('should not render actions for non admin', () => { it('should render actions for admin', () => { const wrapper = shallow( <OrganizationMembers - organization={{ ...organization, canAdmin: true }} + 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 }} - fetchOrganizationMembers={jest.fn()} - fetchOrganizationGroups={jest.fn()} - fetchMoreOrganizationMembers={jest.fn()} + 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.tsx index ad76461e499..e1230f3a18c 100644 --- 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.tsx @@ -17,7 +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. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import { OrganizationPage } from '../OrganizationPage'; @@ -25,7 +25,10 @@ const fetchOrganization = () => Promise.resolve(); it('smoke test', () => { const wrapper = shallow( - <OrganizationPage fetchOrganization={fetchOrganization} params={{ organizationKey: 'foo' }}> + <OrganizationPage + fetchOrganization={fetchOrganization} + location={{ pathname: 'foo' }} + params={{ organizationKey: 'foo' }}> <div>hello</div> </OrganizationPage> ); @@ -38,7 +41,10 @@ it('smoke test', () => { it('not found', () => { const wrapper = shallow( - <OrganizationPage fetchOrganization={fetchOrganization} params={{ organizationKey: 'foo' }}> + <OrganizationPage + fetchOrganization={fetchOrganization} + location={{ pathname: 'foo' }} + params={{ organizationKey: 'foo' }}> <div>hello</div> </OrganizationPage> ); @@ -49,7 +55,10 @@ it('not found', () => { it('should correctly update when the organization changes', () => { const fetchOrganization = jest.fn(() => Promise.resolve()); const wrapper = shallow( - <OrganizationPage params={{ organizationKey: 'foo' }} fetchOrganization={fetchOrganization}> + <OrganizationPage + fetchOrganization={fetchOrganization} + location={{ pathname: 'foo' }} + params={{ organizationKey: 'foo' }}> <div>hello</div> </OrganizationPage> ); 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.tsx.snap index fc8f227d95e..57ff0c52ea6 100644 --- 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.tsx.snap @@ -24,6 +24,9 @@ exports[`should render a list of members of an organization 1`] = ` "name": "Foo", } } + organizationGroups={Array []} + removeMember={[MockFunction]} + updateMemberGroups={[MockFunction]} /> <MembersListItem key="john" @@ -41,6 +44,9 @@ exports[`should render a list of members of an organization 1`] = ` "name": "Foo", } } + organizationGroups={Array []} + removeMember={[MockFunction]} + updateMemberGroups={[MockFunction]} /> </tbody> </table> 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.tsx.snap index 7bfe2725e60..7bfe2725e60 100644 --- 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.tsx.snap 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.tsx.snap index 2f04b608477..2f04b608477 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.tsx.snap 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.tsx.snap index 0abba69f78e..054e0314163 100644 --- 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.tsx.snap @@ -40,7 +40,7 @@ exports[`smoke test 1`] = ` <input disabled={false} id="organization-name" - maxLength="64" + maxLength={64} name="name" onChange={[Function]} required={true} @@ -64,7 +64,7 @@ exports[`smoke test 1`] = ` <input disabled={false} id="organization-avatar" - maxLength="256" + maxLength={256} name="avatar" onChange={[Function]} type="text" @@ -87,10 +87,10 @@ exports[`smoke test 1`] = ` <textarea disabled={false} id="organization-description" - maxLength="256" + maxLength={256} name="description" onChange={[Function]} - rows="3" + rows={3} value="" /> <div @@ -110,7 +110,7 @@ exports[`smoke test 1`] = ` <input disabled={false} id="organization-url" - maxLength="256" + maxLength={256} name="url" onChange={[Function]} type="text" @@ -176,7 +176,7 @@ exports[`smoke test 2`] = ` <input disabled={false} id="organization-name" - maxLength="64" + maxLength={64} name="name" onChange={[Function]} required={true} @@ -200,7 +200,7 @@ exports[`smoke test 2`] = ` <input disabled={false} id="organization-avatar" - maxLength="256" + maxLength={256} name="avatar" onChange={[Function]} type="text" @@ -238,10 +238,10 @@ exports[`smoke test 2`] = ` <textarea disabled={false} id="organization-description" - maxLength="256" + maxLength={256} name="description" onChange={[Function]} - rows="3" + rows={3} value="foo-description" /> <div @@ -261,7 +261,7 @@ exports[`smoke test 2`] = ` <input disabled={false} id="organization-url" - maxLength="256" + maxLength={256} name="url" onChange={[Function]} type="text" @@ -327,7 +327,7 @@ exports[`smoke test 3`] = ` <input disabled={true} id="organization-name" - maxLength="64" + maxLength={64} name="name" onChange={[Function]} required={true} @@ -351,7 +351,7 @@ exports[`smoke test 3`] = ` <input disabled={true} id="organization-avatar" - maxLength="256" + maxLength={256} name="avatar" onChange={[Function]} type="text" @@ -389,10 +389,10 @@ exports[`smoke test 3`] = ` <textarea disabled={true} id="organization-description" - maxLength="256" + maxLength={256} name="description" onChange={[Function]} - rows="3" + rows={3} value="foo-description" /> <div @@ -412,7 +412,7 @@ exports[`smoke test 3`] = ` <input disabled={true} id="organization-url" - maxLength="256" + maxLength={256} name="url" onChange={[Function]} type="text" 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.tsx.snap index e5126eb7157..e97cc7b40c0 100644 --- 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.tsx.snap @@ -3,10 +3,7 @@ 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} @@ -30,10 +27,7 @@ Array [ 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} @@ -47,11 +41,8 @@ exports[`should be able to toggle check 3`] = ` exports[`should disabled default groups 1`] = ` <li - className="capitalize list-item-checkable-link" - disabled={true} + className="capitalize list-item-checkable-link disabled" onClick={[Function]} - role="listitem" - tabIndex={0} > <Checkbox checked={true} @@ -66,10 +57,7 @@ exports[`should disabled default groups 1`] = ` exports[`should render unchecked 1`] = ` <li className="capitalize list-item-checkable-link" - disabled={false} onClick={[Function]} - role="listitem" - tabIndex={0} > <Checkbox checked={false} 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.tsx.snap index 6fb9690cf4b..778362a0ff1 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap @@ -12,7 +12,9 @@ exports[`should not render actions for non admin 1`] = ` <Suggestions suggestions="organization_members" /> - <MembersPageHeader /> + <MembersPageHeader + loading={false} + /> <MembersListHeader handleSearch={[Function]} total={2} @@ -40,6 +42,7 @@ exports[`should not render actions for non admin 1`] = ` "name": "Foo", } } + organizationGroups={Array []} removeMember={[Function]} updateMemberGroups={[Function]} /> @@ -72,6 +75,7 @@ exports[`should render actions for admin 1`] = ` > <AddMemberForm addMember={[Function]} + memberLogins={Array []} organization={ Object { "canAdmin": true, @@ -114,6 +118,7 @@ exports[`should render actions for admin 1`] = ` "name": "Foo", } } + organizationGroups={Array []} removeMember={[Function]} updateMemberGroups={[Function]} /> 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.tsx.snap index 260e3f3ffb6..9802e06cdcb 100644 --- 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.tsx.snap @@ -25,6 +25,11 @@ exports[`smoke test 1`] = ` suggestions="organization_space" /> <OrganizationNavigation + location={ + Object { + "pathname": "foo", + } + } organization={ Object { "canAdmin": false, 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.tsx index 1fe1b0d7d6a..00408077132 100644 --- 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.tsx @@ -17,35 +17,27 @@ * 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 * 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 type { Organization } from '../../../../app/types'; */ -/*:: import type { Member } from '../../../../store/organizationsMembers/actions'; */ +import { Organization, OrganizationMember } from '../../../../app/types'; -/*:: -type Props = { - addMember: (member: Member) => void, - organization: Organization, - memberLogins: Array<string> -}; -*/ - -/*:: -type State = { - open: boolean, - selectedMember?: Member -}; -*/ +interface Props { + addMember: (member: OrganizationMember) => void; + organization: Organization; + memberLogins: string[]; +} -export default class AddMemberForm extends React.PureComponent { - /*:: props: Props; */ +interface State { + open: boolean; + selectedMember?: OrganizationMember; +} - state /*: State */ = { +export default class AddMemberForm extends React.PureComponent<Props, State> { + state: State = { open: false }; @@ -57,7 +49,7 @@ export default class AddMemberForm extends React.PureComponent { this.setState({ open: false, selectedMember: undefined }); }; - handleSearch = (query /*: ?string */, ps /*: number */) => { + handleSearch = (query: string | undefined, ps: number) => { const data = { organization: this.props.organization.key, ps, selected: 'deselected' }; if (!query) { return searchMembers(data); @@ -65,15 +57,15 @@ export default class AddMemberForm extends React.PureComponent { return searchMembers({ ...data, q: query }); }; - handleSubmit = (e /*: Object */) => { - e.preventDefault(); + handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { + event.preventDefault(); if (this.state.selectedMember) { this.props.addMember(this.state.selectedMember); this.closeForm(); } }; - selectedMemberChange = (member /*: Member */) => { + selectedMemberChange = (member: OrganizationMember) => { this.setState({ selectedMember: member }); }; 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.tsx index 563bf17505c..bef77e64000 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.tsx @@ -17,38 +17,31 @@ * 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 * as React from 'react'; import { keyBy, pickBy } from 'lodash'; -import { getUserGroups } from '../../../../api/users'; +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 type { Member } from '../../../../store/organizationsMembers/actions'; */ -/*:: import type { Organization, Group } from '../../../../app/types'; */ +import { Organization, OrganizationMember, Group } from '../../../../app/types'; -/*:: -type Props = { +interface Props { onClose: () => void; - member: Member, - organization: Organization, - organizationGroups: Array<Group>, - updateMemberGroups: (member: Member, add: Array<string>, remove: Array<string>) => void -}; -*/ + member: OrganizationMember; + organization: Organization; + organizationGroups: Group[]; + updateMemberGroups: (member: OrganizationMember, add: string[], remove: string[]) => void; +} -/*:: -type State = { - userGroups?: {}, - loading?: boolean -}; -*/ +interface State { + userGroups?: { [k: string]: UserGroup & { status?: string } }; + loading?: boolean; +} -export default class ManageMemberGroupsForm extends React.PureComponent { - /*:: mounted: boolean */ - /*:: props: Props; */ - state /*: State */ = {}; +export default class ManageMemberGroupsForm extends React.PureComponent<Props, State> { + mounted = false; + state: State = {}; componentDidMount() { this.mounted = true; @@ -75,7 +68,7 @@ export default class ManageMemberGroupsForm extends React.PureComponent { ); }; - isGroupSelected = (groupName /*: string */) => { + isGroupSelected = (groupName: string) => { if (this.state.userGroups) { const group = this.state.userGroups[groupName] || {}; if (group.status) { @@ -87,8 +80,8 @@ export default class ManageMemberGroupsForm extends React.PureComponent { return false; }; - onCheck = (groupName /*: string */, checked /*: boolean */) => { - this.setState((prevState /*: State */) => { + onCheck = (groupName: string, checked: boolean) => { + this.setState((prevState: State) => { const userGroups = prevState.userGroups || {}; const group = userGroups[groupName] || {}; let status = ''; @@ -101,8 +94,8 @@ export default class ManageMemberGroupsForm extends React.PureComponent { }); }; - handleSubmit = (e /*: Object */) => { - e.preventDefault(); + handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { + event.preventDefault(); this.props.updateMemberGroups( this.props.member, Object.keys(pickBy(this.state.userGroups, group => group.status === 'add')), 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.tsx index b33c53f0907..c6e0d603dd2 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.tsx @@ -17,27 +17,22 @@ * 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 * as 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'; */ +import { Organization, OrganizationMember } from '../../../../app/types'; -/*:: -type Props = { +interface Props { onClose: () => void; - member: Member, - organization: Organization, - removeMember: (member: Member) => void -}; -*/ + member: OrganizationMember; + organization: Organization; + removeMember: (member: OrganizationMember) => void; +} -export default class RemoveMemberForm extends React.PureComponent { - /*:: props: Props; */ - handleSubmit = (e /*: Object */) => { - e.preventDefault(); +export default class RemoveMemberForm extends React.PureComponent<Props> { + handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { + event.preventDefault(); this.props.removeMember(this.props.member); this.props.onClose(); }; 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.tsx index 308ff1f7d5e..3406cd7361d 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.tsx @@ -17,7 +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. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import { click } from '../../../../../helpers/testUtils'; import AddMemberForm from '../AddMemberForm'; @@ -25,7 +25,13 @@ import AddMemberForm from '../AddMemberForm'; const memberLogins = ['admin']; it('should render and open the modal', () => { - const wrapper = shallow(<AddMemberForm addMember={jest.fn()} memberLogins={memberLogins} />); + const wrapper = shallow( + <AddMemberForm + addMember={jest.fn()} + memberLogins={memberLogins} + organization={{ key: 'foo', name: 'Foo' }} + /> + ); expect(wrapper).toMatchSnapshot(); wrapper.setState({ open: true }); @@ -34,9 +40,15 @@ it('should render and open the modal', () => { }); it('should correctly handle user interactions', () => { - const wrapper = shallow(<AddMemberForm addMember={jest.fn()} memberLogins={memberLogins} />); + 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().closeForm(); + (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.tsx index 579a42f96ef..32b0839c275 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.tsx @@ -17,28 +17,28 @@ * 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 * as React from 'react'; import { shallow } from 'enzyme'; -import { click, mockEvent } from '../../../../../helpers/testUtils'; +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', + id: 7, name: 'professionals', description: '', membersCount: 12 }, { - id: '11', + id: 11, name: 'pull-request-analysers', description: 'Technical accounts', membersCount: 3 }, { - id: '1', + id: 1, name: 'sonar-administrators', description: 'System administrators', membersCount: 17 @@ -68,6 +68,7 @@ it('should render', () => { const wrapper = shallow( <ManageMemberGroupsForm member={member} + onClose={jest.fn()} organization={organization} organizationGroups={organizationGroups} updateMemberGroups={jest.fn()} @@ -78,21 +79,23 @@ it('should render', () => { 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); + 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(form.instance.isGroupSelected(11)).toBeFalsy(); - expect(form.instance.isGroupSelected(7)).toBeTruthy(); + 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); - form.instance.onCheck(11, false); - form.instance.onCheck(7, true); - form.instance.handleSubmit(mockEvent); + 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.tsx index fca12f0187b..92fbdb9723d 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.tsx @@ -17,17 +17,22 @@ * 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 * as React from 'react'; import { shallow } from 'enzyme'; -import { click, mockEvent } from '../../../../../helpers/testUtils'; +import { mockEvent } from '../../../../../helpers/testUtils'; import RemoveMemberForm from '../RemoveMemberForm'; const member = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }; -const organization = { name: 'MyOrg' }; +const organization = { key: 'myorg', name: 'MyOrg' }; it('should render ', () => { const wrapper = shallow( - <RemoveMemberForm member={member} organization={organization} removeMember={jest.fn()} /> + <RemoveMemberForm + member={member} + onClose={jest.fn()} + organization={organization} + removeMember={jest.fn()} + /> ); expect(wrapper).toMatchSnapshot(); }); @@ -42,7 +47,7 @@ it('should correctly handle user interactions', () => { removeMember={removeMember} /> ); - wrapper.instance().handleSubmit(mockEvent); + (wrapper.instance() as RemoveMemberForm).handleSubmit(mockEvent as any); expect(removeMember).toBeCalledWith({ avatar: '', groupCount: 3, 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.tsx.snap index d480a393f5b..d480a393f5b 100644 --- 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.tsx.snap 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.tsx.snap index 43f0bcecb5a..63f4e47e9b9 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap @@ -55,6 +55,7 @@ Object { exports[`should render 1`] = ` <Modal contentLabel="organization.members.manage_groups" + onRequestClose={[MockFunction]} > <header className="modal-head" @@ -84,7 +85,9 @@ exports[`should render 1`] = ` <SubmitButton> save </SubmitButton> - <ResetButtonLink> + <ResetButtonLink + onClick={[MockFunction]} + > cancel </ResetButtonLink> </div> 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.tsx.snap index ef1c3bfb9f1..c4a8afef3ef 100644 --- 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.tsx.snap @@ -4,6 +4,7 @@ exports[`should render 1`] = ` <Modal contentLabel="users.remove" key="remove-member-modal" + onRequestClose={[MockFunction]} > <header className="modal-head" @@ -30,7 +31,9 @@ exports[`should render 1`] = ` > remove </SubmitButton> - <ResetButtonLink> + <ResetButtonLink + onClick={[MockFunction]} + > cancel </ResetButtonLink> </div> diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx index 3ee87fc0f36..112d2ef75ce 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx @@ -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/OrganizationNavigationMenuContainer.tsx index e63d8b4b0b3..1fda2012f13 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenu.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenuContainer.tsx @@ -18,21 +18,35 @@ * 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 } from '../../../app/types'; +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 Props { +interface StateToProps { + currentUser: CurrentUser; + userOrganizations: Organization[]; +} + +interface OwnProps { location: { pathname: string }; organization: Organization; } -export default function OrganizationNavigationMenu({ location, organization }: Props) { +type Props = OwnProps & StateToProps; + +export function OrganizationNavigationMenu({ + currentUser, + location, + organization, + userOrganizations +}: Props) { return ( <NavBarTabs className="navbar-context-tabs"> <li> @@ -50,7 +64,7 @@ export default function OrganizationNavigationMenu({ location, organization }: P {translate('issues.page')} </Link> </li> - {hasPrivateAccess(organization) && ( + {hasPrivateAccess(currentUser, organization, userOrganizations) && ( <> <li> <Link @@ -72,7 +86,7 @@ export default function OrganizationNavigationMenu({ location, organization }: P </> )} - {isCurrentUserMemberOf(organization) && ( + {isCurrentUserMemberOf(currentUser, organization, userOrganizations) && ( <li> <Link activeClassName="active" to={`/organizations/${organization.key}/members`}> {translate('organization.members.page')} @@ -87,3 +101,10 @@ export default function OrganizationNavigationMenu({ location, organization }: P </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__/OrganizationNavigationMenuContainer-test.tsx index 251e41573de..ee37622b91b 100644 --- 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__/OrganizationNavigationMenuContainer-test.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { shallow } from 'enzyme'; -import OrganizationNavigationMenu from '../OrganizationNavigationMenu'; +import { OrganizationNavigationMenu } from '../OrganizationNavigationMenuContainer'; import { Visibility } from '../../../../app/types'; import { isCurrentUserMemberOf, hasPrivateAccess } from '../../../../helpers/organizations'; @@ -34,6 +34,13 @@ const organization = { 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(); @@ -41,7 +48,14 @@ beforeEach(() => { it('renders', () => { expect( - shallow(<OrganizationNavigationMenu location={{ pathname: '' }} organization={organization} />) + shallow( + <OrganizationNavigationMenu + currentUser={loggedInUser} + location={{ pathname: '' }} + organization={organization} + userOrganizations={[organization]} + /> + ) ).toMatchSnapshot(); }); @@ -49,8 +63,10 @@ it('renders for admin', () => { expect( shallow( <OrganizationNavigationMenu + currentUser={loggedInUser} location={{ pathname: '' }} organization={{ ...organization, canAdmin: true }} + userOrganizations={[organization]} /> ) ).toMatchSnapshot(); diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap index 820e5dc8611..240404ba19f 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap @@ -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__/OrganizationNavigationMenuContainer-test.tsx.snap index a51bf8cc513..a51bf8cc513 100644 --- 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__/OrganizationNavigationMenuContainer-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx index c015b983357..cbb5d82aa9b 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx @@ -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; } diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx index 5de80726a26..cf7600a82b6 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx @@ -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; } diff --git a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx index 1fffa7f7d3e..8b4e37b1ec6 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx @@ -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; diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.tsx index d572edb0d5b..e562aac058e 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.tsx @@ -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; } diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx index a019fca6cbf..23cf0206096 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx @@ -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; } diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx index 7497a286fa9..babf7c999e0 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx @@ -171,6 +171,7 @@ function shallowRender( currentUser={{ isLoggedIn: true }} isFavorite={false} location={{ pathname: '/projects', query: {} }} + organization={undefined} organizationsEnabled={false} {...props} />, diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsList-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsList-test.tsx index a7dcb6ea2bc..fd28e6c2481 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsList-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsList-test.tsx @@ -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} /> diff --git a/server/sonar-web/src/main/js/apps/projects/utils.ts b/server/sonar-web/src/main/js/apps/projects/utils.ts index 419ef569f9f..fe010d42316 100644 --- a/server/sonar-web/src/main/js/apps/projects/utils.ts +++ b/server/sonar-web/src/main/js/apps/projects/utils.ts @@ -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'; diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewOrganizationForm.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewOrganizationForm.tsx index 1a0e5500129..5244898544d 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewOrganizationForm.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewOrganizationForm.tsx @@ -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.ts.snap index c5ad9b73625..c5ad9b73625 100644 --- 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.ts.snap 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.ts index 2180d6af6f0..e9321e29f90 100644 --- 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.ts @@ -19,14 +19,14 @@ */ import organizations, { getOrganizationByKey, areThereCustomOrganizations } from '../duck'; +const state0 = { byKey: {}, my: [], groups: {} }; + describe('Reducer', () => { it('should have initial state', () => { - expect(organizations(undefined, {})).toMatchSnapshot(); + expect((organizations as any)(undefined, {})).toMatchSnapshot(); }); it('should receive organizations', () => { - const state0 = { byKey: {} }; - const action1 = { type: 'RECEIVE_ORGANIZATIONS', organizations: [{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }] @@ -46,7 +46,7 @@ describe('Reducer', () => { describe('Selectors', () => { it('getOrganizationByKey', () => { const foo = { key: 'foo', name: 'Foo' }; - const state = { byKey: { foo } }; + const state = { ...state0, byKey: { foo } }; expect(getOrganizationByKey(state, 'foo')).toBe(foo); expect(getOrganizationByKey(state, 'bar')).toBeFalsy(); }); @@ -54,8 +54,8 @@ describe('Selectors', () => { 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); + expect(areThereCustomOrganizations({ ...state0, byKey: {} })).toBe(false); + expect(areThereCustomOrganizations({ ...state0, byKey: { foo } })).toBe(false); + expect(areThereCustomOrganizations({ ...state0, byKey: { foo, bar } })).toBe(true); }); }); diff --git a/server/sonar-web/src/main/js/store/organizations/duck.ts b/server/sonar-web/src/main/js/store/organizations/duck.ts index 8b6c76e3993..5f01ac95261 100644 --- a/server/sonar-web/src/main/js/store/organizations/duck.ts +++ b/server/sonar-web/src/main/js/store/organizations/duck.ts @@ -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.ts index adf5ed2c5e3..5b3957e1f6c 100644 --- a/server/sonar-web/src/main/js/store/organizations/utils.js +++ b/server/sonar-web/src/main/js/store/organizations/utils.ts @@ -17,14 +17,13 @@ * 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 */) { +export function getOrganization(key: string) { const store = getStore(); const state = store.getState(); return getOrganizationByKey(state, key); |