diff --git a/package-lock.json b/package-lock.json index 89a7ebb47af..5da8753852f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64981,7 +64981,7 @@ }, "packages/common": { "name": "@esri/hub-common", - "version": "14.21.0", + "version": "14.21.1", "license": "Apache-2.0", "dependencies": { "abab": "^2.0.5", diff --git a/packages/common/src/groups/_internal/AddOrInviteUsersToGroupUtils.ts b/packages/common/src/groups/_internal/AddOrInviteUsersToGroupUtils.ts deleted file mode 100644 index a824bcd64d8..00000000000 --- a/packages/common/src/groups/_internal/AddOrInviteUsersToGroupUtils.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { - IAddOrInviteContext, - IAddOrInviteResponse, - IUserOrgRelationship, - IUserWithOrgType, -} from "../types"; - -import { processAutoAddUsers } from "./processAutoAddUsers"; -import { processInviteUsers } from "./processInviteUsers"; - -// Add or invite flow based on the type of user begins here - -/** - * @private - * Handles add/invite logic for collaboration coordinators inside partnered orgs. - * This is intentionally split out from the invitation of partnered org normal members, - * because the two types of partnered org usres (regular and collaboration coordinator) - * always come from the same 'bucket', however have distinctly different add paths Invite vs auto add. - * It returns either an empty instance of the addOrInviteResponse - * object, or their response from auto adding users. - * - * @export - * @param {IAddOrInviteContext} context context object - * @return {IAddOrInviteResponse} response object - */ -export async function addOrInviteCollaborationCoordinators( - context: IAddOrInviteContext -): Promise { - // If there are no org users return handling no users - if ( - !context.collaborationCoordinator || - context.collaborationCoordinator.length === 0 - ) { - // we return an empty object because - // if you leave out any of the props - // from the final object and you are concatting together arrays you can concat - // an undeifined inside an array which will throw off array lengths. - return handleNoUsers(); - } - return processAutoAddUsers(context, "collaborationCoordinator"); -} - -/** - * @private - * Handles add/invite logic for community users - * It returns either an empty instance of the addOrInviteResponse - * object, or either ther esponse from processing auto adding - * users or inviting users. If an email has been passed in it also notifies - * processAutoAddUsers that emails should be sent. - * - * @export - * @param {IAddOrInviteContext} context context object - * @return {IAddOrInviteResponse} response object - */ -export async function addOrInviteCommunityUsers( - context: IAddOrInviteContext -): Promise { - // We default to handleNoUsers - // we return an empty object because - // if you leave out any of the props - // from the final object and you are concatting together arrays you can concat - // an undeifined inside an array which will throw off array lengths. - let fnToCall = handleNoUsers; - let shouldEmail = false; - - // If community users were passed in... - if (context.community && context.community.length > 0) { - // Default to either autoAdd or invite based on canAutoAddUser. - fnToCall = context.canAutoAddUser - ? processAutoAddUsers - : processInviteUsers; - // If we have an email object - // Then we will auto add... - // But whether or not we email is still in question - if (context.email) { - // If the email object has the groupId property... - if (context.email.hasOwnProperty("groupId")) { - // If the email objects groupId property is the same as the current groupId in context... - // (This function is part of a flow that could work for N groupIds) - if (context.email.groupId === context.groupId) { - // Then we auto add and send email - fnToCall = processAutoAddUsers; - shouldEmail = true; - } // ELSE if the groupId's do NOT match, we will fall back - // To autoAdd or invite as per line 32. - // We are doing the above logic (lines 43 - 47) because - // We wish to add users to core groups, followers, and content groups - // but only to email the core group. - } else { - // If it does not have a groupId at all then we will autoAdd and email. - fnToCall = processAutoAddUsers; - shouldEmail = true; - } - } - } - // Return/call the function - return fnToCall(context, "community", shouldEmail); -} - -/** - * @private - * Handles add/invite logic for Org users - * It returns either an empty instance of the addOrInviteResponse - * object, or either ther esponse from processing auto adding a users or inviting a user - * - * @export - * @param {IAddOrInviteContext} context context object - * @return {IAddOrInviteResponse} response object - */ -export async function addOrInviteOrgUsers( - context: IAddOrInviteContext -): Promise { - // If there are no org users return handling no users - if (!context.org || context.org.length === 0) { - // we return an empty object because - // if you leave out any of the props - // from the final object and you are concatting together arrays you can concat - // an undeifined inside an array which will throw off array lengths. - return handleNoUsers(); - } - // for org user if you have assignUsers then auto add the user - // if not then invite the user - return context.canAutoAddUser - ? processAutoAddUsers(context, "org") - : processInviteUsers(context, "org"); -} - -/** - * @private - * Handles add/invite logic for partnered org users. - * It returns either an empty instance of the addOrInviteResponse - * object, or their response from inviting users. - * - * @export - * @param {IAddOrInviteContext} context context object - * @return {IAddOrInviteResponse} response object - */ -export async function addOrInvitePartneredUsers( - context: IAddOrInviteContext -): Promise { - // If there are no org users return handling no users - if (!context.partnered || context.partnered.length === 0) { - // we return an empty object because - // if you leave out any of the props - // from the final object and you are concatting together arrays you can concat - // an undeifined inside an array which will throw off array lengths. - return handleNoUsers(); - } - // process invite - return processInviteUsers(context, "partnered"); -} - -/** - * @private - * Handles add/invite logic for world users - * It either returns an empty instance of the add/invite response - * object, or a populated version from processInviteUsers - * - * @export - * @param {IAddOrInviteContext} context Context object - * @return {IAddOrInviteResponse} Response object - */ -export async function addOrInviteWorldUsers( - context: IAddOrInviteContext -): Promise { - // If there are no world users return handling no users - if (!context.world || context.world.length === 0) { - // we return an empty object because - // if you leave out any of the props - // from the final object and you are concatting together arrays you can concat - // an undeifined inside an array which will throw off array lengths. - return handleNoUsers(); - } - // process invite - return processInviteUsers(context, "world"); -} - -// Add or invite flow based on the type of user ends here - -/** - * @private - * Returns an empty instance of the addorinviteresponse object. - * We are using this because if you leave out any of the props - * from the final object and you are concatting together arrays you can concat - * an undeifined inside an array which will throw off array lengths. - * - * @export - * @return {IAddOrInviteResponse} - */ -export async function handleNoUsers( - context?: IAddOrInviteContext, - userType?: "world" | "org" | "community" | "partnered", - shouldEmail?: boolean -): Promise { - return { - notAdded: [], - notEmailed: [], - notInvited: [], - users: [], - errors: [], - }; -} - -/** - * @private - * Takes users array and sorts them into an object by the type of user they are - * based on the orgType prop (world|org|community) - * - * @export - * @param {IUserWithOrgType[]} users array of users - * @return {IUserOrgRelationship} Object of users sorted by type (world, org, community) - */ -export function groupUsersByOrgRelationship( - users: IUserWithOrgType[] -): IUserOrgRelationship { - return users.reduce( - (acc, user) => { - // keyof needed to make bracket notation work without TS throwing a wobbly. - const orgType = user.orgType as keyof IUserOrgRelationship; - acc[orgType].push(user); - return acc; - }, - { - world: [], - org: [], - community: [], - partnered: [], - collaborationCoordinator: [], - } - ); -} diff --git a/packages/common/src/groups/_internal/processAutoAddUsers.ts b/packages/common/src/groups/_internal/processAutoAddUsers.ts deleted file mode 100644 index 55e9c18dab2..00000000000 --- a/packages/common/src/groups/_internal/processAutoAddUsers.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { IUser } from "@esri/arcgis-rest-types"; -import { IAddOrInviteContext, IAddOrInviteResponse } from "../types"; -import { getProp } from "../../objects/get-prop"; -import { ArcGISRequestError } from "@esri/arcgis-rest-request"; -import { autoAddUsers } from "../autoAddUsers"; -import { processEmailUsers } from "./processEmailUsers"; -import { autoAddUsersAsAdmins } from "./autoAddUsersAsAdmins"; - -/** - * @private - * Governs logic for automatically adding N users to a group. - * Users are added as either a regular user OR as an administrator of the group - * depending on the addUserAsGroupAdmin prop on the IAddOrInviteContext. - * If there is an email object on the IAddOrInviteContext, then email notifications are sent. - * - * @export - * @param {IAddOrInviteContext} context context object - * @param {string} userType what type of user is it: org | world | community - * @param {boolean} [shouldEmail=false] should the user be emailed? - * @return {IAddOrInviteResponse} response object - */ -export async function processAutoAddUsers( - context: IAddOrInviteContext, - userType: - | "world" - | "org" - | "community" - | "partnered" - | "collaborationCoordinator", - shouldEmail: boolean = false -): Promise { - // fetch users out of context object - const users: IUser[] = getProp(context, userType); - let autoAddResponse; - let emailResponse; - let notAdded: string[] = []; - let errors: ArcGISRequestError[] = []; - // fetch addUserAsGroupAdmin out of context - const { addUserAsGroupAdmin } = context; - - if (addUserAsGroupAdmin) { - // if is core group we elevate user to admin - autoAddResponse = await autoAddUsersAsAdmins( - getProp(context, "groupId"), - users, - getProp(context, "primaryRO") - ); - } else { - // if not then we are just auto adding them - autoAddResponse = await autoAddUsers( - getProp(context, "groupId"), - users, - getProp(context, "primaryRO") - ); - } - // handle notAdded users - if (autoAddResponse.notAdded) { - notAdded = notAdded.concat(autoAddResponse.notAdded); - } - // Merge errors into empty array - if (autoAddResponse.errors) { - errors = errors.concat(autoAddResponse.errors); - } - // run email process - if (shouldEmail) { - emailResponse = await processEmailUsers(context); - // merge errors in to overall errors array to keep things flat - if (emailResponse.errors && emailResponse.errors.length > 0) { - errors = errors.concat(emailResponse.errors); - } - } - // if you leave out any of the props - // from the final object and you are concatting together arrays you can concat - // an undeifined inside an array which will throw off array lengths. - return { - users: users.map((u) => u.username), - notAdded, - errors, - notEmailed: emailResponse?.notEmailed || [], - notInvited: [], - }; -} diff --git a/packages/common/src/groups/_internal/processEmailUsers.ts b/packages/common/src/groups/_internal/processEmailUsers.ts deleted file mode 100644 index 1e5764a5c29..00000000000 --- a/packages/common/src/groups/_internal/processEmailUsers.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { IUser } from "@esri/arcgis-rest-types"; -import { IAddOrInviteContext, IAddOrInviteResponse } from "../types"; -import { getProp } from "../../objects/get-prop"; -import { ArcGISRequestError } from "@esri/arcgis-rest-request"; -import { emailOrgUsers } from "../emailOrgUsers"; - -/** - * @private - * Governs the logic for emailing N users. It acts under the assumption - * that all the 'community' users are the ones being emailed (this is due to platform rules we conform to) - * Function is called upstream depending on if an email object is attached to the context. - * Email object contains its own auth as it'll require the community admin to send the email itself. - * An individual email call goes out for each user due to how the response of multiple users in a single call works. - * - * @export - * @param {IAddOrInviteContext} context context object - * @return {IAddOrInviteResponse} response object - */ -export async function processEmailUsers( - context: IAddOrInviteContext -): Promise { - // Fetch users out of context. We only email community users so we are - // explicit about that - const users: IUser[] = getProp(context, "community"); - const notEmailed: string[] = []; - let errors: ArcGISRequestError[] = []; - // iterate through users as we want a distinct email call per user due to how - // batch email will only respond with success: true/false - // and if there is an error then it gets priority even though successes do still go through - for (const user of users) { - // Make email call... - const emailResponse = await emailOrgUsers( - [user], - getProp(context, "email.message"), - getProp(context, "email.auth"), - true - ); - // If it's just a failed email - // then add username to notEmailed array - if (!emailResponse.success) { - notEmailed.push(user.username); - // If there was a legit error - // Then only the error returns from - // online. Add error AND include username in notEmailed array. - if (emailResponse.errors) { - errors = errors.concat(emailResponse.errors); - } - } - } - // if you leave out any of the props - // from the final object and you are concatting together arrays you can concat - // an undeifined inside an array which will throw off array lengths. - return { - users: users.map((u) => u.username), - notEmailed, - errors, - notInvited: [], - notAdded: [], - }; -} diff --git a/packages/common/src/groups/_internal/processInviteUsers.ts b/packages/common/src/groups/_internal/processInviteUsers.ts deleted file mode 100644 index c930475dbb4..00000000000 --- a/packages/common/src/groups/_internal/processInviteUsers.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { IUser } from "@esri/arcgis-rest-types"; -import { IAddOrInviteContext, IAddOrInviteResponse } from "../types"; -import { getProp } from "../../objects/get-prop"; -import { ArcGISRequestError } from "@esri/arcgis-rest-request"; -import { inviteUsers } from "../inviteUsers"; -/** - * @private - * Governs the logic for inviting N users to a single group. - * An individual invite call goes out for each user and the results are consolidated. - * See comment in function about the for...of loop which explains reasoning. - * - * @export - * @param {IAddOrInviteContext} context context object - * @param {string} userType what type of user is it: org | world | community - * @return {IAddOrInviteResponse} response object - */ -export async function processInviteUsers( - context: IAddOrInviteContext, - userType: "world" | "org" | "community" | "partnered" -): Promise { - // Fetch users out of context based on userType - const users: IUser[] = getProp(context, userType); - const notInvited: string[] = []; - let errors: ArcGISRequestError[] = []; - const { addUserAsGroupAdmin } = context; - // iterate through users as we want a distinct invite call per user due to how - // batch invites will only respond with success: true/false - // and if there is an error then it gets priority even though successes do still go through - for (const user of users) { - // Invite users call - const inviteResponse = await inviteUsers( - getProp(context, "groupId"), - [user], - getProp(context, "primaryRO"), - 20160, // timeout - addUserAsGroupAdmin ? "group_admin" : "group_member" // if we are in a core group we want to invite them as a group admin, otherwise a group member - ); - // If it's just a failed invite then - // add username to notInvited array - if (!inviteResponse.success) { - notInvited.push(user.username); - // If there was a legit error - // Then only the error returns from - // online. Add error AND include username in notInvited array. - if (inviteResponse.errors) { - errors = errors.concat(inviteResponse.errors); - } - } - } - // if you leave out any of the props - // from the final object and you are concatting together arrays you can concat - // an undeifined inside an array which will throw off array lengths. - return { - users: users.map((u) => u.username), - notInvited, - errors, - notEmailed: [], - notAdded: [], - }; -} diff --git a/packages/common/src/groups/addGroupMembers.ts b/packages/common/src/groups/addGroupMembers.ts new file mode 100644 index 00000000000..13efe571a5d --- /dev/null +++ b/packages/common/src/groups/addGroupMembers.ts @@ -0,0 +1,94 @@ +import { IAuthenticationManager } from "@esri/arcgis-rest-request"; +import { IUser } from "@esri/arcgis-rest-types"; +import { failSafe } from "../utils"; +import { autoAddUsers } from "./autoAddUsers"; +import { inviteUsers } from "./inviteUsers"; +import { IAddGroupMembersResult, IAddOrInviteMemberResponse } from "./types"; +import { IUserRequestOptions } from "@esri/arcgis-rest-auth"; + +/** + * Add or invite N users to a single group. + * If autoAdd is true (if the user doing the adding has the 'portal:admin:assignToGroups' priv) + * then we attempt to auto add EVERY user to the group. + * If that call fails, then we attempt to invite them. + * + * @export + * @param {string} groupId ID of the group the users will be added to + * @param {string[]} usernames usernames of the users to add + * @param {IUserRequestOptions} auth Auth + * @param {boolean} autoAdd should we auto add users? + * @return {*} {Promise} + */ +export async function addGroupMembers( + groupId: string, + usernames: string[], + auth: IUserRequestOptions, + autoAdd: boolean +): Promise { + const added: string[] = []; + const invited: string[] = []; + const notAdded: string[] = []; + const notInvited: string[] = []; + const responses: IAddOrInviteMemberResponse[] = []; + // iterate through users as we want a distinct add/invite call per user. + // This is primarily because batch inviting of users will only return a single consolidated + // 'success' response, which provides no granularity on which users were actually successfully invited. + // Similarly errors are consolidated into a single array, which again provides no granularity on which users + // experienced the error. + for (const username of usernames) { + let inviteUser = true; + const response: IAddOrInviteMemberResponse = { + username, + add: null, + invite: null, + }; + // expand user into an 'IUser' object for add/invite functions to then extract... + const user = { username } as IUser; + // Create failSafe functions for add/invite + const failSafeAdd = failSafe(autoAddUsers); + const failSafeInvite = failSafe(inviteUsers); + // If we can add the user automatically, then attempt to do so + if (autoAdd) { + // fail safe add + const addResponse = await failSafeAdd( + groupId, + [user], + auth.authentication + ); + // add response to response obj + response.add = addResponse; + // if they were added, then don't invite and add to added array + if (!addResponse.notAdded || addResponse.notAdded.length === 0) { + inviteUser = false; + added.push(username); + } else { + notAdded.push(username); + } + } + if (inviteUser) { + // fail safe invite + const inviteResponse = await failSafeInvite( + groupId, + [user], + auth.authentication + ); + // add response to response obj + response.invite = inviteResponse; + // if they were invited, then add to invited array + if (inviteResponse.success) { + invited.push(username); + } else { + notInvited.push(username); + } + } + // push response to responses array + responses.push(response); + } + return { + added, + invited, + notAdded, + notInvited, + responses, + }; +} diff --git a/packages/common/src/groups/addOrInviteUsersToGroup.ts b/packages/common/src/groups/addOrInviteUsersToGroup.ts deleted file mode 100644 index 47f2912a0c9..00000000000 --- a/packages/common/src/groups/addOrInviteUsersToGroup.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { IAuthenticationManager } from "@esri/arcgis-rest-request"; -import { - IAddOrInviteContext, - IAddOrInviteEmail, - IAddOrInviteToGroupResult, - IUserOrgRelationship, - IUserWithOrgType, -} from "./types"; -import { - addOrInviteCollaborationCoordinators, - addOrInviteCommunityUsers, - addOrInviteOrgUsers, - addOrInvitePartneredUsers, - addOrInviteWorldUsers, - groupUsersByOrgRelationship, -} from "./_internal/AddOrInviteUsersToGroupUtils"; - -/** - * Add or invite N users to a single group - * Org|community|world logic flows are run even if there are no users applicable for that particular path. - * Results from each path are consolidated and surfaced in the return object as failures and errors are of - * more importance than successes. - * - * @export - * @param {string} groupId Group we are adding users to - * @param {IUserWithOrgType[]} users array of users to add - * @param {IAuthenticationManager} primaryRO primary requestOptions - * @param {boolean} canAutoAddUser Can we automatically add a user to the group? - * @param {boolean} addUserAsGroupAdmin Should the user be added as a group administrator - * @param {IAddOrInviteEmail} email Email object - * @return {IAddOrInviteToGroupResult} Result object - */ -export async function addOrInviteUsersToGroup( - groupId: string, - users: IUserWithOrgType[], - primaryRO: IAuthenticationManager, - canAutoAddUser: boolean, - addUserAsGroupAdmin: boolean, - email: IAddOrInviteEmail -): Promise { - // Group users by their org relationship - const parsedUsers: IUserOrgRelationship = groupUsersByOrgRelationship(users); - // build up params for the context - const inputParams = { - groupId, // The group ID that the users shall be added/invited to. - primaryRO, // requestOptions required to auth for all the various add/invite logic except email - allUsers: users, // All users. - canAutoAddUser, // can the user be automatically added to the group rather than just invited? - addUserAsGroupAdmin, // Should they be added to the group as a group Admin vs a normal group member - email, // Either undefined or an object that contains both message/subject of the email and the auth for the email request - }; - // create context from params and parsed users - const context: IAddOrInviteContext = Object.assign(inputParams, parsedUsers); - // result obj by org relationship - const result: IAddOrInviteToGroupResult = { - community: await addOrInviteCommunityUsers(context), - org: await addOrInviteOrgUsers(context), - world: await addOrInviteWorldUsers(context), - partnered: await addOrInvitePartneredUsers(context), - collaborationCoordinator: await addOrInviteCollaborationCoordinators( - context - ), - notAdded: [], - notInvited: [], - notEmailed: [], - errors: [], - groupId, - }; - // Bring not added / invited / emailed / errors up to the top level - result.notAdded = [ - ...result.community.notAdded, - ...result.org.notAdded, - ...result.world.notAdded, - ]; - result.notInvited = [ - ...result.community.notInvited, - ...result.org.notInvited, - ...result.world.notInvited, - ]; - result.notEmailed = [ - ...result.community.notEmailed, - ...result.org.notEmailed, - ...result.world.notEmailed, - ]; - result.errors = [ - ...result.community.errors, - ...result.org.errors, - ...result.world.errors, - ]; - return result; -} diff --git a/packages/common/src/groups/addOrInviteUsersToGroups.ts b/packages/common/src/groups/addOrInviteUsersToGroups.ts deleted file mode 100644 index ce8c86f0f44..00000000000 --- a/packages/common/src/groups/addOrInviteUsersToGroups.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { - ArcGISRequestError, - IAuthenticationManager, -} from "@esri/arcgis-rest-request"; -import { - IAddOrInviteEmail, - IAddOrInviteToGroupResult, - IUserWithOrgType, -} from "./types"; -import { addOrInviteUsersToGroup } from "./addOrInviteUsersToGroup"; - -/** - * addOrInviteUsersToGroups adds/invites N users to N groups - * Initial entry point function for add/invite members flow - * when dealing with multiple groups. - * Responses from each group are then consolidated into the final returned object. - * - * @export - * @param {string[]} groupIds array of groups we are adding users to - * @param {IUserWithOrgType[]} users array of users to add to those groups - * @param {IAuthenticationManager} primaryRO primary requestOptions - * @param {boolean} [canAutoAddUser=false] Can we automatically add a user to the group? - * @param {boolean} [addUserAsGroupAdmin=false] Can the user be added to a group as an administrator of that group? - * @param {IAddOrInviteEmail} [email] Email object contains auth for the email && the email object itself - * @return {*} {Promise<{ - * notAdded: string[]; - * notInvited: string[]; - * notEmailed: string[]; - * errors: ArcGISRequestError[]; - * responses: IAddOrInviteToGroupResult[]; - * }>} Results object - */ -export async function addOrInviteUsersToGroups( - groupIds: string[], - users: IUserWithOrgType[], - primaryRO: IAuthenticationManager, - canAutoAddUser: boolean = false, - addUserAsGroupAdmin: boolean = false, - email?: IAddOrInviteEmail -): Promise<{ - notAdded: string[]; - notInvited: string[]; - notEmailed: string[]; - errors: ArcGISRequestError[]; - responses: IAddOrInviteToGroupResult[]; -}> { - let notAdded: string[] = []; - let notInvited: string[] = []; - let notEmailed: string[] = []; - let errors: ArcGISRequestError[] = []; - const responses: IAddOrInviteToGroupResult[] = []; - // need to for..of loop this as a reduce will overwrite promises during execution - // this way we get an object of each group id nicely. - for (const groupId of groupIds) { - // For each group we'll add the users to them. - const result = await addOrInviteUsersToGroup( - groupId, - users, - primaryRO, - canAutoAddUser, - addUserAsGroupAdmin, - email - ); - // attach each groups results - responses.push(result); - // surface results to the top of the stack... - notAdded = notAdded.concat(result.notAdded); - errors = errors.concat(result.errors); - notInvited = notInvited.concat(result.notInvited); - notEmailed = notEmailed.concat(result.notEmailed); - } - // Return built up result object. - return { - notAdded, - notInvited, - notEmailed, - errors, - responses, - }; -} diff --git a/packages/common/src/groups/index.ts b/packages/common/src/groups/index.ts index dffd5eed1a4..b7a5215418c 100644 --- a/packages/common/src/groups/index.ts +++ b/packages/common/src/groups/index.ts @@ -3,8 +3,7 @@ export * from "./add-users-workflow"; export * from "./types"; export * from "./HubGroups"; export * from "./HubGroup"; -export * from "./addOrInviteUsersToGroup"; -export * from "./addOrInviteUsersToGroups"; +export * from "./addGroupMembers"; // TODO: The below are being used in hub-teams. When we deprecate that package we can move // The below into _internal and remove the exports from here. They were previously in // the add-users-workflow directory diff --git a/packages/common/src/groups/types/types.ts b/packages/common/src/groups/types/types.ts index 663e0082c0f..bda3e13ec63 100644 --- a/packages/common/src/groups/types/types.ts +++ b/packages/common/src/groups/types/types.ts @@ -1,86 +1,27 @@ import { - ArcGISRequestError, - IAuthenticationManager, -} from "@esri/arcgis-rest-request"; -import { IUser } from "@esri/arcgis-rest-types"; + IAddGroupUsersResult, + IInviteGroupUsersResult, +} from "@esri/arcgis-rest-portal"; export interface IEmail { subject?: string; body?: string; copyMe?: boolean; } -/** - * User object returned from add users modal in ember application - * It extends the IUser interface with an additional property - * that denotes what org relationship the user might have (world|org|community|partnered|collaborationCoordinator) - */ -export interface IUserWithOrgType extends IUser { - orgType: - | "world" - | "org" - | "community" - | "partnered" - | "collaborationCoordinator"; -} - -/** - * User org relationship interface - * Object contains users parsed by their org relationship (world|org|community|partnered|collaborationCoordinator) - */ -export interface IUserOrgRelationship { - world: IUserWithOrgType[]; - org: IUserWithOrgType[]; - community: IUserWithOrgType[]; - partnered: IUserWithOrgType[]; - collaborationCoordinator: IUserWithOrgType[]; -} /** - * Interface governing the add or invite response out of process auto add / invite / emailing users + * Interface for result object out of addGroupMembers. */ -export interface IAddOrInviteResponse { - users: string[]; - errors: ArcGISRequestError[]; +export interface IAddGroupMembersResult { + added: string[]; + invited: string[]; notAdded: string[]; notInvited: string[]; - notEmailed: string[]; + responses: IAddOrInviteMemberResponse[]; } -/** - * Email input object for add/invite flow - * contains both the IEmail object and auth for the email. - */ -export interface IAddOrInviteEmail { - message: IEmail; - auth: IAuthenticationManager; - groupId?: string; -} - -/** - * Add or invite flow context - object that contains all the needed - * inputs for org/world/community users - */ -export interface IAddOrInviteContext extends IUserOrgRelationship { - groupId: string; - primaryRO: IAuthenticationManager; - allUsers: IUserWithOrgType[]; - canAutoAddUser: boolean; - addUserAsGroupAdmin: boolean; - email: IAddOrInviteEmail; -} - -/** - * Interface for result object out of addOrInviteUsersToGroup. - */ -export interface IAddOrInviteToGroupResult { - errors: ArcGISRequestError[]; - notAdded: string[]; - notInvited: string[]; - notEmailed: string[]; - community: IAddOrInviteResponse; - org: IAddOrInviteResponse; - world: IAddOrInviteResponse; - partnered: IAddOrInviteResponse; - collaborationCoordinator: IAddOrInviteResponse; - groupId: string; +export interface IAddOrInviteMemberResponse { + username: string; + add: IAddGroupUsersResult; + invite: IInviteGroupUsersResult; } diff --git a/packages/common/test/groups/_internal/AddOrInviteUsersToGroupUtils.test.ts b/packages/common/test/groups/_internal/AddOrInviteUsersToGroupUtils.test.ts deleted file mode 100644 index 051f1efb7e8..00000000000 --- a/packages/common/test/groups/_internal/AddOrInviteUsersToGroupUtils.test.ts +++ /dev/null @@ -1,426 +0,0 @@ -// import * as AddOrInviteUtilsModule from "../../../src/groups/_internal/AddOrInviteUsersToGroupUtils"; -import { - IAddOrInviteContext, - IAddOrInviteEmail, - IUserWithOrgType, -} from "../../../src/groups/types"; -import { - addOrInviteCollaborationCoordinators, - addOrInviteCommunityUsers, - addOrInviteOrgUsers, - addOrInvitePartneredUsers, - addOrInviteWorldUsers, - handleNoUsers, - groupUsersByOrgRelationship, -} from "../../../src/groups/_internal/AddOrInviteUsersToGroupUtils"; -import { MOCK_AUTH } from "../../mocks/mock-auth"; -import * as processAutoAddUsersModule from "../../../src/groups/_internal/processAutoAddUsers"; -import * as processInviteUsersModule from "../../../src/groups/_internal/processInviteUsers"; - -describe("AddOrInviteUsersToGroupUtilsModule", () => { - describe("groupUsersByOrgRelationship: ", () => { - it("properly groups users by org relationship", () => { - const users: IUserWithOrgType[] = [ - { orgType: "world" }, - { orgType: "org" }, - { orgType: "community" }, - { orgType: "org" }, - { orgType: "partnered" }, - { orgType: "collaborationCoordinator" }, - ]; - const result = groupUsersByOrgRelationship(users); - expect(result.world.length).toEqual(1); - expect(result.org.length).toEqual(2); - expect(result.community.length).toEqual(1); - expect(result.partnered.length).toEqual(1); - expect(result.collaborationCoordinator.length).toEqual(1); - }); - }); - - describe("addOrInviteCollaborationCoordinators: ", () => { - it("Properly delegates to handleNoUsers when no users supplied", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: undefined as unknown as IAddOrInviteEmail, - world: [], - org: [], - community: [], - partnered: [], - collaborationCoordinator: [], - }; - - const actual = await addOrInviteCollaborationCoordinators(context); - expect(actual).toEqual({ - notAdded: [], - notInvited: [], - notEmailed: [], - users: [], - errors: [], - }); - }); - it("Properly autoAdds when canAutoAdd is supplied", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [{ orgType: "collaborationCoordinator" }], - canAutoAddUser: true, - addUserAsGroupAdmin: false, - email: undefined as unknown as IAddOrInviteEmail, - world: [], - org: [], - community: [], - partnered: [], - collaborationCoordinator: [{ orgType: "collaborationCoordinator" }], - }; - const processAutoAddUsersSpy = spyOn( - processAutoAddUsersModule, - "processAutoAddUsers" - ).and.callFake(() => { - Promise.resolve(); - }); - - const actual = await addOrInviteCollaborationCoordinators(context); - expect(processAutoAddUsersSpy).toHaveBeenCalled(); - }); - }); - - describe("addOrInviteCommunityUsers:", () => { - it("Properly delegates to handleNoUsers when no users supplied", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: { message: {}, auth: MOCK_AUTH }, - world: [], - org: [], - partnered: [], - collaborationCoordinator: [], - community: [], - }; - - const actual = await addOrInviteCommunityUsers(context); - expect(actual).toEqual({ - notAdded: [], - notInvited: [], - notEmailed: [], - users: [], - errors: [], - }); - }); - it("Properly autoAdds when an email is supplied", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [{ orgType: "community" }], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: { message: {}, auth: MOCK_AUTH }, - world: [], - org: [], - partnered: [], - collaborationCoordinator: [], - community: [{ orgType: "community" }], - }; - const processAutoAddUsersSpy = spyOn( - processAutoAddUsersModule, - "processAutoAddUsers" - ).and.callFake(() => { - Promise.resolve(); - }); - - const actual = await addOrInviteCommunityUsers(context); - expect(processAutoAddUsersSpy).toHaveBeenCalled(); - }); - it("Properly autoAdds when an email is supplied for a given groupID", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [{ orgType: "community" }], - canAutoAddUser: true, - addUserAsGroupAdmin: false, - email: { message: {}, auth: MOCK_AUTH, groupId: "abc123" }, - world: [], - org: [], - partnered: [], - collaborationCoordinator: [], - community: [{ orgType: "community" }], - }; - const processAutoAddUsersSpy = spyOn( - processAutoAddUsersModule, - "processAutoAddUsers" - ).and.callFake(() => { - Promise.resolve(); - }); - - const actual = await addOrInviteCommunityUsers(context); - expect(processAutoAddUsersSpy).toHaveBeenCalled(); - expect(processAutoAddUsersSpy.calls.count()).toEqual(1); - }); - it("Properly invites when an email is supplied, but groups dont match", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [{ orgType: "community" }], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: { message: {}, auth: MOCK_AUTH, groupId: "def456" }, - world: [], - org: [], - partnered: [], - collaborationCoordinator: [], - community: [{ orgType: "community" }], - }; - const processInviteUsersSpy = spyOn( - processInviteUsersModule, - "processInviteUsers" - ).and.callFake(() => { - Promise.resolve(); - }); - - const actual = await addOrInviteCommunityUsers(context); - expect(processInviteUsersSpy).toHaveBeenCalled(); - }); - it("Properly autoAdds when canAutoAdd is supplied", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [{ orgType: "community" }], - canAutoAddUser: true, - addUserAsGroupAdmin: false, - email: undefined as unknown as IAddOrInviteEmail, - world: [], - org: [], - partnered: [], - collaborationCoordinator: [], - community: [{ orgType: "community" }], - }; - const processAutoAddUsersSpy = spyOn( - processAutoAddUsersModule, - "processAutoAddUsers" - ).and.callFake(() => { - Promise.resolve(); - }); - - const actual = await addOrInviteCommunityUsers(context); - expect(processAutoAddUsersSpy).toHaveBeenCalled(); - }); - it("Properly falls back to inviting users", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [{ orgType: "community" }], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: undefined as unknown as IAddOrInviteEmail, - world: [], - org: [], - partnered: [], - collaborationCoordinator: [], - community: [{ orgType: "community" }], - }; - const processInviteUsersSpy = spyOn( - processInviteUsersModule, - "processInviteUsers" - ).and.callFake(() => { - Promise.resolve(); - }); - - const actual = await addOrInviteCommunityUsers(context); - expect(processInviteUsersSpy).toHaveBeenCalled(); - }); - }); - - describe("addOrInviteOrgUsers: ", () => { - it("Properly delegates to handleNoUsers when no users supplied", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: undefined as unknown as IAddOrInviteEmail, - world: [], - org: [], - community: [], - partnered: [], - collaborationCoordinator: [], - }; - - const actual = await addOrInviteOrgUsers(context); - expect(actual).toEqual({ - notAdded: [], - notInvited: [], - notEmailed: [], - users: [], - errors: [], - }); - }); - it("Properly autoAdds when canAutoAdd is supplied", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [{ orgType: "org" }], - canAutoAddUser: true, - addUserAsGroupAdmin: false, - email: undefined as unknown as IAddOrInviteEmail, - world: [], - org: [{ orgType: "org" }], - community: [], - partnered: [], - collaborationCoordinator: [], - }; - const processAutoAddUsersSpy = spyOn( - processAutoAddUsersModule, - "processAutoAddUsers" - ).and.callFake(() => { - Promise.resolve(); - }); - - const actual = await addOrInviteOrgUsers(context); - expect(processAutoAddUsersSpy).toHaveBeenCalled(); - }); - it("Properly falls back to inviting users", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [{ orgType: "org" }], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: undefined as unknown as IAddOrInviteEmail, - world: [], - org: [{ orgType: "org" }], - community: [], - partnered: [], - collaborationCoordinator: [], - }; - const processInviteUsersSpy = spyOn( - processInviteUsersModule, - "processInviteUsers" - ).and.callFake(() => { - Promise.resolve(); - }); - - const actual = await addOrInviteOrgUsers(context); - expect(processInviteUsersSpy).toHaveBeenCalled(); - }); - }); - - describe("addOrInvitePartneredUsers: ", () => { - it("Properly delegates to handleNoUsers when no users supplied", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: undefined as unknown as IAddOrInviteEmail, - world: [], - org: [], - community: [], - partnered: [], - collaborationCoordinator: [], - }; - - const actual = await addOrInvitePartneredUsers(context); - expect(actual).toEqual({ - notAdded: [], - notInvited: [], - notEmailed: [], - users: [], - errors: [], - }); - }); - it("Properly falls back to inviting users", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [{ orgType: "partnered" }], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: undefined as unknown as IAddOrInviteEmail, - world: [], - org: [], - community: [], - partnered: [{ orgType: "partnered" }], - collaborationCoordinator: [], - }; - const processInviteUsersSpy = spyOn( - processInviteUsersModule, - "processInviteUsers" - ).and.callFake(() => { - Promise.resolve(); - }); - - const actual = await addOrInvitePartneredUsers(context); - expect(processInviteUsersSpy).toHaveBeenCalled(); - }); - }); - - describe("addOrInviteWorldUsers: ", () => { - it("Properly delegates to handleNoUsers when no users supplied", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: undefined as unknown as IAddOrInviteEmail, - world: [], - org: [], - community: [], - partnered: [], - collaborationCoordinator: [], - }; - - const actual = await addOrInviteWorldUsers(context); - expect(actual).toEqual({ - notAdded: [], - notInvited: [], - notEmailed: [], - users: [], - errors: [], - }); - }); - it("Properly falls back to inviting users", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [{ orgType: "world" }], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: undefined as unknown as IAddOrInviteEmail, - world: [{ orgType: "world" }], - org: [], - community: [], - partnered: [], - collaborationCoordinator: [], - }; - const processInviteUsersSpy = spyOn( - processInviteUsersModule, - "processInviteUsers" - ).and.callFake(() => { - Promise.resolve(); - }); - - const actual = await addOrInviteWorldUsers(context); - expect(processInviteUsersSpy).toHaveBeenCalled(); - }); - }); - - describe("handleNoUsers: ", () => { - it("returns expected empty addOrInviteReponse object", async () => { - const result = await handleNoUsers(); - expect(result.notAdded.length).toBe(0); - expect(result.notEmailed.length).toBe(0); - expect(result.notInvited.length).toBe(0); - expect(result.users.length).toBe(0); - expect(result.errors.length).toBe(0); - }); - }); -}); diff --git a/packages/common/test/groups/_internal/processAutoAddUsers.test.ts b/packages/common/test/groups/_internal/processAutoAddUsers.test.ts deleted file mode 100644 index a8678543414..00000000000 --- a/packages/common/test/groups/_internal/processAutoAddUsers.test.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { ArcGISRequestError } from "@esri/arcgis-rest-request"; -import { processAutoAddUsers } from "../../../src/groups/_internal/processAutoAddUsers"; -import { - IAddOrInviteContext, - IAddOrInviteEmail, - IAddOrInviteResponse, -} from "../../../src/groups/types"; -import { MOCK_AUTH } from "../../mocks/mock-auth"; -import * as autoAddUsersAsAdminsModule from "../../../src/groups/_internal/autoAddUsersAsAdmins"; -import * as processEmailUsersModule from "../../../src/groups/_internal/processEmailUsers"; -import * as autoAddUsersModule from "../../../src/groups/autoAddUsers"; - -describe("processAutoAddUsers: ", () => { - let autoAddUsersSpy: jasmine.Spy; - let autoAddUsersAsAdminsSpy: jasmine.Spy; - let processEmailUsersSpy: jasmine.Spy; - - beforeEach(() => { - autoAddUsersSpy = spyOn(autoAddUsersModule, "autoAddUsers"); - autoAddUsersAsAdminsSpy = spyOn( - autoAddUsersAsAdminsModule, - "autoAddUsersAsAdmins" - ); - processEmailUsersSpy = spyOn(processEmailUsersModule, "processEmailUsers"); - }); - afterEach(() => { - autoAddUsersSpy.calls.reset(); - autoAddUsersAsAdminsSpy.calls.reset(); - processEmailUsersSpy.calls.reset(); - }); - - it("flows through happy path as admin without email", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [ - { orgType: "org", username: "bob" }, - { orgType: "org", username: "frank" }, - ], - canAutoAddUser: false, - addUserAsGroupAdmin: true, - email: undefined as unknown as IAddOrInviteEmail, - world: [], - org: [ - { orgType: "org", username: "bob" }, - { orgType: "org", username: "frank" }, - ], - community: [], - partnered: [], - collaborationCoordinator: [], - }; - autoAddUsersSpy.and.callFake(() => Promise.resolve({ success: true })); - autoAddUsersAsAdminsSpy.and.callFake(() => - Promise.resolve({ success: true }) - ); - const result = await processAutoAddUsers(context, "org"); - expect(autoAddUsersAsAdminsSpy).toHaveBeenCalled(); - expect(autoAddUsersSpy).not.toHaveBeenCalled(); - expect(result.users.length).toEqual(2); - expect(result.notAdded.length).toEqual(0); - expect(result.errors.length).toEqual(0); - expect(result.notEmailed.length).toEqual(0); - }); - it("flows through happy path not as admin without email", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [ - { orgType: "org", username: "bob" }, - { orgType: "org", username: "frank" }, - ], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: undefined as unknown as IAddOrInviteEmail, - world: [], - org: [ - { orgType: "org", username: "bob" }, - { orgType: "org", username: "frank" }, - ], - community: [], - partnered: [], - collaborationCoordinator: [], - }; - autoAddUsersSpy.and.callFake(() => Promise.resolve({ success: true })); - autoAddUsersAsAdminsSpy.and.callFake(() => - Promise.resolve({ success: true }) - ); - const result = await processAutoAddUsers(context, "org"); - expect(autoAddUsersAsAdminsSpy).not.toHaveBeenCalled(); - expect(autoAddUsersSpy).toHaveBeenCalled(); - expect(result.users.length).toEqual(2); - expect(result.notAdded.length).toEqual(0); - expect(result.errors.length).toEqual(0); - expect(result.notEmailed.length).toEqual(0); - }); - it("flows through happy path not as admin with email", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [ - { orgType: "org", username: "bob" }, - { orgType: "org", username: "frank" }, - ], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: { message: {}, auth: MOCK_AUTH }, - world: [], - org: [ - { orgType: "org", username: "bob" }, - { orgType: "org", username: "frank" }, - ], - community: [], - partnered: [], - collaborationCoordinator: [], - }; - autoAddUsersSpy.and.callFake(() => Promise.resolve({ success: true })); - autoAddUsersAsAdminsSpy.and.callFake(() => - Promise.resolve({ success: true }) - ); - processEmailUsersSpy.and.callFake(() => { - const responseObj: IAddOrInviteResponse = { - users: [], - notEmailed: [], - errors: [], - notInvited: [], - notAdded: [], - }; - return Promise.resolve(responseObj); - }); - const result = await processAutoAddUsers(context, "org", true); - expect(autoAddUsersAsAdminsSpy).not.toHaveBeenCalled(); - expect(autoAddUsersSpy).toHaveBeenCalled(); - expect(processEmailUsersSpy).toHaveBeenCalled(); - expect(result.users.length).toEqual(2); - expect(result.notAdded.length).toEqual(0); - expect(result.errors.length).toEqual(0); - expect(result.notEmailed.length).toEqual(0); - }); - it("handles errors", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [ - { orgType: "org", username: "bob" }, - { orgType: "org", username: "frank" }, - ], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: { message: {}, auth: MOCK_AUTH }, - world: [], - org: [ - { orgType: "org", username: "bob" }, - { orgType: "org", username: "frank" }, - ], - community: [], - partnered: [], - collaborationCoordinator: [], - }; - const error = new ArcGISRequestError("Email not sent"); - autoAddUsersSpy.and.callFake(() => - Promise.resolve({ notAdded: ["bob", "frank"], errors: [error] }) - ); - autoAddUsersAsAdminsSpy.and.callFake(() => - Promise.resolve({ success: true }) - ); - processEmailUsersSpy.and.callFake(() => { - const responseObj: IAddOrInviteResponse = { - users: ["bob", "frank"], - notEmailed: ["bob", "frank"], - errors: [error, error], - notInvited: [], - notAdded: [], - }; - return Promise.resolve(responseObj); - }); - const result = await processAutoAddUsers(context, "org", true); - expect(autoAddUsersAsAdminsSpy).not.toHaveBeenCalled(); - expect(autoAddUsersSpy).toHaveBeenCalled(); - expect(processEmailUsersSpy).toHaveBeenCalled(); - expect(result.users.length).toEqual(2); - expect(result.notAdded.length).toEqual(2); - expect(result.errors.length).toEqual(3); - expect(result.notEmailed.length).toEqual(2); - }); -}); diff --git a/packages/common/test/groups/_internal/processEmailUsers.test.ts b/packages/common/test/groups/_internal/processEmailUsers.test.ts deleted file mode 100644 index 8c788c3f3d8..00000000000 --- a/packages/common/test/groups/_internal/processEmailUsers.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { ArcGISRequestError } from "@esri/arcgis-rest-request"; -import { processEmailUsers } from "../../../src/groups/_internal/processEmailUsers"; -import { IAddOrInviteContext } from "../../../src/groups/types"; -import { MOCK_AUTH } from "../../mocks/mock-auth"; -import * as emailOrgUsersModule from "../../../src/groups/emailOrgUsers"; - -describe("processEmailUsers: ", () => { - let emailOrgUsersSpy: jasmine.Spy; - - beforeEach(() => { - emailOrgUsersSpy = spyOn(emailOrgUsersModule, "emailOrgUsers"); - }); - afterEach(() => { - emailOrgUsersSpy.calls.reset(); - }); - it("flows through happy path...", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [ - { orgType: "community", username: "bob" }, - { orgType: "community", username: "frank" }, - ], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: { message: {}, auth: MOCK_AUTH }, - world: [], - org: [], - community: [ - { orgType: "community", username: "bob" }, - { orgType: "community", username: "frank" }, - ], - partnered: [], - collaborationCoordinator: [], - }; - emailOrgUsersSpy.and.callFake(() => Promise.resolve({ success: true })); - const result = await processEmailUsers(context); - expect(emailOrgUsersSpy).toHaveBeenCalled(); - expect(emailOrgUsersSpy.calls.count()).toEqual(2); - expect(result.users.length).toEqual(2); - expect(result.notEmailed.length).toEqual(0); - expect(result.errors.length).toEqual(0); - }); - it("handles matters when success is false", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [ - { orgType: "community", username: "bob" }, - { orgType: "community", username: "frank" }, - ], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: { message: {}, auth: MOCK_AUTH }, - world: [], - org: [], - community: [ - { orgType: "community", username: "bob" }, - { orgType: "community", username: "frank" }, - ], - partnered: [], - collaborationCoordinator: [], - }; - emailOrgUsersSpy.and.callFake(() => Promise.resolve({ success: false })); - const result = await processEmailUsers(context); - expect(emailOrgUsersSpy).toHaveBeenCalled(); - expect(emailOrgUsersSpy.calls.count()).toEqual(2); - expect(result.users.length).toEqual(2); - expect(result.notEmailed.length).toEqual(2); - expect(result.errors.length).toEqual(0); - }); - it("handles matters when errors are returned", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [ - { orgType: "community", username: "bob" }, - { orgType: "community", username: "frank" }, - ], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: { message: {}, auth: MOCK_AUTH }, - world: [], - org: [], - community: [ - { orgType: "community", username: "bob" }, - { orgType: "community", username: "frank" }, - ], - partnered: [], - collaborationCoordinator: [], - }; - const error = new ArcGISRequestError("Email not sent"); - emailOrgUsersSpy.and.callFake(() => - Promise.resolve({ success: false, errors: [error] }) - ); - const result = await processEmailUsers(context); - expect(emailOrgUsersSpy).toHaveBeenCalled(); - expect(emailOrgUsersSpy.calls.count()).toEqual(2); - expect(result.users.length).toEqual(2, "two people in users array"); - expect(result.notEmailed.length).toEqual(2, "two people not emailed"); - expect(result.errors.length).toEqual(2, "two errors returned"); - }); -}); diff --git a/packages/common/test/groups/_internal/processInviteUsers.test.ts b/packages/common/test/groups/_internal/processInviteUsers.test.ts deleted file mode 100644 index 26e2455da4f..00000000000 --- a/packages/common/test/groups/_internal/processInviteUsers.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { - IAddOrInviteContext, - IAddOrInviteEmail, -} from "../../../src/groups/types"; -import { MOCK_AUTH } from "../../mocks/mock-auth"; -import * as inviteUsersModule from "../../../src/groups/inviteUsers"; -import { processInviteUsers } from "../../../src/groups/_internal/processInviteUsers"; -import { ArcGISRequestError } from "@esri/arcgis-rest-request"; - -describe("processInviteUsers: ", () => { - let inviteUsersSpy: jasmine.Spy; - - beforeEach(() => { - inviteUsersSpy = spyOn(inviteUsersModule, "inviteUsers"); - }); - afterEach(() => { - inviteUsersSpy.calls.reset(); - }); - it("flows through happy path...", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [ - { orgType: "world", username: "bob" }, - { orgType: "world", username: "frank" }, - { orgType: "community", username: "bob" }, - { orgType: "community", username: "frank" }, - ], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: undefined as unknown as IAddOrInviteEmail, - world: [ - { orgType: "world", username: "bob" }, - { orgType: "world", username: "frank" }, - ], - org: [], - community: [ - { orgType: "community", username: "bob" }, - { orgType: "community", username: "frank" }, - ], - partnered: [], - collaborationCoordinator: [], - }; - inviteUsersSpy.and.callFake(() => Promise.resolve({ success: true })); - const result = await processInviteUsers(context, "community"); - expect(inviteUsersSpy).toHaveBeenCalled(); - expect(inviteUsersSpy.calls.count()).toEqual(2); - expect(inviteUsersSpy.calls.argsFor(0)[4]).toEqual("group_member"); - expect(inviteUsersSpy.calls.argsFor(1)[4]).toEqual("group_member"); - expect(result.users.length).toEqual(2); - expect(result.notInvited.length).toEqual(0); - expect(result.errors.length).toEqual(0); - }); - it("flows through happy path...when inviting as admin", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [ - { orgType: "world", username: "bob" }, - { orgType: "world", username: "frank" }, - { orgType: "community", username: "bob" }, - { orgType: "community", username: "frank" }, - ], - canAutoAddUser: false, - addUserAsGroupAdmin: true, - email: undefined as unknown as IAddOrInviteEmail, - world: [ - { orgType: "world", username: "bob" }, - { orgType: "world", username: "frank" }, - ], - org: [], - community: [ - { orgType: "community", username: "bob" }, - { orgType: "community", username: "frank" }, - ], - partnered: [], - collaborationCoordinator: [], - }; - inviteUsersSpy.and.callFake(() => Promise.resolve({ success: true })); - const result = await processInviteUsers(context, "community"); - expect(inviteUsersSpy).toHaveBeenCalled(); - expect(inviteUsersSpy.calls.count()).toEqual(2); - expect(inviteUsersSpy.calls.argsFor(0)[4]).toEqual("group_admin"); - expect(inviteUsersSpy.calls.argsFor(1)[4]).toEqual("group_admin"); - expect(result.users.length).toEqual(2); - expect(result.notInvited.length).toEqual(0); - expect(result.errors.length).toEqual(0); - }); - it("handles matters when success is false.", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [ - { orgType: "world", username: "bob" }, - { orgType: "world", username: "frank" }, - { orgType: "community", username: "bob" }, - { orgType: "community", username: "frank" }, - ], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: undefined as unknown as IAddOrInviteEmail, - world: [ - { orgType: "world", username: "bob" }, - { orgType: "world", username: "frank" }, - ], - org: [], - community: [ - { orgType: "community", username: "bob" }, - { orgType: "community", username: "frank" }, - ], - partnered: [], - collaborationCoordinator: [], - }; - inviteUsersSpy.and.callFake(() => Promise.resolve({ success: false })); - const result = await processInviteUsers(context, "community"); - expect(inviteUsersSpy).toHaveBeenCalled(); - expect(inviteUsersSpy.calls.count()).toEqual(2); - expect(result.users.length).toEqual(2); - expect(result.notInvited.length).toEqual(2); - expect(result.errors.length).toEqual(0); - }); - it("handles matters when errors are returned.", async () => { - const context: IAddOrInviteContext = { - groupId: "abc123", - primaryRO: MOCK_AUTH, - allUsers: [ - { orgType: "community", username: "bob" }, - { orgType: "community", username: "frank" }, - { orgType: "world", username: "bob" }, - { orgType: "world", username: "frank" }, - ], - canAutoAddUser: false, - addUserAsGroupAdmin: false, - email: undefined as unknown as IAddOrInviteEmail, - world: [ - { orgType: "world", username: "bob" }, - { orgType: "world", username: "frank" }, - ], - org: [], - community: [ - { orgType: "community", username: "bob" }, - { orgType: "community", username: "frank" }, - ], - partnered: [], - collaborationCoordinator: [], - }; - const error = new ArcGISRequestError("Email not sent"); - inviteUsersSpy.and.callFake(() => - Promise.resolve({ success: false, errors: [error] }) - ); - const result = await processInviteUsers(context, "community"); - expect(inviteUsersSpy).toHaveBeenCalled(); - expect(inviteUsersSpy.calls.count()).toEqual(2); - expect(result.users.length).toEqual(2); - expect(result.notInvited.length).toEqual(2); - expect(result.errors.length).toEqual(2); - }); -}); diff --git a/packages/common/test/groups/addGroupMembers.test.ts b/packages/common/test/groups/addGroupMembers.test.ts new file mode 100644 index 00000000000..17da80eb3cc --- /dev/null +++ b/packages/common/test/groups/addGroupMembers.test.ts @@ -0,0 +1,83 @@ +import { IUser } from "@esri/arcgis-rest-types"; +import { addGroupMembers } from "../../src/groups/addGroupMembers"; +import * as autoAddUsersModule from "../../src/groups/autoAddUsers"; +import * as inviteUsersModule from "../../src/groups/inviteUsers"; +import { MOCK_AUTH } from "../mocks/mock-auth"; + +describe("addGroupMembers: ", () => { + let addGroupUsersSpy: jasmine.Spy; + let inviteGroupUsersSpy: jasmine.Spy; + + beforeEach(() => { + addGroupUsersSpy = spyOn(autoAddUsersModule, "autoAddUsers"); + inviteGroupUsersSpy = spyOn(inviteUsersModule, "inviteUsers"); + }); + + afterEach(() => { + addGroupUsersSpy.calls.reset(); + inviteGroupUsersSpy.calls.reset(); + }); + + it("Adds members to group when autoAdd is true", async () => { + addGroupUsersSpy.and.callFake(() => Promise.resolve({ success: true })); + const result = await addGroupMembers( + "abc123", + ["bob", "frank"], + { + authentication: MOCK_AUTH, + }, + true + ); + expect(addGroupUsersSpy).toHaveBeenCalled(); + expect(inviteGroupUsersSpy).not.toHaveBeenCalled(); + expect(result.added.length).toEqual(2); + expect(result.notAdded.length).toEqual(0); + expect(result.responses.length).toEqual(2); + }); + + it("Adds members to group when autoAdd is true and falls back to invite if add fails", async () => { + addGroupUsersSpy.and.callFake((groupId: string, users: IUser[]) => { + if (users[0].username === "bob") { + return Promise.resolve({ notAdded: ["bob"] }); + } + return Promise.resolve({ success: true }); + }); + inviteGroupUsersSpy.and.callFake(() => Promise.resolve({ success: true })); + const result = await addGroupMembers( + "abc123", + ["bob", "frank"], + { + authentication: MOCK_AUTH, + }, + true + ); + expect(addGroupUsersSpy).toHaveBeenCalled(); + expect(inviteGroupUsersSpy).toHaveBeenCalled(); + expect(result.added.length).toEqual(1); + expect(result.notAdded.length).toEqual(1); + expect(result.invited.length).toEqual(1); + expect(result.responses.length).toEqual(2); + }); + + it("Invites members to group when autoAdd is false", async () => { + inviteGroupUsersSpy.and.callFake((groupId: string, users: IUser[]) => { + if (users[0].username === "bob") { + return Promise.resolve({ notAdded: ["bob"] }); + } + return Promise.resolve({ success: true }); + }); + const result = await addGroupMembers( + "abc123", + ["bob", "frank"], + { + authentication: MOCK_AUTH, + }, + false + ); + expect(addGroupUsersSpy).not.toHaveBeenCalled(); + expect(inviteGroupUsersSpy).toHaveBeenCalled(); + expect(result.invited.length).toEqual(1); + expect(result.notInvited.length).toEqual(1); + expect(result.responses.length).toEqual(2); + }); +}); diff --git a/packages/common/test/groups/addOrInviteUsersToGroup.test.ts b/packages/common/test/groups/addOrInviteUsersToGroup.test.ts deleted file mode 100644 index 94f9639c05f..00000000000 --- a/packages/common/test/groups/addOrInviteUsersToGroup.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { ArcGISRequestError } from "@esri/arcgis-rest-request"; -import { - IAddOrInviteEmail, - IAddOrInviteResponse, - IUserWithOrgType, -} from "../../src/groups/types"; -import { MOCK_AUTH } from "../mocks/mock-auth"; -import { addOrInviteUsersToGroup } from "../../src/groups/addOrInviteUsersToGroup"; -import * as utilsModule from "../../src/groups/_internal/AddOrInviteUsersToGroupUtils"; - -describe("addOrInviteUsersToGroup: ", () => { - let addOrInviteCommunityUsersSpy: jasmine.Spy; - let addOrInviteOrgUsersSpy: jasmine.Spy; - let addOrInviteWorldUsersSpy: jasmine.Spy; - - beforeEach(() => { - addOrInviteCommunityUsersSpy = spyOn( - utilsModule, - "addOrInviteCommunityUsers" - ); - addOrInviteOrgUsersSpy = spyOn(utilsModule, "addOrInviteOrgUsers"); - addOrInviteWorldUsersSpy = spyOn(utilsModule, "addOrInviteWorldUsers"); - }); - - afterEach(() => { - addOrInviteCommunityUsersSpy.calls.reset(); - addOrInviteOrgUsersSpy.calls.reset(); - addOrInviteWorldUsersSpy.calls.reset(); - }); - - it("all works...", async () => { - const users: IUserWithOrgType[] = [ - { orgType: "world", username: "bob" }, - { orgType: "world", username: "bobb" }, - { orgType: "world", username: "bobbb" }, - { orgType: "org", username: "frank" }, - { orgType: "org", username: "frankk" }, - { orgType: "community", username: "dobby" }, - ]; - const error = new ArcGISRequestError("error in addOrInviteUsersToGroup"); - addOrInviteCommunityUsersSpy.and.callFake(() => { - const result: IAddOrInviteResponse = { - users: [], - notInvited: [], - notAdded: ["dobby"], - notEmailed: ["dobby"], - errors: [error, error], - }; - return Promise.resolve(result); - }); - addOrInviteOrgUsersSpy.and.callFake(() => { - const result: IAddOrInviteResponse = { - users: [], - notInvited: [], - notAdded: ["frank"], - notEmailed: [], - errors: [], - }; - return Promise.resolve(result); - }); - addOrInviteWorldUsersSpy.and.callFake(() => { - const result: IAddOrInviteResponse = { - users: [], - notInvited: ["bob", "bobb"], - notAdded: [], - notEmailed: [], - errors: [error, error], - }; - return Promise.resolve(result); - }); - const response = await addOrInviteUsersToGroup( - "abc123", - users, - MOCK_AUTH, - false, - false, - undefined as unknown as IAddOrInviteEmail - ); - expect(addOrInviteCommunityUsersSpy).toHaveBeenCalled(); - expect(addOrInviteOrgUsersSpy).toHaveBeenCalled(); - expect(addOrInviteWorldUsersSpy).toHaveBeenCalled(); - expect(response.notAdded.length).toEqual(2); - expect(response.notInvited.length).toEqual(2); - expect(response.notEmailed.length).toEqual(1); - expect(response.errors.length).toEqual(4); - expect(response.groupId).toEqual("abc123"); - }); -}); diff --git a/packages/common/test/groups/addOrInviteUsersToGroups.test.ts b/packages/common/test/groups/addOrInviteUsersToGroups.test.ts deleted file mode 100644 index 9ab6020a13d..00000000000 --- a/packages/common/test/groups/addOrInviteUsersToGroups.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { ArcGISRequestError } from "@esri/arcgis-rest-request"; -import { - IAddOrInviteToGroupResult, - IUserWithOrgType, -} from "../../src/groups/types"; -import { MOCK_AUTH } from "../mocks/mock-auth"; -import * as addOrInviteUsersToGroupModule from "../../src/groups/addOrInviteUsersToGroup"; -import { addOrInviteUsersToGroups } from "../../src/groups/addOrInviteUsersToGroups"; - -describe("addOrInviteUsersToGroups: ", () => { - it("all works...", async () => { - const users: IUserWithOrgType[] = [ - { orgType: "world", username: "bob" }, - { orgType: "world", username: "bobb" }, - { orgType: "world", username: "bobbb" }, - { orgType: "org", username: "frank" }, - { orgType: "org", username: "frankk" }, - { orgType: "community", username: "dobby" }, - { orgType: "partnered", username: "randy" }, - { orgType: "partnered", username: "jupe" }, - { orgType: "collaborationCoordinator", username: "freddy" }, - ]; - const error = new ArcGISRequestError("error in addOrInviteUsersToGroups"); - const addOrInviteUsersToGroupSpy = spyOn( - addOrInviteUsersToGroupModule, - "addOrInviteUsersToGroup" - ).and.callFake(() => { - const response: IAddOrInviteToGroupResult = { - groupId: "abc123", - notAdded: ["dobby", "frank"], - notInvited: ["bob", "bobb"], - notEmailed: ["dobby"], - errors: [error, error, error, error], - community: { - users: [], - notInvited: [], - notAdded: ["dobby"], - notEmailed: ["dobby"], - errors: [error, error], - }, - org: { - users: [], - notInvited: [], - notAdded: ["frank"], - notEmailed: [], - errors: [], - }, - world: { - users: [], - notInvited: ["bob", "bobb"], - notAdded: [], - notEmailed: [], - errors: [error, error], - }, - partnered: { - users: ["randy", "jupe"], - notInvited: [], - notAdded: [], - notEmailed: [], - errors: [], - }, - collaborationCoordinator: { - users: ["freddy"], - notInvited: [], - notAdded: [], - notEmailed: [], - errors: [], - }, - }; - return Promise.resolve(response); - }); - const result = await addOrInviteUsersToGroups( - ["abc123", "def456", "ghi789"], - users, - MOCK_AUTH - ); - expect(addOrInviteUsersToGroupSpy).toHaveBeenCalled(); - expect(addOrInviteUsersToGroupSpy.calls.count()).toEqual(3); - expect(result.responses.length).toEqual(3); - expect(result.notAdded.length).toEqual(6); - expect(result.notInvited.length).toEqual(6); - expect(result.notEmailed.length).toEqual(3); - expect(result.errors.length).toEqual(12); - }); -});