supported rendering all suggestion on user avatar click

This commit is contained in:
Ashish Gupta 2025-06-26 18:27:47 +05:30
parent c77f6d5db6
commit efb99c2889
6 changed files with 127 additions and 2 deletions

View File

@ -30,6 +30,7 @@ export interface SuggestionsContextType {
allSuggestionsUsers: EntityReference[];
onUpdateActiveUser: (user?: EntityReference) => void;
fetchSuggestions: () => void;
fetchSuggestionsByUserId: (userId: string, limit?: number) => void;
acceptRejectSuggestion: (
suggestion: Suggestion,
action: SuggestionAction

View File

@ -45,6 +45,7 @@ jest.mock('../../../hooks/useFqn', () => ({
jest.mock('../../../rest/suggestionsAPI', () => ({
getSuggestionsList: jest.fn().mockImplementation(() => Promise.resolve()),
getSuggestionsByUserId: jest.fn().mockImplementation(() => Promise.resolve()),
approveRejectAllSuggestions: jest.fn(),
updateSuggestionStatus: jest.fn(),
}));
@ -144,6 +145,26 @@ describe('SuggestionsProvider', () => {
SuggestionAction.Reject
);
});
it('calls fetchSuggestionsByUserId when button is clicked', async () => {
const { getSuggestionsByUserId } = await import(
'../../../rest/suggestionsAPI'
);
render(
<SuggestionsProvider>
<TestComponent />
</SuggestionsProvider>
);
const fetchByUserIdBtn = screen.getByText('Fetch By User ID');
fireEvent.click(fetchByUserIdBtn);
expect(getSuggestionsByUserId).toHaveBeenCalledWith('test-user-id', {
entityFQN: 'mockFQN',
limit: 10,
});
});
});
function TestComponent() {
@ -151,6 +172,7 @@ function TestComponent() {
acceptRejectAllSuggestions,
onUpdateActiveUser,
acceptRejectSuggestion,
fetchSuggestionsByUserId,
} = useSuggestionsContext();
return (
@ -181,6 +203,9 @@ function TestComponent() {
}>
Reject One
</button>
<button onClick={() => fetchSuggestionsByUserId('test-user-id')}>
Fetch By User ID
</button>
</>
);
}

View File

@ -35,6 +35,7 @@ import { useFqn } from '../../../hooks/useFqn';
import { usePub } from '../../../hooks/usePubSub';
import {
approveRejectAllSuggestions,
getSuggestionsByUserId,
getSuggestionsList,
updateSuggestionStatus,
} from '../../../rest/suggestionsAPI';
@ -97,6 +98,43 @@ const SuggestionsProvider = ({ children }: { children?: ReactNode }) => {
[entityFqn, suggestionLimit]
);
const fetchSuggestionsByUserId = useCallback(
async (userId: string, limit?: number) => {
setLoading(true);
try {
const { data } = await getSuggestionsByUserId(userId, {
entityFQN: entityFqn,
limit: limit ?? suggestionLimit,
});
// Merge new suggestions with existing ones, removing duplicates by ID
setSuggestions((prevSuggestions) => {
const existingIds = new Set(prevSuggestions.map((s) => s.id));
const newSuggestions = data.filter((s) => !existingIds.has(s.id));
const mergedSuggestions = [...prevSuggestions, ...newSuggestions];
// Update grouped suggestions with merged data
const { allUsersList, groupedSuggestions } =
getSuggestionByType(mergedSuggestions);
setAllSuggestionsUsers(uniqWith(allUsersList, isEqual));
setSuggestionsByUser(groupedSuggestions);
return mergedSuggestions;
});
} catch (err) {
showErrorToast(
err as AxiosError,
t('server.entity-fetch-error', {
entity: t('label.suggestion-lowercase-plural'),
})
);
} finally {
setLoading(false);
}
},
[entityFqn, suggestionLimit]
);
const acceptRejectSuggestion = useCallback(
async (suggestion: Suggestion, status: SuggestionAction) => {
try {
@ -188,6 +226,7 @@ const SuggestionsProvider = ({ children }: { children?: ReactNode }) => {
allSuggestionsUsers,
onUpdateActiveUser,
fetchSuggestions,
fetchSuggestionsByUserId,
acceptRejectSuggestion,
acceptRejectAllSuggestions,
};
@ -203,6 +242,7 @@ const SuggestionsProvider = ({ children }: { children?: ReactNode }) => {
allSuggestionsUsers,
onUpdateActiveUser,
fetchSuggestions,
fetchSuggestionsByUserId,
acceptRejectSuggestion,
acceptRejectAllSuggestions,
]);

View File

@ -13,6 +13,7 @@
import { render } from '@testing-library/react';
import React from 'react';
import { EntityReference } from '../../../generated/entity/type';
import { useSuggestionsContext } from '../../Suggestions/SuggestionsProvider/SuggestionsProvider';
import AvatarCarouselItem from './AvatarCarouselItem';
const suggestions = [
@ -46,6 +47,7 @@ jest.mock('../../Suggestions/SuggestionsProvider/SuggestionsProvider', () => ({
acceptRejectSuggestion: jest.fn(),
selectedUserSuggestions: [],
onUpdateActiveUser: jest.fn(),
fetchSuggestionsByUserId: jest.fn(),
})),
__esModule: true,
default: 'SuggestionsProvider',
@ -101,6 +103,37 @@ describe('AvatarCarouselItem', () => {
expect(onAvatarClick).toHaveBeenCalledWith(index);
});
it('calls fetchSuggestionsByUserId when avatar is clicked', () => {
const mockFetchSuggestionsByUserId = jest.fn();
(useSuggestionsContext as jest.Mock).mockImplementation(() => ({
suggestions: suggestions,
suggestionsByUser: suggByUser,
allSuggestionsUsers: [
{ id: '1', name: 'Avatar 1', type: 'user' },
{ id: '2', name: 'Avatar 2', type: 'user' },
],
acceptRejectSuggestion: jest.fn(),
selectedUserSuggestions: [],
onUpdateActiveUser: jest.fn(),
fetchSuggestionsByUserId: mockFetchSuggestionsByUserId,
}));
const { getByTestId } = render(
<AvatarCarouselItem
avatar={avatar}
avatarBtnRefs={avatarBtnRefs}
index={index}
isActive={isActive}
onAvatarClick={onAvatarClick}
/>
);
const button = getByTestId(`avatar-carousel-item-${avatar.id}`);
button.click();
expect(mockFetchSuggestionsByUserId).toHaveBeenCalledWith(avatar.id);
});
it('sets isActive class when isActive is true', () => {
const { getByTestId } = render(
<AvatarCarouselItem

View File

@ -33,7 +33,8 @@ const AvatarCarouselItem = ({
onAvatarClick,
isActive,
}: AvatarCarouselItemProps) => {
const { suggestionsByUser } = useSuggestionsContext();
const { suggestionsByUser, fetchSuggestionsByUserId } =
useSuggestionsContext();
const buttonRef = useRef(null);
avatarBtnRefs.current[index] = buttonRef;
const getUserSuggestionsCount = useCallback(
@ -42,6 +43,16 @@ const AvatarCarouselItem = ({
[suggestionsByUser]
);
const handleAvatarClick = useCallback(() => {
// Call the original onAvatarClick function
onAvatarClick(index);
// Fetch suggestions for this specific user
if (avatar.id) {
fetchSuggestionsByUserId(avatar.id);
}
}, [onAvatarClick, index, avatar.id, fetchSuggestionsByUserId]);
const button = (
<Button
className={classNames('p-0 m-r-xss avatar-item', {
@ -50,7 +61,7 @@ const AvatarCarouselItem = ({
data-testid={`avatar-carousel-item-${avatar.id}`}
ref={buttonRef}
shape="circle"
onClick={() => onAvatarClick(index)}>
onClick={handleAvatarClick}>
<ProfilePicture name={avatar.name ?? ''} width="28" />
</Button>
);

View File

@ -25,6 +25,7 @@ const BASE_URL = '/suggestions';
export type ListSuggestionsParams = ListParams & {
entityFQN?: string;
limit?: number;
userId?: string;
};
export const getSuggestionsList = async (params?: ListSuggestionsParams) => {
@ -35,6 +36,20 @@ export const getSuggestionsList = async (params?: ListSuggestionsParams) => {
return response.data;
};
export const getSuggestionsByUserId = async (
userId: string,
params?: Omit<ListSuggestionsParams, 'userId'>
) => {
const response = await APIClient.get<PagingResponse<Suggestion[]>>(BASE_URL, {
params: {
...params,
userId,
},
});
return response.data;
};
export const updateSuggestionStatus = (
data: Suggestion,
action: SuggestionAction