From 944102cdbe600a198e42b779dc20151e7c986982 Mon Sep 17 00:00:00 2001 From: Kim Lan Phan Hoang Date: Thu, 24 Oct 2024 14:00:08 +0200 Subject: [PATCH] feat: split upload csv endpoint (#1537) * feat: split upload csv endpoint * refactor: fix flacky tests * refactor: apply changes * refactor: fix test --- cypress/e2e/item/share/shareItemFromCsv.cy.ts | 2 +- cypress/support/commands.ts | 9 +-- cypress/support/commands/item.ts | 5 +- cypress/support/server.ts | 58 +++++++------------ package.json | 2 +- .../csvImport/DisplayInvitationSummary.tsx | 43 +++++--------- .../csvImport/ImportUsersDialogContent.tsx | 49 +++++++++++----- yarn.lock | 10 ++-- 8 files changed, 84 insertions(+), 94 deletions(-) diff --git a/cypress/e2e/item/share/shareItemFromCsv.cy.ts b/cypress/e2e/item/share/shareItemFromCsv.cy.ts index c3840b35c..8016be089 100644 --- a/cypress/e2e/item/share/shareItemFromCsv.cy.ts +++ b/cypress/e2e/item/share/shareItemFromCsv.cy.ts @@ -101,7 +101,7 @@ describe('Share Item From CSV', () => { ); cy.get(`#${SHARE_ITEM_FROM_CSV_CONFIRM_BUTTON_ID}`).should('be.enabled'); cy.get(`#${SHARE_ITEM_FROM_CSV_CONFIRM_BUTTON_ID}`).click(); - cy.wait('@uploadCSV').then(({ request }) => { + cy.wait('@uploadCSVWithTemplate').then(({ request }) => { expect(request.query.templateId).equal(templateItemId); }); cy.get(`#${SHARE_CSV_TEMPLATE_SUMMARY_CONTAINER_ID}`) diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index c635288b2..6daf67f2b 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -48,11 +48,9 @@ import { mockGetItemLoginSchema, mockGetItemLoginSchemaType, mockGetItemMembershipsForItem, - mockGetItemTags, mockGetItemThumbnailUrl, mockGetItemValidationGroups, mockGetItems, - mockGetItemsTags, mockGetLatestValidationGroup, mockGetLinkMetadata, mockGetManyPublishItemInformations, @@ -99,6 +97,7 @@ import { mockUnpublishItem, mockUpdatePassword, mockUploadInvitationCSV, + mockUploadInvitationCSVWithTemplate, mockUploadItem, } from './server'; @@ -237,10 +236,6 @@ Cypress.Commands.add( mockGetItemMembershipsForItem(items, currentMember); - mockGetItemTags(items); - - mockGetItemsTags(items); - mockPostItemTag(cachedItems, currentMember, postItemTagError); mockDeleteItemTag(deleteItemTagError); @@ -318,6 +313,8 @@ Cypress.Commands.add( mockUploadInvitationCSV(items, false); + mockUploadInvitationCSVWithTemplate(items, false); + mockGetPublicationStatus(itemPublicationStatus); mockPublishItem(items); mockUnpublishItem(items); diff --git a/cypress/support/commands/item.ts b/cypress/support/commands/item.ts index cebadbb60..9e5b7c641 100644 --- a/cypress/support/commands/item.ts +++ b/cypress/support/commands/item.ts @@ -69,7 +69,10 @@ Cypress.Commands.add('clickTreeMenuItem', (value: string) => { Cypress.Commands.add( 'handleTreeMenu', (toItemPath, treeRootId = HOME_MODAL_ITEM_ID) => { - const ids = getParentsIdsFromPath(toItemPath); + const ids = + toItemPath === MY_GRAASP_ITEM_PATH + ? [] + : getParentsIdsFromPath(toItemPath); [MY_GRAASP_ITEM_PATH, ...ids].forEach((value, idx, array) => { cy.get(`#${treeRootId}`).then(($tree) => { diff --git a/cypress/support/server.ts b/cypress/support/server.ts index 065bad88c..40da63d7a 100644 --- a/cypress/support/server.ts +++ b/cypress/support/server.ts @@ -67,7 +67,6 @@ const { buildPostItemLoginSignInRoute, buildGetItemLoginSchemaRoute, buildGetItemMembershipsForItemsRoute, - buildGetItemTagsRoute, buildPostItemTagRoute, buildPatchCurrentMemberRoute, buildEditItemMembershipRoute, @@ -90,6 +89,7 @@ const { buildGetItemInvitationsForItemRoute, buildDeleteInvitationRoute, buildPatchInvitationRoute, + buildPostUserCSVUploadWithTemplateRoute, buildResendInvitationRoute, buildPostUserCSVUploadRoute, buildGetPublishedItemsForMemberRoute, @@ -1100,39 +1100,6 @@ export const mockDeleteItemMembershipForItem = (): void => { ).as('deleteItemMembership'); }; -export const mockGetItemTags = (items: ItemForTest[]): void => { - cy.intercept( - { - method: HttpMethod.Get, - url: new RegExp(`${API_HOST}/${buildGetItemTagsRoute(ID_FORMAT)}$`), - }, - ({ reply, url }) => { - const itemId = url.slice(API_HOST.length).split('/')[2]; - const result = items.find(({ id }) => id === itemId)?.tags || []; - reply(result); - }, - ).as('getItemTags'); -}; - -export const mockGetItemsTags = (items: ItemForTest[]): void => { - cy.intercept( - { - method: HttpMethod.Get, - url: `${API_HOST}/items/tags?id=*`, - }, - ({ reply, url }) => { - const ids = new URL(url).searchParams.getAll('id'); - const result = ids.map( - (itemId) => - items.find(({ id }) => id === itemId)?.tags || [ - { statusCode: StatusCodes.NOT_FOUND }, - ], - ); - reply(result); - }, - ).as('getItemsTags'); -}; - export const mockPostItemTag = ( items: ItemForTest[], currentMember: Member, @@ -1834,16 +1801,31 @@ export const mockUploadInvitationCSV = ( if (shouldThrowError) { return reply({ statusCode: StatusCodes.BAD_REQUEST }); } - const query = new URL(url).searchParams; - if (query.get('templateId')) { - return reply([{ groupName: 'A', memberships: [], invitations: [] }]); - } const itemId = url.split('/').at(-3); const item = items.find(({ id }) => id === itemId); return reply({ memberships: item.memberships }); }, ).as('uploadCSV'); }; +export const mockUploadInvitationCSVWithTemplate = ( + items: ItemForTest[], + shouldThrowError: boolean, +): void => { + cy.intercept( + { + method: HttpMethod.Post, + url: new RegExp( + `${API_HOST}/${buildPostUserCSVUploadWithTemplateRoute(ID_FORMAT)}`, + ), + }, + ({ reply }) => { + if (shouldThrowError) { + return reply({ statusCode: StatusCodes.BAD_REQUEST }); + } + return reply([{ groupName: 'A', memberships: [], invitations: [] }]); + }, + ).as('uploadCSVWithTemplate'); +}; export const mockGetPublicationStatus = (status: PublicationStatus): void => { const interceptingPathFormat = buildGetPublicationStatusRoute(ID_FORMAT); diff --git a/package.json b/package.json index ac28e3639..216b57705 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@emotion/styled": "11.13.0", "@graasp/chatbox": "3.3.0", "@graasp/map": "1.19.0", - "@graasp/query-client": "4.2.0", + "@graasp/query-client": "5.0.0", "@graasp/sdk": "4.32.1", "@graasp/stylis-plugin-rtl": "2.2.0", "@graasp/translations": "1.39.0", diff --git a/src/components/item/sharing/csvImport/DisplayInvitationSummary.tsx b/src/components/item/sharing/csvImport/DisplayInvitationSummary.tsx index 88e9ec1ef..3a38a8449 100644 --- a/src/components/item/sharing/csvImport/DisplayInvitationSummary.tsx +++ b/src/components/item/sharing/csvImport/DisplayInvitationSummary.tsx @@ -42,17 +42,15 @@ const LineDisplay = ({ ); type Props = { - userCsvData?: - | { memberships: ItemMembership[]; invitations: Invitation[] } - | { - groupName: string; - memberships: ItemMembership[]; - invitations: Invitation[]; - }[]; + userCsvDataWithTemplate?: { + groupName: string; + memberships: ItemMembership[]; + invitations: Invitation[]; + }[]; error: Error | null | AxiosError; }; const DisplayInvitationSummary = ({ - userCsvData, + userCsvDataWithTemplate, error, }: Props): JSX.Element | null => { const { t } = useBuilderTranslation(); @@ -68,16 +66,14 @@ const DisplayInvitationSummary = ({ ); } - if (userCsvData) { + if (userCsvDataWithTemplate) { // display group creation - if (Array.isArray(userCsvData)) { - return ( - - - {t(BUILDER.SHARE_ITEM_CSV_SUMMARY_GROUP_TITLE)} - - - {userCsvData.map(({ groupName, memberships, invitations }) => ( + return ( + + {t(BUILDER.SHARE_ITEM_CSV_SUMMARY_GROUP_TITLE)} + + {userCsvDataWithTemplate.map( + ({ groupName, memberships, invitations }) => ( {t(BUILDER.INVITATION_SUMMARY_GROUP_TITLE, { groupName })} @@ -100,16 +96,9 @@ const DisplayInvitationSummary = ({ ))} - ))} - - - ); - } - - return ( - - {t(BUILDER.IMPORT_CSV_SUCCESS_TITLE)} - {t(BUILDER.IMPORT_CSV_SUCCESS_TEXT)} + ), + )} + ); } diff --git a/src/components/item/sharing/csvImport/ImportUsersDialogContent.tsx b/src/components/item/sharing/csvImport/ImportUsersDialogContent.tsx index 86c483070..880e3756c 100644 --- a/src/components/item/sharing/csvImport/ImportUsersDialogContent.tsx +++ b/src/components/item/sharing/csvImport/ImportUsersDialogContent.tsx @@ -2,12 +2,14 @@ import { ChangeEvent, ChangeEventHandler, useState } from 'react'; import { Alert, + AlertTitle, Button, DialogActions, DialogContent, DialogContentText, DialogTitle, Stack, + Typography, } from '@mui/material'; import { DiscriminatedItem } from '@graasp/sdk'; @@ -97,12 +99,16 @@ const ImportUsersDialogContent = ({ const [selectedTemplateId, setSelectedTemplateId] = useState(); const [isConfirmButtonEnabled, setIsConfirmButtonEnabled] = useState(false); const { t } = useBuilderTranslation(); + const { mutate: postUserCsv, isSuccess: isSuccessPostingCSV } = + mutations.useCSVUserImport(); const { - mutate: postUserCsv, - data: userCsvData, - error: userCSVError, - isSuccess: isSuccessPostingCSV, - } = mutations.useCSVUserImport(); + mutate: postUserCsvWithTemplate, + data: userCsvDataWithTemplate, + error: userCSVErrorWithTemplate, + isSuccess: isSuccessPostingCSVWithTemplate, + } = mutations.useCSVUserImportWithTemplate(); + + const isSuccess = isSuccessPostingCSV || isSuccessPostingCSVWithTemplate; const handleFileChange = ({ target }: ChangeEvent) => { if (target.files?.length) { @@ -154,11 +160,18 @@ const ImportUsersDialogContent = ({ const handlePostUserCSV = () => { if (csvFile) { - postUserCsv({ - file: csvFile, - itemId: item.id, - templateItemId: selectedTemplateId, - }); + if (selectedTemplateId) { + postUserCsvWithTemplate({ + file: csvFile, + itemId: item.id, + templateItemId: selectedTemplateId, + }); + } else { + postUserCsv({ + file: csvFile, + itemId: item.id, + }); + } } else { console.error('no file set'); } @@ -196,9 +209,15 @@ const ImportUsersDialogContent = ({ /> )} + {isSuccess && ( + + {t(BUILDER.IMPORT_CSV_SUCCESS_TITLE)} + {t(BUILDER.IMPORT_CSV_SUCCESS_TEXT)} + + )} @@ -206,7 +225,7 @@ const ImportUsersDialogContent = ({ id={SHARE_ITEM_FROM_CSV_CANCEL_BUTTON_ID} variant="text" onClick={handleClose} - disabled={isSuccessPostingCSV} + disabled={isSuccess} > {translateCommon(COMMON.CANCEL_BUTTON)} @@ -214,11 +233,11 @@ const ImportUsersDialogContent = ({ diff --git a/yarn.lock b/yarn.lock index 71d675b86..e3502eed7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1637,9 +1637,9 @@ __metadata: languageName: node linkType: hard -"@graasp/query-client@npm:4.2.0": - version: 4.2.0 - resolution: "@graasp/query-client@npm:4.2.0" +"@graasp/query-client@npm:5.0.0": + version: 5.0.0 + resolution: "@graasp/query-client@npm:5.0.0" dependencies: "@tanstack/react-query": "npm:5.59.8" "@tanstack/react-query-devtools": "npm:5.59.8" @@ -1649,7 +1649,7 @@ __metadata: "@graasp/sdk": ^4.0.0 "@graasp/translations": "*" react: ^18.0.0 - checksum: 10/fdf06272486d4c9979459c00f7cb0d9f98c027deef0c948ff734679b46435658076aeede49d54730922f0d12e572cf9d3bac045e346ab38629a15fab6b30d00d + checksum: 10/463dd556081d76fb2cb4bcf69b595560eb40ff528de5b1c64f189af106209bf394135d400374c80c031a57b0e3a13fdedd12d4d86988a4533b49d37c405a662a languageName: node linkType: hard @@ -6472,7 +6472,7 @@ __metadata: "@emotion/styled": "npm:11.13.0" "@graasp/chatbox": "npm:3.3.0" "@graasp/map": "npm:1.19.0" - "@graasp/query-client": "npm:4.2.0" + "@graasp/query-client": "npm:5.0.0" "@graasp/sdk": "npm:4.32.1" "@graasp/stylis-plugin-rtl": "npm:2.2.0" "@graasp/translations": "npm:1.39.0"