Skip to content
This repository was archived by the owner on Jan 23, 2025. It is now read-only.

feat: split upload csv endpoint #1537

Merged
merged 4 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cypress/e2e/item/share/shareItemFromCsv.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`)
Expand Down
9 changes: 3 additions & 6 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,9 @@ import {
mockGetItemLoginSchema,
mockGetItemLoginSchemaType,
mockGetItemMembershipsForItem,
mockGetItemTags,
mockGetItemThumbnailUrl,
mockGetItemValidationGroups,
mockGetItems,
mockGetItemsTags,
mockGetLatestValidationGroup,
mockGetLinkMetadata,
mockGetManyPublishItemInformations,
Expand Down Expand Up @@ -99,6 +97,7 @@ import {
mockUnpublishItem,
mockUpdatePassword,
mockUploadInvitationCSV,
mockUploadInvitationCSVWithTemplate,
mockUploadItem,
} from './server';

Expand Down Expand Up @@ -237,10 +236,6 @@ Cypress.Commands.add(

mockGetItemMembershipsForItem(items, currentMember);

mockGetItemTags(items);

mockGetItemsTags(items);

mockPostItemTag(cachedItems, currentMember, postItemTagError);

mockDeleteItemTag(deleteItemTagError);
Expand Down Expand Up @@ -318,6 +313,8 @@ Cypress.Commands.add(

mockUploadInvitationCSV(items, false);

mockUploadInvitationCSVWithTemplate(items, false);

mockGetPublicationStatus(itemPublicationStatus);
mockPublishItem(items);
mockUnpublishItem(items);
Expand Down
5 changes: 4 additions & 1 deletion cypress/support/commands/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
58 changes: 20 additions & 38 deletions cypress/support/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ const {
buildPostItemLoginSignInRoute,
buildGetItemLoginSchemaRoute,
buildGetItemMembershipsForItemsRoute,
buildGetItemTagsRoute,
buildPostItemTagRoute,
buildPatchCurrentMemberRoute,
buildEditItemMembershipRoute,
Expand All @@ -90,6 +89,7 @@ const {
buildGetItemInvitationsForItemRoute,
buildDeleteInvitationRoute,
buildPatchInvitationRoute,
buildPostUserCSVUploadWithTemplateRoute,
buildResendInvitationRoute,
buildPostUserCSVUploadRoute,
buildGetPublishedItemsForMemberRoute,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
43 changes: 16 additions & 27 deletions src/components/item/sharing/csvImport/DisplayInvitationSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -68,16 +66,14 @@ const DisplayInvitationSummary = ({
</Alert>
);
}
if (userCsvData) {
if (userCsvDataWithTemplate) {
// display group creation
if (Array.isArray(userCsvData)) {
return (
<Alert severity="info" id={SHARE_CSV_TEMPLATE_SUMMARY_CONTAINER_ID}>
<AlertTitle>
{t(BUILDER.SHARE_ITEM_CSV_SUMMARY_GROUP_TITLE)}
</AlertTitle>
<Stack direction="column" gap={2}>
{userCsvData.map(({ groupName, memberships, invitations }) => (
return (
<Alert severity="info" id={SHARE_CSV_TEMPLATE_SUMMARY_CONTAINER_ID}>
<AlertTitle>{t(BUILDER.SHARE_ITEM_CSV_SUMMARY_GROUP_TITLE)}</AlertTitle>
<Stack direction="column" gap={2}>
{userCsvDataWithTemplate.map(
({ groupName, memberships, invitations }) => (
<Stack>
<Typography fontWeight="bold">
{t(BUILDER.INVITATION_SUMMARY_GROUP_TITLE, { groupName })}
Expand All @@ -100,16 +96,9 @@ const DisplayInvitationSummary = ({
))}
</Stack>
</Stack>
))}
</Stack>
</Alert>
);
}

return (
<Alert severity="success">
<AlertTitle>{t(BUILDER.IMPORT_CSV_SUCCESS_TITLE)}</AlertTitle>
<Typography>{t(BUILDER.IMPORT_CSV_SUCCESS_TEXT)}</Typography>
),
)}
</Stack>
</Alert>
);
}
Expand Down
49 changes: 34 additions & 15 deletions src/components/item/sharing/csvImport/ImportUsersDialogContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -97,12 +99,16 @@ const ImportUsersDialogContent = ({
const [selectedTemplateId, setSelectedTemplateId] = useState<string>();
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<HTMLInputElement>) => {
if (target.files?.length) {
Expand Down Expand Up @@ -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');
}
Expand Down Expand Up @@ -196,29 +209,35 @@ const ImportUsersDialogContent = ({
/>
)}
<DisplayInvitationSummary
userCsvData={userCsvData}
error={userCSVError}
userCsvDataWithTemplate={userCsvDataWithTemplate}
error={userCSVErrorWithTemplate}
/>
{isSuccess && (
<Alert severity="success">
<AlertTitle>{t(BUILDER.IMPORT_CSV_SUCCESS_TITLE)}</AlertTitle>
<Typography>{t(BUILDER.IMPORT_CSV_SUCCESS_TEXT)}</Typography>
</Alert>
)}
</Stack>
</DialogContent>
<DialogActions>
<Button
id={SHARE_ITEM_FROM_CSV_CANCEL_BUTTON_ID}
variant="text"
onClick={handleClose}
disabled={isSuccessPostingCSV}
disabled={isSuccess}
>
{translateCommon(COMMON.CANCEL_BUTTON)}
</Button>

<Button
id={SHARE_ITEM_FROM_CSV_CONFIRM_BUTTON_ID}
variant="contained"
onClick={isSuccessPostingCSV ? handleClose : handlePostUserCSV}
onClick={isSuccess ? handleClose : handlePostUserCSV}
color="primary"
disabled={!isConfirmButtonEnabled}
>
{isSuccessPostingCSV
{isSuccess
? translateCommon(COMMON.CLOSE_BUTTON)
: translateCommon(COMMON.CONFIRM_BUTTON)}
</Button>
Expand Down
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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

Expand Down Expand Up @@ -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"
Expand Down