diff --git a/i18n/en-US.properties b/i18n/en-US.properties index 6a4d3b5999..ab057cdb44 100644 --- a/i18n/en-US.properties +++ b/i18n/en-US.properties @@ -140,6 +140,8 @@ be.contentSharing.badRequestError = The request for this item was malformed. be.contentSharing.collaboratorsLoadingError = Could not retrieve collaborators for this item. # Message that appears when users cannot be retrieved in the ContentSharing Element. be.contentSharing.getContactsError = Could not retrieve contacts. +# Display text for a Group contact type +be.contentSharing.groupContactLabel = Group # Message that appears when the ContentSharing Element cannot be loaded. be.contentSharing.loadingError = Could not load shared link for this item. # Message that appears when the user cannot access the item for the ContentSharing Element. diff --git a/src/elements/content-sharing/SharingModal.js b/src/elements/content-sharing/SharingModal.js index 64e7e85341..d035828ab1 100644 --- a/src/elements/content-sharing/SharingModal.js +++ b/src/elements/content-sharing/SharingModal.js @@ -39,6 +39,7 @@ import type { import type { ContentSharingItemAPIResponse, ContentSharingSharedLinkType, + GetContactByEmailFnType, GetContactsFnType, GetContactsByEmailFnType, SendInvitesFnType, @@ -80,18 +81,16 @@ function SharingModal({ const [collaboratorsList, setCollaboratorsList] = React.useState(null); const [onAddLink, setOnAddLink] = React.useState(null); const [onRemoveLink, setOnRemoveLink] = React.useState(null); - const [ - changeSharedLinkAccessLevel, - setChangeSharedLinkAccessLevel, - ] = React.useState(null); - const [ - changeSharedLinkPermissionLevel, - setChangeSharedLinkPermissionLevel, - ] = React.useState(null); + const [changeSharedLinkAccessLevel, setChangeSharedLinkAccessLevel] = + React.useState(null); + const [changeSharedLinkPermissionLevel, setChangeSharedLinkPermissionLevel] = + React.useState(null); const [onSubmitSettings, setOnSubmitSettings] = React.useState(null); const [currentView, setCurrentView] = React.useState(CONTENT_SHARING_VIEWS.UNIFIED_SHARE_MODAL); const [getContacts, setGetContacts] = React.useState(null); - const [getContactsByEmail, setGetContactsByEmail] = React.useState(null); + const [getContactsByEmail, setGetContactsByEmail] = React.useState< + null | GetContactsByEmailFnType | GetContactByEmailFnType, + >(null); const [sendInvites, setSendInvites] = React.useState(null); const [isLoading, setIsLoading] = React.useState(true); @@ -191,11 +190,15 @@ function SharingModal({ // Set the getContactsByEmail function. This call is not associated with a banner notification, // which is why it exists at this level and not in SharingNotification - const getContactsByEmailFn: GetContactsByEmailFnType | null = useContactsByEmail(api, itemID, { - transformUsers: data => convertUserContactsByEmailResponse(data), - }); + const getContactsByEmailFn: GetContactsByEmailFnType | GetContactByEmailFnType | null = useContactsByEmail( + api, + itemID, + { + transformUsers: data => convertUserContactsByEmailResponse(data), + }, + ); if (getContactsByEmailFn && !getContactsByEmail) { - setGetContactsByEmail((): GetContactsByEmailFnType => getContactsByEmailFn); + setGetContactsByEmail((): GetContactsByEmailFnType | GetContactByEmailFnType => getContactsByEmailFn); } // Display a notification if there is an error in retrieving initial data diff --git a/src/elements/content-sharing/__tests__/useContactsByEmail.test.js b/src/elements/content-sharing/__tests__/useContactsByEmail.test.js index e2b27c2837..10c5c5ff35 100644 --- a/src/elements/content-sharing/__tests__/useContactsByEmail.test.js +++ b/src/elements/content-sharing/__tests__/useContactsByEmail.test.js @@ -1,8 +1,6 @@ // @flow -import React, { act } from 'react'; -import { mount } from 'enzyme'; -import API from '../../../api'; +import { renderHook, act } from '@testing-library/react'; import useContactsByEmail from '../hooks/useContactsByEmail'; import { MOCK_CONTACTS_API_RESPONSE, @@ -12,34 +10,12 @@ import { const handleSuccess = jest.fn(); const handleError = jest.fn(); -const transformUsersSpy = jest.fn().mockReturnValue(MOCK_CONTACTS_BY_EMAIL_CONVERTED_RESPONSE); +const mockTransformUsers = jest.fn().mockReturnValue(MOCK_CONTACTS_BY_EMAIL_CONVERTED_RESPONSE); const createAPIMock = markerBasedUsersAPI => ({ getMarkerBasedUsersAPI: jest.fn().mockReturnValue(markerBasedUsersAPI), }); -function FakeComponent({ api, transformUsers }: { api: API, transformUsers: Function }) { - const [getContactsByEmail, setGetContactsByEmail] = React.useState(null); - - const updatedGetContactsByEmailFn = useContactsByEmail(api, MOCK_ITEM_ID, { - handleSuccess, - handleError, - transformUsers, - }); - - if (updatedGetContactsByEmailFn && !getContactsByEmail) { - setGetContactsByEmail(() => updatedGetContactsByEmailFn); - } - - return ( - getContactsByEmail && ( - - ) - ); -} - const MOCK_EMAIL = 'contentsharing@box.com'; describe('elements/content-sharing/hooks/useContactsByEmail', () => { @@ -47,21 +23,30 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => { let mockAPI; describe('with a successful API call', () => { - beforeAll(() => { + beforeEach(() => { getUsersInEnterprise = jest.fn().mockImplementation((itemID, getUsersInEnterpriseSuccess) => { return getUsersInEnterpriseSuccess(MOCK_CONTACTS_API_RESPONSE); }); mockAPI = createAPIMock({ getUsersInEnterprise }); }); - test('should set the value of getContactsByEmail() and retrieve contacts on invocation', () => { - let fakeComponent; - act(() => { - fakeComponent = mount(); - }); - fakeComponent.update(); + afterEach(() => { + jest.resetAllMocks(); + }); - const contacts = fakeComponent.find('button').invoke('onClick')({ emails: [MOCK_EMAIL] }); + test('should set the value of getContactsByEmail() and retrieve contacts on invocation', async () => { + const { result } = renderHook(() => + useContactsByEmail(mockAPI, MOCK_ITEM_ID, { + handleSuccess, + handleError, + transformUsers: mockTransformUsers, + }), + ); + + let contacts; + await act(async () => { + contacts = await result.current({ emails: [MOCK_EMAIL] }); + }); expect(getUsersInEnterprise).toHaveBeenCalledWith( MOCK_ITEM_ID, @@ -70,18 +55,22 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => { { filter_term: MOCK_EMAIL }, ); expect(handleSuccess).toHaveBeenCalledWith(MOCK_CONTACTS_API_RESPONSE); - expect(transformUsersSpy).toHaveBeenCalledWith(MOCK_CONTACTS_API_RESPONSE); - return expect(contacts).resolves.toEqual(MOCK_CONTACTS_BY_EMAIL_CONVERTED_RESPONSE); + expect(mockTransformUsers).toHaveBeenCalledWith(MOCK_CONTACTS_API_RESPONSE); + expect(contacts).toEqual(MOCK_CONTACTS_BY_EMAIL_CONVERTED_RESPONSE); }); - test('should return the entries from the API data if transformUsers() is not provided', () => { - let fakeComponent; - act(() => { - fakeComponent = mount(); - }); - fakeComponent.update(); + test('should return the entries from the API data if transformUsers() is not provided', async () => { + const { result } = renderHook(() => + useContactsByEmail(mockAPI, MOCK_ITEM_ID, { + handleSuccess, + handleError, + }), + ); - const contacts = fakeComponent.find('button').invoke('onClick')({ emails: [MOCK_EMAIL] }); + let contacts; + await act(async () => { + contacts = await result.current({ emails: [MOCK_EMAIL] }); + }); expect(getUsersInEnterprise).toHaveBeenCalledWith( MOCK_ITEM_ID, @@ -90,28 +79,33 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => { { filter_term: MOCK_EMAIL }, ); expect(handleSuccess).toHaveBeenCalledWith(MOCK_CONTACTS_API_RESPONSE); - expect(transformUsersSpy).not.toHaveBeenCalled(); - expect(contacts).resolves.toEqual(MOCK_CONTACTS_API_RESPONSE.entries); + expect(mockTransformUsers).not.toHaveBeenCalled(); + expect(contacts).toEqual(MOCK_CONTACTS_API_RESPONSE.entries); }); - test('should set the value of getContactsByEmail() to an empty object when no results are found', () => { + test('should set the value of getContactsByEmail() to an empty object when no results are found', async () => { const EMPTY_USERS = { entries: [] }; getUsersInEnterprise = jest.fn().mockImplementation((itemID, getUsersInEnterpriseSuccess) => { return getUsersInEnterpriseSuccess(EMPTY_USERS); }); mockAPI = createAPIMock({ getUsersInEnterprise }); - let fakeComponent; - act(() => { - fakeComponent = mount(); - }); - fakeComponent.update(); + const { result } = renderHook(() => + useContactsByEmail(mockAPI, MOCK_ITEM_ID, { + handleSuccess, + handleError, + transformUsers: mockTransformUsers, + }), + ); - const contacts = fakeComponent.find('button').invoke('onClick')({ emails: [MOCK_EMAIL] }); + let contacts; + await act(async () => { + contacts = await result.current({ emails: [MOCK_EMAIL] }); + }); expect(handleSuccess).toHaveBeenCalledWith(EMPTY_USERS); - expect(transformUsersSpy).not.toHaveBeenCalled(); - return expect(contacts).resolves.toEqual({}); + expect(mockTransformUsers).not.toHaveBeenCalled(); + expect(contacts).toEqual({}); }); test.each` @@ -120,23 +114,95 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => { ${{ content: 'sharing' }} | ${'an object, but does not have an emails key'} ${{ emails: 'contentsharing' }} | ${'an object with the emails key, but filterTerm.emails is not an array'} ${{ emails: [] }} | ${'an object with the emails key, but filterTerm.emails is an empty array'} - `('should return an empty object when filterTerm is $description', ({ filterTerm }) => { - let fakeComponent; - act(() => { - fakeComponent = mount(); - }); - fakeComponent.update(); + `('should return an empty object when filterTerm is $description', async ({ filterTerm }) => { + const { result } = renderHook(() => + useContactsByEmail(mockAPI, MOCK_ITEM_ID, { + handleSuccess, + handleError, + }), + ); - const contacts = fakeComponent.find('button').invoke('onClick')(filterTerm); + let contacts; + await act(async () => { + contacts = await result.current(filterTerm); + }); expect(getUsersInEnterprise).not.toHaveBeenCalled(); expect(handleError).not.toHaveBeenCalled(); - return expect(contacts).resolves.toEqual({}); + expect(contacts).toEqual({}); + }); + + test('should set the value of getContactsByEmail() and retrieve contacts when isContentSharingV2Enabled is true and email is provided', async () => { + const mockUser1 = MOCK_CONTACTS_API_RESPONSE.entries[0]; + const { id, login: email, name, type } = mockUser1; + const expectedTransformedResult = { + id, + email, + name, + type, + value: email, + }; + const MOCK_CONTACT_BY_EMAIL_API_RESPONSE = { entries: [mockUser1] }; + const mockTransformUsersV2 = jest.fn().mockReturnValue(expectedTransformedResult); + getUsersInEnterprise = jest.fn().mockImplementation((itemID, getUsersInEnterpriseSuccess) => { + return getUsersInEnterpriseSuccess(MOCK_CONTACT_BY_EMAIL_API_RESPONSE); + }); + mockAPI = createAPIMock({ getUsersInEnterprise }); + + const { result } = renderHook(() => + useContactsByEmail(mockAPI, MOCK_ITEM_ID, { + isContentSharingV2Enabled: true, + handleSuccess, + handleError, + transformUsers: mockTransformUsersV2, + }), + ); + + let contacts; + await act(async () => { + contacts = await result.current('contentopenwith@box.com'); + }); + + expect(getUsersInEnterprise).toHaveBeenCalledWith( + MOCK_ITEM_ID, + expect.anything(Function), + expect.anything(Function), + { filter_term: 'contentopenwith@box.com' }, + ); + expect(handleSuccess).toHaveBeenCalledWith(MOCK_CONTACT_BY_EMAIL_API_RESPONSE); + expect(mockTransformUsersV2).toHaveBeenCalledWith(MOCK_CONTACT_BY_EMAIL_API_RESPONSE); + expect(contacts).toEqual(expectedTransformedResult); + }); + + test('should set the value of getContactsByEmail() to an empty object when isContentSharingV2Enabled is true and email is not provided', async () => { + const EMPTY_USERS = { entries: [] }; + getUsersInEnterprise = jest.fn().mockImplementation((itemID, getUsersInEnterpriseSuccess) => { + return getUsersInEnterpriseSuccess(EMPTY_USERS); + }); + mockAPI = createAPIMock({ getUsersInEnterprise }); + + const { result } = renderHook(() => + useContactsByEmail(mockAPI, MOCK_ITEM_ID, { + isContentSharingV2Enabled: true, + handleSuccess, + handleError, + transformUsers: mockTransformUsers, + }), + ); + + let contacts; + await act(async () => { + contacts = await result.current({ MOCK_EMAIL }); + }); + + expect(handleSuccess).toHaveBeenCalledWith(EMPTY_USERS); + expect(mockTransformUsers).not.toHaveBeenCalled(); + expect(contacts).toEqual({}); }); }); describe('with a failed API call', () => { - beforeAll(() => { + beforeEach(() => { getUsersInEnterprise = jest .fn() .mockImplementation((itemID, getUsersInEnterpriseSuccess, getUsersInEnterpriseError) => { @@ -145,14 +211,25 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => { mockAPI = createAPIMock({ getUsersInEnterprise }); }); - test('should set the value of getContactsByEmail() and call handleError() when invoked', () => { - let fakeComponent; - act(() => { - fakeComponent = mount(); - }); - fakeComponent.update(); + afterEach(() => { + jest.resetAllMocks(); + }); + + test('should set the value of getContactsByEmail() and call handleError() when invoked', async () => { + const { result } = renderHook(() => + useContactsByEmail(mockAPI, MOCK_ITEM_ID, { + handleSuccess, + handleError, + transformUsers: mockTransformUsers, + }), + ); + + result.current({ emails: [MOCK_EMAIL] }); - const contacts = fakeComponent.find('button').invoke('onClick')({ emails: [MOCK_EMAIL] }); + // Wait a short time to ensure handleError is called + await act(async () => { + await new Promise(resolve => setTimeout(resolve, 100)); + }); expect(getUsersInEnterprise).toHaveBeenCalledWith( MOCK_ITEM_ID, @@ -161,7 +238,6 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => { { filter_term: MOCK_EMAIL }, ); expect(handleError).toHaveBeenCalled(); - expect(contacts).resolves.toBeFalsy(); }); }); }); diff --git a/src/elements/content-sharing/hooks/__tests__/useContactService.test.ts b/src/elements/content-sharing/hooks/__tests__/useContactService.test.ts index 9ce3382d4c..21827cd58b 100644 --- a/src/elements/content-sharing/hooks/__tests__/useContactService.test.ts +++ b/src/elements/content-sharing/hooks/__tests__/useContactService.test.ts @@ -1,41 +1,55 @@ import { renderHook } from '@testing-library/react'; -import { convertGroupContactsResponse, convertUserContactsResponse } from '../../utils'; +import { + convertGroupContactsResponse, + convertUserContactByEmailResponse, + convertUserContactsResponse, +} from '../../utils'; import { useContactService } from '../useContactService'; import useContacts from '../useContacts'; +import useContactsByEmail from '../useContactsByEmail'; jest.mock('../useContacts'); +jest.mock('../useContactsByEmail'); jest.mock('../../utils'); const mockApi = { getMarkerBasedUsersAPI: jest.fn(), getMarkerBasedGroupsAPI: jest.fn(), }; -const mockItemID = '123456789'; -const mockCurrentUserID = '123'; +const mockItemId = '123456789'; +const mockCurrentUserId = '123'; const mockGetContacts = jest.fn(); +const mockGetContactByEmail = jest.fn(); describe('elements/content-sharing/hooks/useContactService', () => { beforeEach(() => { (useContacts as jest.Mock).mockReturnValue(mockGetContacts); + (useContactsByEmail as jest.Mock).mockReturnValue(mockGetContactByEmail); (convertGroupContactsResponse as jest.Mock).mockReturnValue([]); (convertUserContactsResponse as jest.Mock).mockReturnValue([]); + (convertUserContactByEmailResponse as jest.Mock).mockReturnValue([]); }); afterEach(() => { jest.clearAllMocks(); }); - test('should return contactService with getContacts function', () => { - const { result } = renderHook(() => useContactService(mockApi, mockItemID, mockCurrentUserID)); + test('should return contactService with getContactByEmail and getContacts functions', () => { + const { result } = renderHook(() => useContactService(mockApi, mockItemId, mockCurrentUserId)); - expect(useContacts).toHaveBeenCalledWith(mockApi, mockItemID, { - currentUserId: mockCurrentUserID, + expect(useContacts).toHaveBeenCalledWith(mockApi, mockItemId, { + currentUserId: mockCurrentUserId, isContentSharingV2Enabled: true, transformUsers: expect.any(Function), transformGroups: expect.any(Function), }); + expect(useContactsByEmail).toHaveBeenCalledWith(mockApi, mockItemId, { + isContentSharingV2Enabled: true, + transformUsers: expect.any(Function), + }); expect(result.current.contactService).toEqual({ + getContactByEmail: mockGetContactByEmail, getContacts: mockGetContacts, }); }); @@ -43,13 +57,16 @@ describe('elements/content-sharing/hooks/useContactService', () => { test('should pass transform functions that call correct conversion functions with params', () => { const mockTransformedUsers = [{ id: 'user1', email: 'user1@test.com' }]; const mockTransformedGroups = [{ id: 'group1', name: 'Test Group' }]; + const mockTransformedContactByEmail = [{ id: 'user2', email: 'user2@test.com' }]; const mockUserData = { entries: mockTransformedUsers }; const mockGroupData = { entries: mockTransformedGroups }; + const mockContactByEmailData = { entries: mockTransformedContactByEmail }; (convertUserContactsResponse as jest.Mock).mockReturnValue(mockTransformedUsers); (convertGroupContactsResponse as jest.Mock).mockReturnValue(mockTransformedGroups); + (convertUserContactByEmailResponse as jest.Mock).mockReturnValue(mockTransformedContactByEmail); - renderHook(() => useContactService(mockApi, mockItemID, mockCurrentUserID)); + renderHook(() => useContactService(mockApi, mockItemId, mockCurrentUserId)); // Get the transform functions that were passed to useContacts const transformUsersFn = useContacts.mock.calls[0][2].transformUsers; @@ -57,9 +74,15 @@ describe('elements/content-sharing/hooks/useContactService', () => { const resultUsers = transformUsersFn(mockUserData); const resultGroups = transformGroupsFn(mockGroupData); - expect(convertUserContactsResponse as jest.Mock).toHaveBeenCalledWith(mockUserData, mockCurrentUserID); - expect(convertGroupContactsResponse as jest.Mock).toHaveBeenCalledWith(mockGroupData); + expect(convertUserContactsResponse as jest.Mock).toHaveBeenCalledWith(mockUserData, mockCurrentUserId); + expect(convertGroupContactsResponse as jest.Mock).toHaveBeenCalledWith(mockGroupData, 'Group'); expect(resultUsers).toBe(mockTransformedUsers); expect(resultGroups).toBe(mockTransformedGroups); + + // Get the transform function that was passed to useContactsByEmail + const transformContactByEmailFn = useContactsByEmail.mock.calls[0][2].transformUsers; + const resultContactByEmail = transformContactByEmailFn(mockContactByEmailData); + expect(convertUserContactByEmailResponse as jest.Mock).toHaveBeenCalledWith(mockContactByEmailData); + expect(resultContactByEmail).toBe(mockTransformedContactByEmail); }); }); diff --git a/src/elements/content-sharing/hooks/useContactService.ts b/src/elements/content-sharing/hooks/useContactService.ts index 589c0547d8..c48dbc937c 100644 --- a/src/elements/content-sharing/hooks/useContactService.ts +++ b/src/elements/content-sharing/hooks/useContactService.ts @@ -1,13 +1,25 @@ -import { convertGroupContactsResponse, convertUserContactsResponse } from '../utils'; +import { useIntl } from 'react-intl'; + +import { convertGroupContactsResponse, convertUserContactByEmailResponse, convertUserContactsResponse } from '../utils'; +import useContactsByEmail from './useContactsByEmail'; import useContacts from './useContacts'; +import messages from '../messages'; + export const useContactService = (api, itemId, currentUserId) => { + const { formatMessage } = useIntl(); + const getContacts = useContacts(api, itemId, { currentUserId, isContentSharingV2Enabled: true, transformUsers: data => convertUserContactsResponse(data, currentUserId), - transformGroups: data => convertGroupContactsResponse(data), + transformGroups: data => convertGroupContactsResponse(data, formatMessage(messages.groupContactLabel)), + }); + + const getContactByEmail = useContactsByEmail(api, itemId, { + isContentSharingV2Enabled: true, + transformUsers: data => convertUserContactByEmailResponse(data), }); - return { contactService: { getContacts } }; + return { contactService: { getContactByEmail, getContacts } }; }; diff --git a/src/elements/content-sharing/hooks/useContactsByEmail.js b/src/elements/content-sharing/hooks/useContactsByEmail.js index d45c0eea36..90eacc08f9 100644 --- a/src/elements/content-sharing/hooks/useContactsByEmail.js +++ b/src/elements/content-sharing/hooks/useContactsByEmail.js @@ -4,7 +4,12 @@ import * as React from 'react'; import noop from 'lodash/noop'; import API from '../../../api'; import type { UserCollection, UserMini } from '../../../common/types/core'; -import type { ContactByEmailObject, ContentSharingHooksOptions, GetContactsByEmailFnType } from '../types'; +import type { + ContactByEmailObject, + ContentSharingHooksOptions, + GetContactByEmailFnType, + GetContactsByEmailFnType, +} from '../types'; /** * Generate the getContactsByEmail() function, which is used for looking up contacts added to the collaborators field in the USM. @@ -12,15 +17,17 @@ import type { ContactByEmailObject, ContentSharingHooksOptions, GetContactsByEma * @param {API} api * @param {string} itemID * @param {ContentSharingHooksOptions} options - * @returns {GetContactsByEmailFnType | null} + * @returns {GetContactsByEmailFnType | GetContactByEmailFnType | null} */ function useContactsByEmail( api: API, itemID: string, options: ContentSharingHooksOptions, -): GetContactsByEmailFnType | null { - const [getContactsByEmail, setGetContactsByEmail] = React.useState(null); - const { handleSuccess = noop, handleError = noop, transformUsers } = options; +): GetContactsByEmailFnType | GetContactByEmailFnType | null { + const [getContactsByEmail, setGetContactsByEmail] = React.useState< + null | GetContactsByEmailFnType | GetContactByEmailFnType, + >(null); + const { handleSuccess = noop, handleError = noop, isContentSharingV2Enabled, transformUsers } = options; React.useEffect(() => { if (getContactsByEmail) return; @@ -38,25 +45,44 @@ function useContactsByEmail( return resolve({}); }; - const updatedGetContactsByEmailFn: GetContactsByEmailFnType = () => (filterTerm: { - [emails: string]: string, - }) => { - if (!filterTerm || !Array.isArray(filterTerm.emails) || !filterTerm.emails.length) { - return Promise.resolve({}); - } - const parsedFilterTerm = filterTerm.emails[0]; + if (isContentSharingV2Enabled) { + const getContactsByEmailV2: GetContactByEmailFnType = () => email => { + if (!email) { + return Promise.resolve({}); + } - return new Promise((resolve: (result: ContactByEmailObject | Array) => void) => { - api.getMarkerBasedUsersAPI(false).getUsersInEnterprise( - itemID, - (response: UserCollection) => resolveAPICall(resolve, response, transformUsers), - handleError, - { filter_term: parsedFilterTerm }, - ); - }); - }; - setGetContactsByEmail(updatedGetContactsByEmailFn); - }, [api, getContactsByEmail, handleError, handleSuccess, itemID, transformUsers]); + return new Promise(resolve => { + api.getMarkerBasedUsersAPI(false).getUsersInEnterprise( + itemID, + response => resolveAPICall(resolve, response, transformUsers), + handleError, + { filter_term: email }, + ); + }); + }; + + setGetContactsByEmail(getContactsByEmailV2); + } else { + const updatedGetContactsByEmailFn: GetContactsByEmailFnType = + () => (filterTerm: { [emails: string]: string }) => { + if (!filterTerm || !Array.isArray(filterTerm.emails) || !filterTerm.emails.length) { + return Promise.resolve({}); + } + const parsedFilterTerm = filterTerm.emails[0]; + + return new Promise((resolve: (result: ContactByEmailObject | Array) => void) => { + api.getMarkerBasedUsersAPI(false).getUsersInEnterprise( + itemID, + (response: UserCollection) => resolveAPICall(resolve, response, transformUsers), + handleError, + { filter_term: parsedFilterTerm }, + ); + }); + }; + + setGetContactsByEmail(updatedGetContactsByEmailFn); + } + }, [api, getContactsByEmail, handleError, handleSuccess, isContentSharingV2Enabled, itemID, transformUsers]); return getContactsByEmail; } diff --git a/src/elements/content-sharing/messages.js b/src/elements/content-sharing/messages.js index 1c9b8ddd3f..4e2d5a1273 100644 --- a/src/elements/content-sharing/messages.js +++ b/src/elements/content-sharing/messages.js @@ -59,6 +59,11 @@ const messages = defineMessages({ 'Message that appears when collaborators were added to the shared link in the ContentSharing Element.', id: 'be.contentSharing.sendInvitesSuccess', }, + groupContactLabel: { + defaultMessage: 'Group', + description: 'Display text for a Group contact type', + id: 'be.contentSharing.groupContactLabel', + }, }); export default messages; diff --git a/src/elements/content-sharing/types.js b/src/elements/content-sharing/types.js index 4a375dbc1d..fec4d92b91 100644 --- a/src/elements/content-sharing/types.js +++ b/src/elements/content-sharing/types.js @@ -140,6 +140,8 @@ export type GetContactsByEmailFnType = () => (filterTerm: { [emails: string]: string, }) => Promise> | null; +export type GetContactByEmailFnType = () => (email: string) => Promise> | null; + export type SendInvitesFnType = () => InviteCollaboratorsRequest => Promise>; export type ConnectToItemShareFnType = ({ diff --git a/src/elements/content-sharing/utils/__tests__/convertContactServiceData.test.ts b/src/elements/content-sharing/utils/__tests__/convertContactServiceData.test.ts index ba534f57fa..b535de4d0f 100644 --- a/src/elements/content-sharing/utils/__tests__/convertContactServiceData.test.ts +++ b/src/elements/content-sharing/utils/__tests__/convertContactServiceData.test.ts @@ -1,5 +1,9 @@ import { STATUS_INACTIVE } from '../../../../constants'; -import { convertUserContactsResponse, convertGroupContactsResponse } from '../convertContactServiceData'; +import { + convertUserContactsResponse, + convertGroupContactsResponse, + convertUserContactByEmailResponse, +} from '../convertContactServiceData'; const mockCurrentUserId = '123'; @@ -203,7 +207,7 @@ describe('elements/content-sharing/utils/convertContactServiceData', () => { describe('basic conversion', () => { test('should return empty array when entries is empty', () => { const contactsApiData = { entries: [] }; - const result = convertGroupContactsResponse(contactsApiData); + const result = convertGroupContactsResponse(contactsApiData, 'Group'); expect(result).toEqual([]); }); @@ -229,7 +233,7 @@ describe('elements/content-sharing/utils/convertContactServiceData', () => { ], }; - const result = convertGroupContactsResponse(contactsApiData); + const result = convertGroupContactsResponse(contactsApiData, 'Group'); expect(result).toEqual([ { @@ -237,14 +241,14 @@ describe('elements/content-sharing/utils/convertContactServiceData', () => { email: 'Group', name: 'Engineering Team', type: 'group', - value: 'Group', + value: 'group-1', }, { id: 'group-2', email: 'Group', name: 'Marketing Team', type: 'group', - value: 'Group', + value: 'group-2', }, ]); }); @@ -284,7 +288,7 @@ describe('elements/content-sharing/utils/convertContactServiceData', () => { ], }; - const result = convertGroupContactsResponse(contactsApiData); + const result = convertGroupContactsResponse(contactsApiData, 'Group'); expect(result).toHaveLength(1); expect(result[0].id).toBe('group-4'); @@ -321,7 +325,7 @@ describe('elements/content-sharing/utils/convertContactServiceData', () => { ], }; - const result = convertGroupContactsResponse(contactsApiData); + const result = convertGroupContactsResponse(contactsApiData, 'Group'); expect(result).toHaveLength(3); expect(result[0].name).toBe('Alice Group'); @@ -329,4 +333,81 @@ describe('elements/content-sharing/utils/convertContactServiceData', () => { expect(result[2].name).toBe('Charlie Group'); }); }); + + describe('convertUserContactByEmailResponse', () => { + describe('basic conversion', () => { + test('should return empty object when entries is empty', () => { + const contactsApiData = { entries: [] }; + const result = convertUserContactByEmailResponse(contactsApiData); + expect(result).toEqual({}); + }); + + test('should convert valid user contact correctly', () => { + const contactsApiData = { + entries: [ + { + id: 'user-1', + login: 'jane.smith@example.com', + name: 'Jane Smith', + type: 'user', + }, + ], + }; + + const result = convertUserContactByEmailResponse(contactsApiData); + + expect(result).toEqual({ + id: 'user-1', + email: 'jane.smith@example.com', + name: 'Jane Smith', + type: 'user', + value: 'jane.smith@example.com', + }); + }); + + test('should handle user contact with missing login field', () => { + const contactsApiData = { + entries: [ + { + id: 'user-1', + name: 'Jane Smith', + type: 'user', + }, + ], + }; + + const result = convertUserContactByEmailResponse(contactsApiData); + + expect(result).toEqual({ + id: 'user-1', + email: '', + name: 'Jane Smith', + type: 'user', + value: '', + }); + }); + + test('should handle user contact with undefined login field', () => { + const contactsApiData = { + entries: [ + { + id: 'user-1', + name: 'Jane Smith', + type: 'user', + }, + ], + }; + + const result = convertUserContactByEmailResponse(contactsApiData); + + expect(result).toEqual({ + id: 'user-1', + email: '', + name: 'Jane Smith', + type: 'user', + value: '', + }); + }); + }); + }); }); diff --git a/src/elements/content-sharing/utils/convertContactServiceData.ts b/src/elements/content-sharing/utils/convertContactServiceData.ts index 45d92e09ae..d7805e3a54 100644 --- a/src/elements/content-sharing/utils/convertContactServiceData.ts +++ b/src/elements/content-sharing/utils/convertContactServiceData.ts @@ -35,7 +35,7 @@ export const convertUserContactsResponse = (contactsApiData, currentUserId) => { /** * Convert an enterprise groups API response into an array of internal USM contacts. */ -export const convertGroupContactsResponse = contactsApiData => { +export const convertGroupContactsResponse = (contactsApiData, emailMessage) => { const { entries = [] } = contactsApiData; // Only return groups with the correct permissions @@ -47,11 +47,31 @@ export const convertGroupContactsResponse = contactsApiData => { const { id, name, type } = contact; return { id, - email: 'Group', // Need this for the avatar to work for isUserContactType + email: emailMessage, // Need this for the avatar to work for isUserContactType name, type, - value: 'Group', + value: id, }; }) .sort(sortByName); }; + +/** + * Convert an enterprise users API response into a single internal USM contact object (from the first entry). + */ +export const convertUserContactByEmailResponse = contactsApiData => { + const { entries = [] } = contactsApiData; + const entry = entries[0]; + if (!entry) { + return {}; + } + + const { id, login: email = '', name, type } = entry; + return { + id, + email, + name, + type, + value: email, + }; +}; diff --git a/src/elements/content-sharing/utils/index.ts b/src/elements/content-sharing/utils/index.ts index a7de99c363..24f9717345 100644 --- a/src/elements/content-sharing/utils/index.ts +++ b/src/elements/content-sharing/utils/index.ts @@ -1,6 +1,10 @@ export { convertCollabsResponse } from './convertCollaborators'; export { convertItemResponse } from './convertItemResponse'; -export { convertGroupContactsResponse, convertUserContactsResponse } from './convertContactServiceData'; +export { + convertGroupContactsResponse, + convertUserContactByEmailResponse, + convertUserContactsResponse, +} from './convertContactServiceData'; export { convertSharedLinkPermissions, convertSharedLinkSettings } from './convertSharingServiceData'; export { getAllowedAccessLevels } from './getAllowedAccessLevels'; export { getAllowedPermissionLevels } from './getAllowedPermissionLevels';