From d9d726944f3cc6d49a0cd80b385cee28cb331f08 Mon Sep 17 00:00:00 2001 From: Meetul Rathore Date: Mon, 28 Oct 2024 00:30:49 +0530 Subject: [PATCH 01/12] feat: Add people to tag functaionality (GSoC) (#2355) * add people to tag functaionality * minor change * more translations * minor change * add a variable for page size --- public/locales/en/translation.json | 9 +- public/locales/fr/translation.json | 9 +- public/locales/hi/translation.json | 9 +- public/locales/sp/translation.json | 9 +- public/locales/zh/translation.json | 9 +- src/GraphQl/Mutations/TagMutations.ts | 15 + src/GraphQl/Queries/userTagQueries.ts | 42 ++ .../AddPeopleToTag/AddPeopleToTag.module.css | 77 ++++ .../AddPeopleToTag/AddPeopleToTag.test.tsx | 198 +++++++++ .../AddPeopleToTag/AddPeopleToTag.tsx | 377 ++++++++++++++++++ .../AddPeopleToTag/AddPeopleToTagsMocks.ts | 169 ++++++++ src/screens/ManageTag/ManageTag.test.tsx | 7 +- src/screens/ManageTag/ManageTag.tsx | 43 +- src/screens/ManageTag/ManageTagMocks.ts | 84 ++++ src/utils/interfaces.ts | 39 +- src/utils/organizationTagsUtils.ts | 2 + 16 files changed, 1040 insertions(+), 58 deletions(-) create mode 100644 src/components/AddPeopleToTag/AddPeopleToTag.module.css create mode 100644 src/components/AddPeopleToTag/AddPeopleToTag.test.tsx create mode 100644 src/components/AddPeopleToTag/AddPeopleToTag.tsx create mode 100644 src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index a3415f199f..83bb999138 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -332,7 +332,14 @@ "successfullyUnassigned": "Tag unassigned from user", "addPeople": "Add People", "add": "Add", - "subTags": "Sub Tags" + "subTags": "Sub Tags", + "assignedToAll": "Tag Assigned to All", + "successfullyAssignedToPeople": "Tag assigned successfully", + "assignPeople": "Assign", + "errorOccurredWhileLoadingMembers": "Error occured while loading members", + "userName": "User Name", + "actions": "Actions", + "noOneSelected": "No One Selected" }, "userListCard": { "addAdmin": "Add Admin", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 6f7332057b..cd560dcda6 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -332,7 +332,14 @@ "successfullyUnassigned": "Étiquette retirée de l'utilisateur", "addPeople": "Ajouter des personnes", "add": "Ajouter", - "subTags": "Sous-étiquettes" + "subTags": "Sous-étiquettes", + "assignedToAll": "Étiquette attribuée à tous", + "successfullyAssignedToPeople": "Étiquette attribuée avec succès", + "assignPeople": "Attribuer", + "errorOccurredWhileLoadingMembers": "Erreur survenue lors du chargement des membres", + "userName": "Nom d'utilisateur", + "actions": "Actions", + "noOneSelected": "Personne sélectionnée" }, "userListCard": { "addAdmin": "Ajouter un administrateur", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 95d704daca..58d0aa5e57 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -332,7 +332,14 @@ "successfullyUnassigned": "उपयोगकर्ता से टैग हटा दिया गया", "addPeople": "लोगों को जोड़ें", "add": "जोड़ें", - "subTags": "उप-टैग्स" + "subTags": "उप-टैग्स", + "assignedToAll": "सभी को टैग असाइन किया गया", + "successfullyAssignedToPeople": "टैग सफलतापूर्वक असाइन किया गया", + "assignPeople": "असाइन करें", + "errorOccurredWhileLoadingMembers": "सदस्यों को लोड करते समय त्रुटि हुई", + "userName": "उपयोगकर्ता नाम", + "actions": "क्रियाएँ", + "noOneSelected": "कोई चयनित नहीं" }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index bd2959808d..48c8f9940d 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -332,7 +332,14 @@ "successfullyUnassigned": "Etiqueta desasignada del usuario", "addPeople": "Agregar Personas", "add": "Agregar", - "subTags": "Subetiquetas" + "subTags": "Subetiquetas", + "assignedToAll": "Etiqueta asignada a todos", + "successfullyAssignedToPeople": "Etiqueta asignada con éxito", + "assignPeople": "Asignar", + "errorOccurredWhileLoadingMembers": "Error al cargar los miembros", + "userName": "Nombre de usuario", + "actions": "Acciones", + "noOneSelected": "Nadie seleccionado" }, "userListCard": { "joined": "Unido", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index d5610594f6..9fb3965fd6 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -332,7 +332,14 @@ "successfullyUnassigned": "标签已从用户中取消分配", "addPeople": "添加人员", "add": "添加", - "subTags": "子标签" + "subTags": "子标签", + "assignedToAll": "标签分配给所有人", + "successfullyAssignedToPeople": "标签分配成功", + "assignPeople": "分配", + "errorOccurredWhileLoadingMembers": "加载成员时出错", + "userName": "用户名", + "actions": "操作", + "noOneSelected": "未选择任何人" }, "userListCard": { "addAdmin": "添加管理员", diff --git a/src/GraphQl/Mutations/TagMutations.ts b/src/GraphQl/Mutations/TagMutations.ts index 2e92babce4..d97fefc246 100644 --- a/src/GraphQl/Mutations/TagMutations.ts +++ b/src/GraphQl/Mutations/TagMutations.ts @@ -72,3 +72,18 @@ export const REMOVE_USER_TAG = gql` } } `; + +/** + * GraphQL mutation to add people to tag. + * + * @param tagId - Id of the tag to be assigned. + * @param userIds - Ids of the users to assign to. + */ + +export const ADD_PEOPLE_TO_TAG = gql` + mutation AddPeopleToUserTag($tagId: ID!, $userIds: [ID!]!) { + addPeopleToUserTag(input: { tagId: $tagId, userIds: $userIds }) { + _id + } + } +`; diff --git a/src/GraphQl/Queries/userTagQueries.ts b/src/GraphQl/Queries/userTagQueries.ts index e0673d81d7..9e1a11daa3 100644 --- a/src/GraphQl/Queries/userTagQueries.ts +++ b/src/GraphQl/Queries/userTagQueries.ts @@ -84,6 +84,48 @@ export const USER_TAG_SUB_TAGS = gql` } `; +/** + * GraphQL query to retrieve organization members that aren't assigned a certain tag. + * + * @param id - The ID of the tag. + * @returns The list of organization members. + */ + +export const USER_TAGS_MEMBERS_TO_ASSIGN_TO = gql` + query GetMembersToAssignTo( + $id: ID! + $after: String + $before: String + $first: PositiveInt + $last: PositiveInt + ) { + getUserTag(id: $id) { + name + usersToAssignTo( + after: $after + before: $before + first: $first + last: $last + ) { + edges { + node { + _id + firstName + lastName + } + } + pageInfo { + startCursor + endCursor + hasNextPage + hasPreviousPage + } + totalCount + } + } + } +`; + /** * GraphQL query to retrieve the ancestor tags of a certain tag. * diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.module.css b/src/components/AddPeopleToTag/AddPeopleToTag.module.css new file mode 100644 index 0000000000..fb8599d96c --- /dev/null +++ b/src/components/AddPeopleToTag/AddPeopleToTag.module.css @@ -0,0 +1,77 @@ +.errorContainer { + min-height: 100vh; +} + +.errorMessage { + margin-top: 25%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.errorIcon { + transform: scale(1.5); + color: var(--bs-danger); + margin-bottom: 1rem; +} + +.tableHeader { + background-color: var(--bs-primary); + color: var(--bs-white); + font-size: 1rem; +} + +.rowBackground { + background-color: var(--bs-white); + max-height: 120px; +} + +.scrollContainer { + max-height: 100px; /* Adjust as needed */ + overflow-y: auto; + margin-bottom: 1rem; +} + +.memberBadge { + display: flex; + align-items: center; + padding: 5px 10px; + border-radius: 12px; + background-color: #f8f9fa; /* Light background */ + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + max-width: calc(100% - 30px); /* Ensure it fits within the container */ +} + +.removeFilterIcon { + cursor: pointer; +} + +.scrollContainer { + max-height: 350px; /* Set your desired max height */ + overflow-y: auto; /* Enable vertical scrolling */ +} + +/* SimpleLoader.css */ +.simple-loader { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +} + +.spinner { + width: 40px; + height: 40px; + border: 4px solid rgba(0, 0, 0, 0.1); + border-top-color: #3498db; + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx new file mode 100644 index 0000000000..4b24694a25 --- /dev/null +++ b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx @@ -0,0 +1,198 @@ +import React from 'react'; +import { MockedProvider } from '@apollo/react-testing'; +import type { RenderResult } from '@testing-library/react'; +import { + render, + screen, + fireEvent, + cleanup, + waitFor, +} from '@testing-library/react'; +import { Provider } from 'react-redux'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import 'jest-location-mock'; +import { I18nextProvider } from 'react-i18next'; + +import { store } from 'state/store'; +import userEvent from '@testing-library/user-event'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import { toast } from 'react-toastify'; +import type { ApolloLink } from '@apollo/client'; +import type { InterfaceAddPeopleToTagProps } from './AddPeopleToTag'; +import AddPeopleToTag from './AddPeopleToTag'; +import i18n from 'utils/i18nForTest'; +import { MOCKS, MOCKS_ERROR } from './AddPeopleToTagsMocks'; + +const link = new StaticMockLink(MOCKS, true); +const link2 = new StaticMockLink(MOCKS_ERROR, true); + +async function wait(): Promise { + await waitFor(() => { + // The waitFor utility automatically uses optimal timing + return Promise.resolve(); + }); +} + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +const translations = { + ...JSON.parse( + JSON.stringify(i18n.getDataByLanguage('en')?.translation.manageTag ?? {}), + ), + ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.common ?? {})), + ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.errors ?? {})), +}; + +const props: InterfaceAddPeopleToTagProps = { + addPeopleToTagModalIsOpen: true, + hideAddPeopleToTagModal: () => {}, + refetchAssignedMembersData: () => {}, + t: (key: string) => translations[key], + tCommon: (key: string) => translations[key], +}; + +const renderAddPeopleToTagModal = ( + props: InterfaceAddPeopleToTagProps, + link: ApolloLink, +): RenderResult => { + return render( + + + + + + } + /> + + + + + , + ); +}; + +describe('Organisation Tags Page', () => { + beforeEach(() => { + jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ orgId: 'orgId' }), + })); + // cache.reset(); + }); + + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + test('Component loads correctly', async () => { + const { getByText } = renderAddPeopleToTagModal(props, link); + + await wait(); + + await waitFor(() => { + expect(getByText(translations.addPeople)).toBeInTheDocument(); + }); + }); + + test('Renders error component when when query is unsuccessful', async () => { + const { queryByText } = renderAddPeopleToTagModal(props, link2); + + await wait(); + + await waitFor(() => { + expect(queryByText(translations.addPeople)).not.toBeInTheDocument(); + }); + }); + + test('Selects and deselects members to assign to', async () => { + renderAddPeopleToTagModal(props, link); + + await wait(); + + await waitFor(() => { + expect(screen.getAllByTestId('selectMemberBtn')[0]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('selectMemberBtn')[0]); + + await waitFor(() => { + expect(screen.getAllByTestId('selectMemberBtn')[1]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('selectMemberBtn')[1]); + + await waitFor(() => { + expect( + screen.getAllByTestId('clearSelectedMember')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('clearSelectedMember')[0]); + + await waitFor(() => { + expect(screen.getAllByTestId('deselectMemberBtn')[0]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('deselectMemberBtn')[0]); + }); + + test('Renders more members with infinite scroll', async () => { + const { getByText } = renderAddPeopleToTagModal(props, link); + + await wait(); + + await waitFor(() => { + expect(getByText(translations.addPeople)).toBeInTheDocument(); + }); + + // Find the infinite scroll div by test ID or another selector + const scrollableDiv = screen.getByTestId('scrollableDiv'); + + const initialMemberDataLength = screen.getAllByTestId('memberName').length; + + // Set scroll position to the bottom + fireEvent.scroll(scrollableDiv, { + target: { scrollY: scrollableDiv.scrollHeight }, + }); + + await waitFor(() => { + const finalMemberDataLength = screen.getAllByTestId('memberName').length; + expect(finalMemberDataLength).toBeGreaterThan(initialMemberDataLength); + + expect(getByText(translations.addPeople)).toBeInTheDocument(); + }); + }); + + test('Assigns tag to multiple people', async () => { + renderAddPeopleToTagModal(props, link); + + await wait(); + + await waitFor(() => { + expect(screen.getAllByTestId('selectMemberBtn')[0]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('selectMemberBtn')[0]); + + await waitFor(() => { + expect(screen.getAllByTestId('selectMemberBtn')[1]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('selectMemberBtn')[1]); + + await waitFor(() => { + expect(screen.getAllByTestId('selectMemberBtn')[2]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('selectMemberBtn')[2]); + + userEvent.click(screen.getByTestId('assignPeopleBtn')); + + await waitFor(() => { + expect(toast.success).toHaveBeenCalledWith( + translations.successfullyAssignedToPeople, + ); + }); + }); +}); diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx new file mode 100644 index 0000000000..73066d2f0f --- /dev/null +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -0,0 +1,377 @@ +import type { ApolloError } from '@apollo/client'; +import { useMutation, useQuery } from '@apollo/client'; +import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; +import { DataGrid } from '@mui/x-data-grid'; +import Loader from 'components/Loader/Loader'; +import { USER_TAGS_MEMBERS_TO_ASSIGN_TO } from 'GraphQl/Queries/userTagQueries'; +import type { ChangeEvent } from 'react'; +import React, { useState } from 'react'; +import { Modal, Form, Button } from 'react-bootstrap'; +import { useParams } from 'react-router-dom'; +import type { InterfaceQueryUserTagsMembersToAssignTo } from 'utils/interfaces'; +import styles from './AddPeopleToTag.module.css'; +import { + ADD_PEOPLE_TO_TAGS_QUERY_LIMIT, + dataGridStyle, +} from 'utils/organizationTagsUtils'; +import { Stack } from '@mui/material'; +import { toast } from 'react-toastify'; +import { ADD_PEOPLE_TO_TAG } from 'GraphQl/Mutations/TagMutations'; +import InfiniteScroll from 'react-infinite-scroll-component'; +import { WarningAmberRounded } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; + +/** + * Props for the `AddPeopleToTag` component. + */ +export interface InterfaceAddPeopleToTagProps { + addPeopleToTagModalIsOpen: boolean; + hideAddPeopleToTagModal: () => void; + refetchAssignedMembersData: () => void; + t: (key: string) => string; + tCommon: (key: string) => string; +} + +interface InterfaceMemberData { + _id: string; + firstName: string; + lastName: string; +} + +const AddPeopleToTag: React.FC = ({ + addPeopleToTagModalIsOpen, + hideAddPeopleToTagModal, + refetchAssignedMembersData, + t, + tCommon, +}) => { + const { tagId: currentTagId } = useParams(); + + const { t: tErrors } = useTranslation('error'); + + const [assignToMembers, setAssignToMembers] = useState( + [], + ); + + const { + data: userTagsMembersToAssignToData, + loading: userTagsMembersToAssignToLoading, + error: userTagsMembersToAssignToError, + fetchMore: fetchMoreMembersToAssignTo, + }: { + data?: { + getUserTag: InterfaceQueryUserTagsMembersToAssignTo; + }; + loading: boolean; + error?: ApolloError; + fetchMore: (options: { + variables: { + after?: string | null; + first?: number | null; + }; + updateQuery?: ( + previousQueryResult: { + getUserTag: InterfaceQueryUserTagsMembersToAssignTo; + }, + options: { + fetchMoreResult: { + getUserTag: InterfaceQueryUserTagsMembersToAssignTo; + }; + }, + ) => { getUserTag: InterfaceQueryUserTagsMembersToAssignTo }; + }) => Promise; + } = useQuery(USER_TAGS_MEMBERS_TO_ASSIGN_TO, { + variables: { + id: currentTagId, + first: ADD_PEOPLE_TO_TAGS_QUERY_LIMIT, + }, + skip: !addPeopleToTagModalIsOpen, + }); + + const loadMoreMembersToAssignTo = (): void => { + fetchMoreMembersToAssignTo({ + variables: { + first: ADD_PEOPLE_TO_TAGS_QUERY_LIMIT, + after: + userTagsMembersToAssignToData?.getUserTag.usersToAssignTo.pageInfo + .endCursor, // Fetch after the last loaded cursor + }, + updateQuery: ( + prevResult: { getUserTag: InterfaceQueryUserTagsMembersToAssignTo }, + { + fetchMoreResult, + }: { + fetchMoreResult: { + getUserTag: InterfaceQueryUserTagsMembersToAssignTo; + }; + }, + ) => { + if (!fetchMoreResult) return prevResult; + + return { + getUserTag: { + ...fetchMoreResult.getUserTag, + usersToAssignTo: { + ...fetchMoreResult.getUserTag.usersToAssignTo, + edges: [ + ...prevResult.getUserTag.usersToAssignTo.edges, + ...fetchMoreResult.getUserTag.usersToAssignTo.edges, + ], + }, + }, + }; + }, + }); + }; + + const userTagMembersToAssignTo = + userTagsMembersToAssignToData?.getUserTag.usersToAssignTo.edges.map( + (edge) => edge.node, + ); + + const handleAddOrRemoveMember = (member: InterfaceMemberData): void => { + setAssignToMembers((prevMembers) => { + const isAssigned = prevMembers.some((m) => m._id === member._id); + if (isAssigned) { + return prevMembers.filter((m) => m._id !== member._id); + } else { + return [...prevMembers, member]; + } + }); + }; + + const removeMember = (id: string): void => { + setAssignToMembers((prevMembers) => + prevMembers.filter((m) => m._id !== id), + ); + }; + + const [addPeople, { loading: addPeopleToTagLoading }] = + useMutation(ADD_PEOPLE_TO_TAG); + + const addPeopleToCurrentTag = async ( + e: ChangeEvent, + ): Promise => { + e.preventDefault(); + + try { + const { data } = await addPeople({ + variables: { + tagId: currentTagId, + userIds: assignToMembers.map((member) => member._id), + }, + }); + + if (data) { + toast.success(t('successfullyAssignedToPeople')); + refetchAssignedMembersData(); + hideAddPeopleToTagModal(); + setAssignToMembers([]); + } + } catch (error: unknown) { + /* istanbul ignore next */ + const errorMessage = + error instanceof Error ? error.message : tErrors('unknownError'); + toast.error(errorMessage); + } + }; + + if (userTagsMembersToAssignToError) { + return ( +
+
+ +
+ {t('errorOccurredWhileLoadingMembers')} +
+ {userTagsMembersToAssignToError.message} +
+
+
+ ); + } + + const columns: GridColDef[] = [ + { + field: 'id', + headerName: '#', + minWidth: 100, + align: 'center', + headerAlign: 'center', + headerClassName: `${styles.tableHeader}`, + sortable: false, + renderCell: (params: GridCellParams) => { + return
{params.row.id}
; + }, + }, + { + field: 'userName', + headerName: t('userName'), + flex: 2, + minWidth: 100, + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + return ( +
+ {params.row.firstName + ' ' + params.row.lastName} +
+ ); + }, + }, + { + field: 'actions', + headerName: t('actions'), + flex: 1, + align: 'center', + minWidth: 100, + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + const isToBeAssigned = assignToMembers.some( + (member) => member._id === params.row._id, + ); + + return ( + + ); + }, + }, + ]; + + return ( + <> + + + {t('addPeople')} + +
+ + {userTagsMembersToAssignToLoading ? ( + + ) : ( + <> +
+ {assignToMembers.length === 0 ? ( +
+ {t('noOneSelected')} +
+ ) : ( + assignToMembers.map((member) => ( +
+ {member.firstName} {member.lastName} + removeMember(member._id)} + data-testid="clearSelectedMember" + /> +
+ )) + )} +
+ +
+ +
+
+ } + scrollableTarget="scrollableDiv" + > + row._id} + slots={{ + noRowsOverlay: /* istanbul ignore next */ () => ( + + {t('assignedToAll')} + + ), + }} + sx={dataGridStyle} + getRowClassName={() => `${styles.rowBackground}`} + autoHeight + rowHeight={65} + rows={userTagMembersToAssignTo?.map( + (membersToAssignTo, index) => ({ + id: index + 1, + ...membersToAssignTo, + }), + )} + columns={columns} + isRowSelectable={() => false} + /> +
+
+ + )} +
+ + + + + +
+ + ); +}; + +export default AddPeopleToTag; diff --git a/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts b/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts new file mode 100644 index 0000000000..ab185bb858 --- /dev/null +++ b/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts @@ -0,0 +1,169 @@ +import { ADD_PEOPLE_TO_TAG } from 'GraphQl/Mutations/TagMutations'; +import { USER_TAGS_MEMBERS_TO_ASSIGN_TO } from 'GraphQl/Queries/userTagQueries'; + +export const MOCKS = [ + { + request: { + query: USER_TAGS_MEMBERS_TO_ASSIGN_TO, + variables: { + id: '1', + first: 7, + }, + }, + result: { + data: { + getUserTag: { + name: 'tag1', + usersToAssignTo: { + edges: [ + { + node: { + _id: '1', + firstName: 'member', + lastName: '1', + }, + cursor: '1', + }, + { + node: { + _id: '2', + firstName: 'member', + lastName: '2', + }, + cursor: '2', + }, + { + node: { + _id: '3', + firstName: 'member', + lastName: '3', + }, + cursor: '3', + }, + { + node: { + _id: '4', + firstName: 'member', + lastName: '4', + }, + cursor: '4', + }, + { + node: { + _id: '5', + firstName: 'member', + lastName: '5', + }, + cursor: '5', + }, + { + node: { + _id: '6', + firstName: 'member', + lastName: '6', + }, + cursor: '6', + }, + { + node: { + _id: '7', + firstName: 'member', + lastName: '7', + }, + cursor: '7', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '7', + hasNextPage: true, + hasPreviousPage: false, + }, + totalCount: 10, + }, + }, + }, + }, + }, + { + request: { + query: USER_TAGS_MEMBERS_TO_ASSIGN_TO, + variables: { + id: '1', + first: 7, + after: '7', + }, + }, + result: { + data: { + getUserTag: { + name: 'tag1', + usersToAssignTo: { + edges: [ + { + node: { + _id: '8', + firstName: 'member', + lastName: '8', + }, + cursor: '8', + }, + { + node: { + _id: '9', + firstName: 'member', + lastName: '9', + }, + cursor: '9', + }, + { + node: { + _id: '10', + firstName: 'member', + lastName: '10', + }, + cursor: '10', + }, + ], + pageInfo: { + startCursor: '8', + endCursor: '10', + hasNextPage: false, + hasPreviousPage: true, + }, + totalCount: 10, + }, + }, + }, + }, + }, + { + request: { + query: ADD_PEOPLE_TO_TAG, + variables: { + tagId: '1', + userIds: ['1', '3', '5'], + }, + }, + result: { + data: { + addPeopleToUserTag: { + _id: '1', + }, + }, + }, + }, +]; + +export const MOCKS_ERROR = [ + { + request: { + query: USER_TAGS_MEMBERS_TO_ASSIGN_TO, + variables: { + id: '1', + first: 7, + }, + }, + error: new Error('Mock Graphql Error'), + }, +]; diff --git a/src/screens/ManageTag/ManageTag.test.tsx b/src/screens/ManageTag/ManageTag.test.tsx index 38ebba7402..d497d2a0a8 100644 --- a/src/screens/ManageTag/ManageTag.test.tsx +++ b/src/screens/ManageTag/ManageTag.test.tsx @@ -60,6 +60,7 @@ const cache = new InMemoryCache({ getUserTag: { keyArgs: false, merge(existing = {}, incoming) { + console.log(existing); return incoming; }, }, @@ -99,7 +100,7 @@ const renderManageTag = (link: ApolloLink): RenderResult => { ); }; -describe('Organisation Tags Page', () => { +describe('Manage Tag Page', () => { beforeEach(() => { jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -290,7 +291,9 @@ describe('Organisation Tags Page', () => { userEvent.click(screen.getByTestId('unassignTagModalSubmitBtn')); await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.successfullyUnassigned); + expect(toast.success).toHaveBeenCalledWith( + translations.successfullyUnassigned, + ); }); }); }); diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 86a44fb169..25b39e884d 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -23,6 +23,7 @@ import { USER_TAG_ANCESTORS, USER_TAGS_ASSIGNED_MEMBERS, } from 'GraphQl/Queries/userTagQueries'; +import AddPeopleToTag from 'components/AddPeopleToTag/AddPeopleToTag'; /** * Component that renders the Manage Tag screen when the app navigates to '/orgtags/:orgId/managetag/:tagId'. @@ -432,41 +433,13 @@ function ManageTag(): JSX.Element { {/* Add People To Tag Modal */} - - - {t('addPeople')} - -
- - - - - - -
-
+ {/* Unassign Tag Modal */} Date: Mon, 28 Oct 2024 01:08:57 +0530 Subject: [PATCH 02/12] exclude ManageTag from countline check --- .github/workflows/pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 75588cc4df..c423143e6f 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -38,7 +38,7 @@ jobs: - name: Count number of lines run: | chmod +x ./.github/workflows/countline.py - ./.github/workflows/countline.py --lines 600 --exclude_files src/screens/LoginPage/LoginPage.tsx src/GraphQl/Queries/Queries.ts src/screens/OrgList/OrgList.tsx src/GraphQl/Mutations/mutations.ts src/components/EventListCard/EventListCardModals.tsx + ./.github/workflows/countline.py --lines 600 --exclude_files src/screens/LoginPage/LoginPage.tsx src/GraphQl/Queries/Queries.ts src/screens/OrgList/OrgList.tsx src/GraphQl/Mutations/mutations.ts src/components/EventListCard/EventListCardModals.tsx src/screens/ManageTag/ManageTag.tsx - name: Get changed TypeScript files id: changed-files From 58af28677839a49bf25c75a6660de324539e37c1 Mon Sep 17 00:00:00 2001 From: meetul Date: Mon, 28 Oct 2024 01:14:25 +0530 Subject: [PATCH 03/12] fix linting --- src/components/TagActions/TagNode.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx index 8bad6c78ec..e419e22e05 100644 --- a/src/components/TagActions/TagNode.tsx +++ b/src/components/TagActions/TagNode.tsx @@ -10,6 +10,9 @@ import { TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; import styles from './TagActions.module.css'; import InfiniteScroll from 'react-infinite-scroll-component'; +/** + * Props for the `AssignToTags` component. + */ interface InterfaceTagNodeProps { tag: InterfaceTagData; checkedTags: Set; @@ -17,6 +20,9 @@ interface InterfaceTagNodeProps { t: (key: string) => string; } +/** + * Renders the Tags which can be expanded to list subtags. + */ const TagNode: React.FC = ({ tag, checkedTags, From 59f5e03c1ead0e081d839452ade3e560c3d503f9 Mon Sep 17 00:00:00 2001 From: meetul Date: Mon, 28 Oct 2024 01:18:48 +0530 Subject: [PATCH 04/12] fix linting --- public/locales/fr/translation.json | 1 - 1 file changed, 1 deletion(-) diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 9957849399..28dc919936 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -334,7 +334,6 @@ "subTags": "Sous-étiquettes", "assignedToAll": "Étiquette attribuée à tous", "successfullyAssignedToPeople": "Étiquette attribuée avec succès", - "assignPeople": "Attribuer", "errorOccurredWhileLoadingMembers": "Erreur survenue lors du chargement des membres", "userName": "Nom d'utilisateur", "actions": "Actions", From 879ef421253e68a51d6b22dab8876202cf9fcc7a Mon Sep 17 00:00:00 2001 From: meetul Date: Mon, 28 Oct 2024 01:25:19 +0530 Subject: [PATCH 05/12] fix linting --- public/locales/fr/translation.json | 1 + 1 file changed, 1 insertion(+) diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 28dc919936..5d1cf583a8 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -330,6 +330,7 @@ "unassignUserTag": "Désassigner l'étiquette", "unassignUserTagMessage": "Voulez-vous retirer l'étiquette de cet utilisateur?", "successfullyUnassigned": "Étiquette retirée de l'utilisateur", + "addPeople": "Ajouter des personnes", "add": "Ajouter", "subTags": "Sous-étiquettes", "assignedToAll": "Étiquette attribuée à tous", From 6a79d5133c990ac8b2914fb4f27f95c0ccb3fe92 Mon Sep 17 00:00:00 2001 From: meetul Date: Mon, 28 Oct 2024 12:52:20 +0530 Subject: [PATCH 06/12] make coderabbit suggested changes --- public/locales/en/translation.json | 3 +- public/locales/fr/translation.json | 3 +- public/locales/hi/translation.json | 3 +- public/locales/sp/translation.json | 3 +- public/locales/zh/translation.json | 3 +- src/components/TagActions/TagActions.test.tsx | 10 ++-- src/components/TagActions/TagActions.tsx | 59 +++++++------------ src/components/TagActions/TagActionsMocks.ts | 15 ++--- src/screens/ManageTag/ManageTag.test.tsx | 7 +++ src/screens/ManageTag/ManageTag.tsx | 18 ++++-- 10 files changed, 61 insertions(+), 63 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 2485fec3e4..19bea8e4b4 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -352,7 +352,8 @@ "tagName": "Name", "tagUpdationSuccess": "Tag updated successfully", "tagRemovalSuccess": "Tag deleted successfully", - "noTagSelected": "No Tag Selected" + "noTagSelected": "No Tag Selected", + "changeNameToEdit": "Change the name to make an update" }, "userListCard": { "addAdmin": "Add Admin", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 5d1cf583a8..b961c587ff 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -352,7 +352,8 @@ "tagName": "Nom", "tagUpdationSuccess": "Étiquette mise à jour avec succès", "tagRemovalSuccess": "Étiquette supprimée avec succès", - "noTagSelected": "Aucun tag sélectionné" + "noTagSelected": "Aucun tag sélectionné", + "changeNameToEdit": "Modifiez le nom pour faire une mise à jour" }, "userListCard": { "addAdmin": "Ajouter un administrateur", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index cdc1d5e52b..940be5a73c 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -352,7 +352,8 @@ "tagName": "नाम", "tagUpdationSuccess": "टैग सफलतापूर्वक अपडेट की गई", "tagRemovalSuccess": "टैग सफलतापूर्वक हटाई गई", - "noTagSelected": "कोई टैग चयनित नहीं" + "noTagSelected": "कोई टैग चयनित नहीं", + "changeNameToEdit": "अपडेट करने के लिए नाम बदलें" }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index fc6d9eb839..4e65b4f8d2 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -352,7 +352,8 @@ "tagName": "Nombre", "tagUpdationSuccess": "Etiqueta actualizada con éxito", "tagRemovalSuccess": "Etiqueta eliminada con éxito", - "noTagSelected": "Ninguna etiqueta seleccionada" + "noTagSelected": "Ninguna etiqueta seleccionada", + "changeNameToEdit": "Cambia el nombre para hacer una actualización" }, "userListCard": { "joined": "Unido", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index a264056547..d6fe494a04 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -352,7 +352,8 @@ "tagName": "名称", "tagUpdationSuccess": "标签更新成功", "tagRemovalSuccess": "标签删除成功", - "noTagSelected": "未选择标签" + "noTagSelected": "未选择标签", + "changeNameToEdit": "更改名称以进行更新" }, "userListCard": { "addAdmin": "添加管理员", diff --git a/src/components/TagActions/TagActions.test.tsx b/src/components/TagActions/TagActions.test.tsx index 3f44b314ce..014748a913 100644 --- a/src/components/TagActions/TagActions.test.tsx +++ b/src/components/TagActions/TagActions.test.tsx @@ -7,6 +7,7 @@ import { fireEvent, cleanup, waitFor, + act, } from '@testing-library/react'; import { Provider } from 'react-redux'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; @@ -26,10 +27,11 @@ import { MOCKS, MOCKS_ERROR } from './TagActionsMocks'; const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(MOCKS_ERROR, true); -async function wait(): Promise { - await waitFor(() => { - // The waitFor utility automatically uses optimal timing - return Promise.resolve(); +async function wait(ms = 500): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); }); } diff --git a/src/components/TagActions/TagActions.tsx b/src/components/TagActions/TagActions.tsx index a8c0871053..b5c219aa3d 100644 --- a/src/components/TagActions/TagActions.tsx +++ b/src/components/TagActions/TagActions.tsx @@ -82,7 +82,7 @@ const TagActions: React.FC = ({ skip: !assignToTagsModalIsOpen, }); - const userTagsList = orgUserTagsData?.organizations[0].userTags.edges.map( + const userTagsList = orgUserTagsData?.organizations[0]?.userTags.edges.map( (edge) => edge.node, ); @@ -246,49 +246,32 @@ const TagActions: React.FC = ({ }; const [assignToTags] = useMutation(ASSIGN_TO_TAGS); - - const assignToSelectedTags = async ( - e: FormEvent, - ): Promise => { - e.preventDefault(); - - try { - const { data } = await assignToTags({ - variables: { - currentTagId, - selectedTagIds: selectedTags.map((selectedTag) => selectedTag._id), - }, - }); - - if (data) { - toast.success(t('successfullyAssignedToTags')); - hideAssignToTagsModal(); - } - } catch (error: unknown) { - /* istanbul ignore next */ - if (error instanceof Error) { - toast.error(error.message); - } - } - }; - const [removeFromTags] = useMutation(REMOVE_FROM_TAGS); - const removeFromSelectedTags = async ( + const handleTagAction = async ( e: FormEvent, ): Promise => { e.preventDefault(); + const mutationObject = { + variables: { + currentTagId, + selectedTagIds: selectedTags.map((selectedTag) => selectedTag._id), + }, + }; + try { - const { data } = await removeFromTags({ - variables: { - currentTagId, - selectedTagIds: selectedTags.map((selectedTag) => selectedTag._id), - }, - }); + const { data } = + tagActionType === 'assignToTags' + ? await assignToTags(mutationObject) + : await removeFromTags(mutationObject); if (data) { - toast.success(t('successfullyRemovedFromTags')); + if (tagActionType === 'assignToTags') { + toast.success(t('successfullyAssignedToTags')); + } else { + toast.success(t('successfullyRemovedFromTags')); + } hideAssignToTagsModal(); } } catch (error: unknown) { @@ -306,8 +289,6 @@ const TagActions: React.FC = ({
{t('errorOccurredWhileLoadingOrganizationUserTags')} -
- {orgUserTagsError.message}
@@ -337,8 +318,8 @@ const TagActions: React.FC = ({
tagActionType === 'assignToTags' - ? assignToSelectedTags(e) - : removeFromSelectedTags(e) + ? handleTagAction(e) + : handleTagAction(e) } > diff --git a/src/components/TagActions/TagActionsMocks.ts b/src/components/TagActions/TagActionsMocks.ts index c7a9c80ee2..46ef0b85db 100644 --- a/src/components/TagActions/TagActionsMocks.ts +++ b/src/components/TagActions/TagActionsMocks.ts @@ -366,8 +366,8 @@ export const MOCKS = [ }, ], pageInfo: { - startCursor: '1', - endCursor: '10', + startCursor: 'subTag1', + endCursor: 'subTag10', hasNextPage: true, hasPreviousPage: false, }, @@ -382,7 +382,7 @@ export const MOCKS = [ query: USER_TAG_SUB_TAGS, variables: { id: '1', - after: '10', + after: 'subTag10', first: TAGS_QUERY_LIMIT, }, }, @@ -407,8 +407,8 @@ export const MOCKS = [ }, ], pageInfo: { - startCursor: '11', - endCursor: '11', + startCursor: 'subTag11', + endCursor: 'subTag11', hasNextPage: false, hasPreviousPage: true, }, @@ -458,10 +458,7 @@ export const MOCKS_ERROR = [ query: ORGANIZATION_USER_TAGS_LIST, variables: { id: '123', - after: null, - before: null, - first: 5, - last: null, + first: TAGS_QUERY_LIMIT, }, }, error: new Error('Mock Graphql Error'), diff --git a/src/screens/ManageTag/ManageTag.test.tsx b/src/screens/ManageTag/ManageTag.test.tsx index a27ebad8b4..5de5e97c88 100644 --- a/src/screens/ManageTag/ManageTag.test.tsx +++ b/src/screens/ManageTag/ManageTag.test.tsx @@ -49,6 +49,7 @@ async function wait(ms = 500): Promise { jest.mock('react-toastify', () => ({ toast: { success: jest.fn(), + info: jest.fn(), error: jest.fn(), }, })); @@ -394,6 +395,12 @@ describe('Manage Tag Page', () => { }); userEvent.click(screen.getByTestId('editTag')); + userEvent.click(screen.getByTestId('editTagSubmitBtn')); + + await waitFor(() => { + expect(toast.info).toHaveBeenCalledWith(translations.changeNameToEdit); + }); + const tagNameInput = screen.getByTestId('tagNameInput'); await userEvent.clear(tagNameInput); await userEvent.type(tagNameInput, 'tag 1 edited'); diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index b861ff962b..8099ca8754 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -87,6 +87,10 @@ function ManageTag(): JSX.Element { setAssignToTagsModalIsOpen(false); }; + const showEditTagModal = (): void => { + setEditTagModalIsOpen(true); + }; + const hideEditTagModal = (): void => { setEditTagModalIsOpen(false); }; @@ -159,6 +163,7 @@ function ManageTag(): JSX.Element { const [edit] = useMutation(UPDATE_USER_TAG); const [newTagName, setNewTagName] = useState(''); + const currentTagName = userTagAssignedMembersData?.getUserTag.name ?? ''; useEffect(() => { setNewTagName(userTagAssignedMembersData?.getUserTag.name ?? ''); @@ -167,6 +172,11 @@ function ManageTag(): JSX.Element { const editTag = async (e: FormEvent): Promise => { e.preventDefault(); + if (newTagName === currentTagName) { + toast.info(t('changeNameToEdit')); + return; + } + try { const { data } = await edit({ variables: { @@ -524,18 +534,14 @@ function ManageTag(): JSX.Element {
{ - setEditTagModalIsOpen(true); - }} + onClick={showEditTagModal} className="ms-5 mt-3 mb-2 btn btn-primary btn-sm w-75" data-testid="editTag" > {tCommon('edit')}
{ - setRemoveTagModalIsOpen(true); - }} + onClick={toggleRemoveUserTagModal} className="ms-5 mb-2 btn btn-danger btn-sm w-75" data-testid="removeTag" > From 9bc25d75e83803564832cb83561756ce6ced1aa1 Mon Sep 17 00:00:00 2001 From: meetul Date: Mon, 28 Oct 2024 13:34:32 +0530 Subject: [PATCH 07/12] more changes --- public/locales/en/translation.json | 6 +++++- public/locales/fr/translation.json | 6 +++++- public/locales/hi/translation.json | 6 +++++- public/locales/sp/translation.json | 6 +++++- public/locales/zh/translation.json | 6 +++++- src/components/TagActions/TagActions.tsx | 19 +++++++------------ src/components/TagActions/TagActionsMocks.ts | 2 +- src/components/TagActions/TagNode.tsx | 5 ++++- src/screens/ManageTag/ManageTag.tsx | 2 +- 9 files changed, 38 insertions(+), 20 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 19bea8e4b4..3b202bd593 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -353,7 +353,11 @@ "tagUpdationSuccess": "Tag updated successfully", "tagRemovalSuccess": "Tag deleted successfully", "noTagSelected": "No Tag Selected", - "changeNameToEdit": "Change the name to make an update" + "changeNameToEdit": "Change the name to make an update", + "selectTag": "Select Tag", + "collapse": "Collapse", + "expand": "Expand", + "tagNamePlaceholder": "Write the name of the tag" }, "userListCard": { "addAdmin": "Add Admin", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index b961c587ff..7b11e35f56 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -353,7 +353,11 @@ "tagUpdationSuccess": "Étiquette mise à jour avec succès", "tagRemovalSuccess": "Étiquette supprimée avec succès", "noTagSelected": "Aucun tag sélectionné", - "changeNameToEdit": "Modifiez le nom pour faire une mise à jour" + "changeNameToEdit": "Modifiez le nom pour faire une mise à jour", + "selectTag": "Sélectionner le tag", + "collapse": "Réduire", + "expand": "Développer", + "tagNamePlaceholder": "Écrire le nom du tag" }, "userListCard": { "addAdmin": "Ajouter un administrateur", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 940be5a73c..bab222175c 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -353,7 +353,11 @@ "tagUpdationSuccess": "टैग सफलतापूर्वक अपडेट की गई", "tagRemovalSuccess": "टैग सफलतापूर्वक हटाई गई", "noTagSelected": "कोई टैग चयनित नहीं", - "changeNameToEdit": "अपडेट करने के लिए नाम बदलें" + "changeNameToEdit": "अपडेट करने के लिए नाम बदलें", + "selectTag": "टैग चुनें", + "collapse": "संक्षिप्त करें", + "expand": "विस्तारित करें", + "tagNamePlaceholder": "टैग का नाम लिखें" }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index 4e65b4f8d2..e301bde4d7 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -353,7 +353,11 @@ "tagUpdationSuccess": "Etiqueta actualizada con éxito", "tagRemovalSuccess": "Etiqueta eliminada con éxito", "noTagSelected": "Ninguna etiqueta seleccionada", - "changeNameToEdit": "Cambia el nombre para hacer una actualización" + "changeNameToEdit": "Cambia el nombre para hacer una actualización", + "selectTag": "Seleccionar etiqueta", + "collapse": "Colapsar", + "expand": "Expandir", + "tagNamePlaceholder": "Escribe el nombre de la etiqueta" }, "userListCard": { "joined": "Unido", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index d6fe494a04..07d1905ca8 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -353,7 +353,11 @@ "tagUpdationSuccess": "标签更新成功", "tagRemovalSuccess": "标签删除成功", "noTagSelected": "未选择标签", - "changeNameToEdit": "更改名称以进行更新" + "changeNameToEdit": "更改名称以进行更新", + "selectTag": "选择标签", + "collapse": "收起", + "expand": "展开", + "tagNamePlaceholder": "输入标签名称" }, "userListCard": { "addAdmin": "添加管理员", diff --git a/src/components/TagActions/TagActions.tsx b/src/components/TagActions/TagActions.tsx index b5c219aa3d..c1006c12a9 100644 --- a/src/components/TagActions/TagActions.tsx +++ b/src/components/TagActions/TagActions.tsx @@ -87,7 +87,7 @@ const TagActions: React.FC = ({ ); const [checkedTagId, setCheckedTagId] = useState(null); - const [uncheckedTagId, setUnheckedTagId] = useState(null); + const [uncheckedTagId, setUncheckedTagId] = useState(null); // tags that we have selected to assigned const [selectedTags, setSelectedTags] = useState([]); @@ -148,11 +148,11 @@ const TagActions: React.FC = ({ const addAncestorTags = (tagId: string): void => { setCheckedTagId(tagId); - setUnheckedTagId(null); + setUncheckedTagId(null); }; const removeAncestorTags = (tagId: string): void => { - setUnheckedTagId(tagId); + setUncheckedTagId(tagId); setCheckedTagId(null); }; @@ -315,13 +315,7 @@ const TagActions: React.FC = ({ : t('removeFromTags')} - - tagActionType === 'assignToTags' - ? handleTagAction(e) - : handleTagAction(e) - } - > + {orgUserTagsLoading ? ( @@ -341,10 +335,11 @@ const TagActions: React.FC = ({ className={`badge bg-dark-subtle text-secondary-emphasis lh-lg my-2 ms-2 d-flex align-items-center ${styles.tagBadge}`} > {tag.name} - deSelectTag(tag)} data-testid={`clearSelectedTag${tag._id}`} + aria-label={t('remove')} />
)) diff --git a/src/components/TagActions/TagActionsMocks.ts b/src/components/TagActions/TagActionsMocks.ts index 46ef0b85db..30fff78e3e 100644 --- a/src/components/TagActions/TagActionsMocks.ts +++ b/src/components/TagActions/TagActionsMocks.ts @@ -389,7 +389,7 @@ export const MOCKS = [ result: { data: { getUserTag: { - name: 'tag1', + name: 'userTag 1', childTags: { edges: [ { diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx index e419e22e05..4d2f6dbadd 100644 --- a/src/components/TagActions/TagNode.tsx +++ b/src/components/TagActions/TagNode.tsx @@ -11,7 +11,7 @@ import styles from './TagActions.module.css'; import InfiniteScroll from 'react-infinite-scroll-component'; /** - * Props for the `AssignToTags` component. + * Props for the `TagNode` component. */ interface InterfaceTagNodeProps { tag: InterfaceTagData; @@ -122,6 +122,7 @@ const TagNode: React.FC = ({ className="me-3" style={{ cursor: 'pointer' }} data-testid={`expandSubTags${tag._id}`} + aria-label={expanded ? t('collapse') : t('expand')} > {expanded ? '▼' : '▶'} @@ -132,6 +133,7 @@ const TagNode: React.FC = ({ className="me-2" onChange={handleCheckboxChange} data-testid={`checkTag${tag._id}`} + aria-label={t('selectTag')} /> {' '} @@ -145,6 +147,7 @@ const TagNode: React.FC = ({ className="ms-1 me-2" onChange={handleCheckboxChange} data-testid={`checkTag${tag._id}`} + aria-label={tag.name} /> {' '} diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 8099ca8754..05b45b1205 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -628,7 +628,7 @@ function ManageTag(): JSX.Element { {t('tagName')} Date: Mon, 28 Oct 2024 13:57:13 +0530 Subject: [PATCH 08/12] more changes --- public/locales/en/translation.json | 5 +++-- public/locales/fr/translation.json | 3 ++- public/locales/hi/translation.json | 3 ++- public/locales/sp/translation.json | 3 ++- public/locales/zh/translation.json | 3 ++- src/components/TagActions/TagActions.tsx | 4 ++-- src/components/TagActions/TagNode.tsx | 6 +----- src/screens/ManageTag/ManageTag.tsx | 2 +- 8 files changed, 15 insertions(+), 14 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 3b202bd593..0aaf9bbc03 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -345,7 +345,7 @@ "remove": "Remove", "successfullyAssignedToTags": "Successfully Assigned to Tags", "successfullyRemovedFromTags": "Successfully Removed from Tags", - "errorOccurredWhileLoadingOrganizationUserTags": "Error occured while loading organization tags", + "errorOccurredWhileLoadingOrganizationUserTags": "Error occurred while loading organization tags", "removeUserTag": "Delete Tag", "removeUserTagMessage": "Do you want to delete this tag? It delete all the sub tags and all the associations.", "tagDetails": "Tag Details", @@ -357,7 +357,8 @@ "selectTag": "Select Tag", "collapse": "Collapse", "expand": "Expand", - "tagNamePlaceholder": "Write the name of the tag" + "tagNamePlaceholder": "Write the name of the tag", + "allTags": "All Tags" }, "userListCard": { "addAdmin": "Add Admin", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 7b11e35f56..bcaa5e8333 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -357,7 +357,8 @@ "selectTag": "Sélectionner le tag", "collapse": "Réduire", "expand": "Développer", - "tagNamePlaceholder": "Écrire le nom du tag" + "tagNamePlaceholder": "Écrire le nom du tag", + "allTags": "Tous les tags" }, "userListCard": { "addAdmin": "Ajouter un administrateur", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index bab222175c..2d47e00566 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -357,7 +357,8 @@ "selectTag": "टैग चुनें", "collapse": "संक्षिप्त करें", "expand": "विस्तारित करें", - "tagNamePlaceholder": "टैग का नाम लिखें" + "tagNamePlaceholder": "टैग का नाम लिखें", + "allTags": "सभी टैग" }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index e301bde4d7..e47ee49105 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -357,7 +357,8 @@ "selectTag": "Seleccionar etiqueta", "collapse": "Colapsar", "expand": "Expandir", - "tagNamePlaceholder": "Escribe el nombre de la etiqueta" + "tagNamePlaceholder": "Escribe el nombre de la etiqueta", + "allTags": "Todas las etiquetas" }, "userListCard": { "joined": "Unido", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 07d1905ca8..c3a69273bb 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -357,7 +357,8 @@ "selectTag": "选择标签", "collapse": "收起", "expand": "展开", - "tagNamePlaceholder": "输入标签名称" + "tagNamePlaceholder": "输入标签名称", + "allTags": "所有标签" }, "userListCard": { "addAdmin": "添加管理员", diff --git a/src/components/TagActions/TagActions.tsx b/src/components/TagActions/TagActions.tsx index c1006c12a9..0c3246f16c 100644 --- a/src/components/TagActions/TagActions.tsx +++ b/src/components/TagActions/TagActions.tsx @@ -315,7 +315,7 @@ const TagActions: React.FC = ({ : t('removeFromTags')} - + {orgUserTagsLoading ? ( @@ -347,7 +347,7 @@ const TagActions: React.FC = ({
- All Tags + {t('allTags')}
= ({ ); const handleTagClick = (): void => { - if (!expanded) { - setExpanded(true); - } else { - setExpanded(false); // collapse on second click - } + setExpanded(!expanded); }; const handleCheckboxChange = ( diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 05b45b1205..884b402a7c 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -627,7 +627,7 @@ function ManageTag(): JSX.Element { {t('tagName')} Date: Mon, 28 Oct 2024 14:16:11 +0530 Subject: [PATCH 09/12] minor correction --- src/components/TagActions/TagNode.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx index ecc6514f3c..9a97efc62c 100644 --- a/src/components/TagActions/TagNode.tsx +++ b/src/components/TagActions/TagNode.tsx @@ -159,7 +159,7 @@ const TagNode: React.FC = ({
)} - {expanded && subTagsList && ( + {expanded && subTagsList?.length && (
Date: Mon, 28 Oct 2024 14:39:58 +0530 Subject: [PATCH 10/12] add error component for tagNode subtags query --- public/locales/en/translation.json | 1 + public/locales/fr/translation.json | 1 + public/locales/hi/translation.json | 1 + public/locales/sp/translation.json | 1 + public/locales/zh/translation.json | 1 + src/components/TagActions/TagActions.test.tsx | 27 ++++++- src/components/TagActions/TagActionsMocks.ts | 71 ++++++++++++++++++- src/components/TagActions/TagNode.tsx | 15 ++++ 8 files changed, 114 insertions(+), 4 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 0aaf9bbc03..7d2108bc06 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -346,6 +346,7 @@ "successfullyAssignedToTags": "Successfully Assigned to Tags", "successfullyRemovedFromTags": "Successfully Removed from Tags", "errorOccurredWhileLoadingOrganizationUserTags": "Error occurred while loading organization tags", + "errorOccurredWhileLoadingSubTags": "Error occurred while loading subTags tags", "removeUserTag": "Delete Tag", "removeUserTagMessage": "Do you want to delete this tag? It delete all the sub tags and all the associations.", "tagDetails": "Tag Details", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index bcaa5e8333..f01c2f8ad7 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -346,6 +346,7 @@ "successfullyAssignedToTags": "Attribué aux tags avec succès", "successfullyRemovedFromTags": "Retiré des tags avec succès", "errorOccurredWhileLoadingOrganizationUserTags": "Erreur lors du chargement des tags de l'organisation", + "errorOccurredWhileLoadingSubTags": "Une erreur s'est produite lors du chargement des sous-tags", "removeUserTag": "Supprimer le tag", "removeUserTagMessage": "Voulez-vous supprimer ce tag ? Cela supprimera tous les sous-tags et toutes les associations.", "tagDetails": "Détails du tag", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 2d47e00566..4384648ca3 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -346,6 +346,7 @@ "successfullyAssignedToTags": "सफलतापूर्वक टैग्स को असाइन किया गया", "successfullyRemovedFromTags": "सफलतापूर्वक टैग्स से हटाया गया", "errorOccurredWhileLoadingOrganizationUserTags": "संगठन टैग्स को लोड करते समय त्रुटि हुई", + "errorOccurredWhileLoadingSubTags": "उप-टैग लोड करते समय त्रुटि हुई", "removeUserTag": "टैग हटाएं", "removeUserTagMessage": "क्या आप इस टैग को हटाना चाहते हैं? यह सभी उप-टैग्स और सभी संबंधों को हटा देगा।", "tagDetails": "टैग विवरण", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index e47ee49105..24ce0dbdec 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -346,6 +346,7 @@ "successfullyAssignedToTags": "Asignado a etiquetas con éxito", "successfullyRemovedFromTags": "Eliminado de etiquetas con éxito", "errorOccurredWhileLoadingOrganizationUserTags": "Error al cargar las etiquetas de la organización", + "errorOccurredWhileLoadingSubTags": "Ocurrió un error al cargar las subetiquetas", "removeUserTag": "Eliminar etiqueta", "removeUserTagMessage": "¿Desea eliminar esta etiqueta? Esto eliminará todas las subetiquetas y todas las asociaciones.", "tagDetails": "Detalles de la etiqueta", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index c3a69273bb..0c070bcf8b 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -346,6 +346,7 @@ "successfullyAssignedToTags": "成功分配到标签", "successfullyRemovedFromTags": "成功从标签中移除", "errorOccurredWhileLoadingOrganizationUserTags": "加载组织标签时出错", + "errorOccurredWhileLoadingSubTags": "加载子标签时发生错误", "removeUserTag": "删除标签", "removeUserTagMessage": "您要删除此标签吗?这将删除所有子标签和所有关联。", "tagDetails": "标签详情", diff --git a/src/components/TagActions/TagActions.test.tsx b/src/components/TagActions/TagActions.test.tsx index 014748a913..39e287395b 100644 --- a/src/components/TagActions/TagActions.test.tsx +++ b/src/components/TagActions/TagActions.test.tsx @@ -22,10 +22,15 @@ import type { ApolloLink } from '@apollo/client'; import type { InterfaceTagActionsProps } from './TagActions'; import TagActions from './TagActions'; import i18n from 'utils/i18nForTest'; -import { MOCKS, MOCKS_ERROR } from './TagActionsMocks'; +import { + MOCKS, + MOCKS_ERROR_ORGANIZATION_TAGS_QUERY, + MOCKS_ERROR_SUBTAGS_QUERY, +} from './TagActionsMocks'; const link = new StaticMockLink(MOCKS, true); -const link2 = new StaticMockLink(MOCKS_ERROR, true); +const link2 = new StaticMockLink(MOCKS_ERROR_ORGANIZATION_TAGS_QUERY, true); +const link3 = new StaticMockLink(MOCKS_ERROR_SUBTAGS_QUERY, true); async function wait(ms = 500): Promise { await act(() => { @@ -132,6 +137,24 @@ describe('Organisation Tags Page', () => { }); }); + test('Renders error component when when subTags query is unsuccessful', async () => { + const { getByText } = renderTagActionsModal(props[0], link3); + + await wait(); + + // expand tag 1 to list its subtags + await waitFor(() => { + expect(screen.getByTestId('expandSubTags1')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('expandSubTags1')); + + await waitFor(() => { + expect( + getByText(translations.errorOccurredWhileLoadingSubTags), + ).toBeInTheDocument(); + }); + }); + test('Renders more members with infinite scroll', async () => { const { getByText } = renderTagActionsModal(props[0], link); diff --git a/src/components/TagActions/TagActionsMocks.ts b/src/components/TagActions/TagActionsMocks.ts index 30fff78e3e..4e3a08f184 100644 --- a/src/components/TagActions/TagActionsMocks.ts +++ b/src/components/TagActions/TagActionsMocks.ts @@ -452,7 +452,7 @@ export const MOCKS = [ }, ]; -export const MOCKS_ERROR = [ +export const MOCKS_ERROR_ORGANIZATION_TAGS_QUERY = [ { request: { query: ORGANIZATION_USER_TAGS_LIST, @@ -461,6 +461,73 @@ export const MOCKS_ERROR = [ first: TAGS_QUERY_LIMIT, }, }, - error: new Error('Mock Graphql Error'), + error: new Error('Mock Graphql Error for organization root tags query'), + }, +]; + +export const MOCKS_ERROR_SUBTAGS_QUERY = [ + { + request: { + query: ORGANIZATION_USER_TAGS_LIST, + variables: { + id: '123', + first: TAGS_QUERY_LIMIT, + }, + }, + result: { + data: { + organizations: [ + { + userTags: { + edges: [ + { + node: { + _id: '1', + name: 'userTag 1', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 11, + }, + }, + cursor: '1', + }, + { + node: { + _id: '2', + name: 'userTag 2', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 0, + }, + }, + cursor: '2', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '2', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + }, + ], + }, + }, + }, + { + request: { + query: USER_TAG_SUB_TAGS, + variables: { + id: '1', + first: TAGS_QUERY_LIMIT, + }, + }, + error: new Error('Mock Graphql Error for subTags query'), }, ]; diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx index 9a97efc62c..db08c3b451 100644 --- a/src/components/TagActions/TagNode.tsx +++ b/src/components/TagActions/TagNode.tsx @@ -9,6 +9,7 @@ import type { import { TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; import styles from './TagActions.module.css'; import InfiniteScroll from 'react-infinite-scroll-component'; +import { WarningAmberRounded } from '@mui/icons-material'; /** * Props for the `TagNode` component. @@ -34,6 +35,7 @@ const TagNode: React.FC = ({ const { data: subTagsData, loading: subTagsLoading, + error: subTagsError, fetchMore: fetchMoreSubTags, }: { data?: { @@ -62,6 +64,19 @@ const TagNode: React.FC = ({ skip: !expanded, }); + if (subTagsError) { + return ( +
+
+ +
+ {t('errorOccurredWhileLoadingSubTags')} +
+
+
+ ); + } + const subTagsList = subTagsData?.getUserTag.childTags.edges.map( (edge) => edge.node, ); From 052aead3c914341465f0f394cd297a3ac4bd3f0b Mon Sep 17 00:00:00 2001 From: meetul Date: Mon, 28 Oct 2024 14:52:20 +0530 Subject: [PATCH 11/12] fix translation --- public/locales/fr/translation.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index f01c2f8ad7..74689bc209 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -347,19 +347,19 @@ "successfullyRemovedFromTags": "Retiré des tags avec succès", "errorOccurredWhileLoadingOrganizationUserTags": "Erreur lors du chargement des tags de l'organisation", "errorOccurredWhileLoadingSubTags": "Une erreur s'est produite lors du chargement des sous-tags", - "removeUserTag": "Supprimer le tag", - "removeUserTagMessage": "Voulez-vous supprimer ce tag ? Cela supprimera tous les sous-tags et toutes les associations.", - "tagDetails": "Détails du tag", - "tagName": "Nom", + "removeUserTag": "Supprimer l'étiquette", + "removeUserTagMessage": "Voulez-vous supprimer cette étiquette ? Cela supprimera toutes les sous-étiquettes et toutes les associations.", + "tagDetails": "Détails de l'étiquette", + "tagName": "Nom de l'étiquette", "tagUpdationSuccess": "Étiquette mise à jour avec succès", "tagRemovalSuccess": "Étiquette supprimée avec succès", - "noTagSelected": "Aucun tag sélectionné", + "noTagSelected": "Aucune étiquette sélectionnée", "changeNameToEdit": "Modifiez le nom pour faire une mise à jour", - "selectTag": "Sélectionner le tag", + "selectTag": "Sélectionner l'étiquette", "collapse": "Réduire", "expand": "Développer", - "tagNamePlaceholder": "Écrire le nom du tag", - "allTags": "Tous les tags" + "tagNamePlaceholder": "Écrire le nom de l'étiquette", + "allTags": "Toutes les étiquettes" }, "userListCard": { "addAdmin": "Ajouter un administrateur", From d8d657a472e9d9c34c99485f9ead847cd54fb532 Mon Sep 17 00:00:00 2001 From: meetul Date: Mon, 28 Oct 2024 15:14:57 +0530 Subject: [PATCH 12/12] fix translation --- public/locales/fr/translation.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 74689bc209..7f74826272 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -339,14 +339,14 @@ "userName": "Nom d'utilisateur", "actions": "Actions", "noOneSelected": "Personne sélectionnée", - "assignToTags": "Attribuer aux tags", - "removeFromTags": "Retirer des tags", + "assignToTags": "Attribuer aux étiquettes", + "removeFromTags": "Retirer des étiquettes", "assign": "Attribuer", "remove": "Retirer", - "successfullyAssignedToTags": "Attribué aux tags avec succès", - "successfullyRemovedFromTags": "Retiré des tags avec succès", - "errorOccurredWhileLoadingOrganizationUserTags": "Erreur lors du chargement des tags de l'organisation", - "errorOccurredWhileLoadingSubTags": "Une erreur s'est produite lors du chargement des sous-tags", + "successfullyAssignedToTags": "Attribué aux étiquettes avec succès", + "successfullyRemovedFromTags": "Retiré des étiquettes avec succès", + "errorOccurredWhileLoadingOrganizationUserTags": "Erreur lors du chargement des étiquettes de l'organisation", + "errorOccurredWhileLoadingSubTags": "Une erreur s'est produite lors du chargement des sous-étiquettes", "removeUserTag": "Supprimer l'étiquette", "removeUserTagMessage": "Voulez-vous supprimer cette étiquette ? Cela supprimera toutes les sous-étiquettes et toutes les associations.", "tagDetails": "Détails de l'étiquette",