Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat sheet select modal #512

Merged
merged 5 commits into from
Feb 27, 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
30 changes: 27 additions & 3 deletions apps/api/src/app/common/common.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import { Body, Controller, Post, Get, UseGuards, Query, BadRequestException } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiSecurity, ApiExcludeEndpoint } from '@nestjs/swagger';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiTags, ApiOperation, ApiSecurity, ApiExcludeEndpoint, ApiConsumes } from '@nestjs/swagger';
import {
Body,
Controller,
Post,
Get,
UseGuards,
Query,
BadRequestException,
UseInterceptors,
UploadedFile,
} from '@nestjs/common';

import { ACCESS_KEY_NAME } from '@impler/shared';
import { JwtAuthGuard } from '@shared/framework/auth.gaurd';
import { ValidRequestDto, SignedUrlDto, ImportConfigResponseDto } from './dtos';
import { ValidRequestCommand, GetSignedUrl, ValidRequest, GetImportConfig } from './usecases';
import { ValidImportFile } from '@shared/validations/valid-import-file.validation';
import { ValidRequestCommand, GetSignedUrl, ValidRequest, GetImportConfig, GetSheetNames } from './usecases';

@ApiTags('Common')
@Controller('/common')
Expand All @@ -13,6 +26,7 @@ export class CommonController {
constructor(
private validRequest: ValidRequest,
private getSignedUrl: GetSignedUrl,
private getSheetNames: GetSheetNames,
private getImportConfig: GetImportConfig
) {}

Expand Down Expand Up @@ -50,4 +64,14 @@ export class CommonController {

return this.getImportConfig.execute(projectId);
}

@Post('/sheet-names')
@ApiOperation({
summary: 'Get sheet names for user selected file',
})
@ApiConsumes('multipart/form-data')
@UseInterceptors(FileInterceptor('file'))
async getSheetNamesRoute(@UploadedFile('file', ValidImportFile) file: Express.Multer.File): Promise<string[]> {
return this.getSheetNames.execute({ file });
}
}
1 change: 1 addition & 0 deletions apps/api/src/app/common/dtos/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { SignedUrlDto } from './signed-url.dto';
export { ValidRequestDto } from './valid.dto';
export { SheetNamesDto } from './sheet-names.dto';
export { ImportConfigResponseDto } from './import-config-response.dto';
9 changes: 9 additions & 0 deletions apps/api/src/app/common/dtos/sheet-names.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';

export class SheetNamesDto {
@ApiProperty({
type: 'file',
required: true,
})
file: Express.Multer.File;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { IsDefined } from 'class-validator';
import { BaseCommand } from '@shared/commands/base.command';

export class GetSheetNamesCommand extends BaseCommand {
@IsDefined()
file: Express.Multer.File;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Injectable } from '@nestjs/common';
import { FileMimeTypesEnum } from '@impler/shared';

import { CONSTANTS } from '@shared/constants';
import { ExcelFileService } from '@shared/services/file';
import { GetSheetNamesCommand } from './get-sheet-names.command';
import { FileParseException } from '@shared/exceptions/file-parse-issue.exception';

@Injectable()
export class GetSheetNames {
async execute({ file }: GetSheetNamesCommand): Promise<string[]> {
if (file.mimetype === FileMimeTypesEnum.EXCEL || file.mimetype === FileMimeTypesEnum.EXCELX) {
try {
const fileService = new ExcelFileService();
const sheetNames = await fileService.getExcelSheets(file);

return sheetNames.filter((sheetName) => !sheetName.startsWith(CONSTANTS.EXCEL_DATA_SHEET_STARTER));
} catch (error) {
throw new FileParseException();
}
} else {
throw new Error('Invalid file type');
}
}
}
10 changes: 7 additions & 3 deletions apps/api/src/app/common/usecases/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { GetImportConfig } from './get-import-config/get-import-config.usecase';
import { ValidRequest } from './valid-request/valid-request.usecase';
import { GetSignedUrl } from './get-signed-url/get-signed-url.usecase';
import { GetSheetNames } from './get-sheet-names/get-sheet-names.usecase';
import { GetImportConfig } from './get-import-config/get-import-config.usecase';

import { ValidRequestCommand } from './valid-request/valid-request.command';
import { GetSheetNamesCommand } from './get-sheet-names/get-sheet-names.command';

export const USE_CASES = [
ValidRequest,
GetSignedUrl,
GetSheetNames,
GetImportConfig,
//
];

export { GetSignedUrl, ValidRequest, GetImportConfig };
export { ValidRequestCommand };
export { GetSignedUrl, ValidRequest, GetImportConfig, GetSheetNames };
export { ValidRequestCommand, GetSheetNamesCommand };
1 change: 1 addition & 0 deletions apps/api/src/app/shared/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const APIMessages = {

export const CONSTANTS = {
PASSWORD_SALT: 10,
EXCEL_DATA_SHEET_STARTER: 'imp_',
AUTH_COOKIE_NAME: 'authentication',
// eslint-disable-next-line no-magic-numbers
maxAge: 1000 * 60 * 60 * 24 * 1, // 1 day
Expand Down
19 changes: 15 additions & 4 deletions apps/api/src/app/shared/services/file/file.service.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import * as XLSX from 'xlsx';
import * as ExcelJS from 'exceljs';
import { ParseConfig, parse } from 'papaparse';
import { CONSTANTS } from '@shared/constants';
import { ColumnTypesEnum, Defaults, FileEncodingsEnum } from '@impler/shared';
import { EmptyFileException } from '@shared/exceptions/empty-file.exception';
import { InvalidFileException } from '@shared/exceptions/invalid-file.exception';
import { IExcelFileHeading } from '@shared/types/file.types';

export class ExcelFileService {
async convertToCsv(file: Express.Multer.File): Promise<string> {
async convertToCsv(file: Express.Multer.File, sheetName?: string): Promise<string> {
return new Promise(async (resolve, reject) => {
try {
const wb = XLSX.read(file.buffer);
const ws = wb.Sheets[wb.SheetNames[0]];
const ws = sheetName && wb.SheetNames.includes(sheetName) ? wb.Sheets[sheetName] : wb.Sheets[wb.SheetNames[0]];
resolve(
XLSX.utils.sheet_to_csv(ws, {
blankrows: false,
Expand All @@ -25,12 +26,12 @@ export class ExcelFileService {
});
}
formatName(name: string): string {
return name.replace(/[^a-zA-Z0-9]/g, '');
return CONSTANTS.EXCEL_DATA_SHEET_STARTER + name.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
}
addSelectSheet(wb: ExcelJS.Workbook, heading: IExcelFileHeading): string {
const name = this.formatName(heading.key);
const companies = wb.addWorksheet(name);
const companyHeadings = [name];
const companyHeadings = [heading.key];
companies.addRow(companyHeadings);
heading.selectValues.forEach((value) => companies.addRow([value]));

Expand Down Expand Up @@ -120,6 +121,16 @@ export class ExcelFileService {

return workbook.xlsx.writeBuffer() as Promise<Buffer>;
}
getExcelSheets(file: Express.Multer.File): Promise<string[]> {
return new Promise(async (resolve, reject) => {
try {
const wb = XLSX.read(file.buffer);
resolve(wb.SheetNames);
} catch (error) {
reject(error);
}
});
}
}

export class CSVFileService2 {
Expand Down
7 changes: 7 additions & 0 deletions apps/api/src/app/upload/dtos/upload-request.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,11 @@ export class UploadRequestDto {
@IsOptional()
@IsJSON()
extra: string;

@ApiProperty({
description: 'Name of the excel sheet to Import',
})
@IsOptional()
@IsString()
selectedSheetName: string;
}
9 changes: 5 additions & 4 deletions apps/api/src/app/upload/upload.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ import {
GetOriginalFileContent,
} from './usecases';

@Controller('/upload')
@ApiTags('Uploads')
@Controller('/upload')
@ApiSecurity(ACCESS_KEY_NAME)
@UseGuards(JwtAuthGuard)
export class UploadController {
Expand All @@ -68,18 +68,19 @@ export class UploadController {
@ApiConsumes('multipart/form-data')
@UseInterceptors(FileInterceptor('file'))
async uploadFile(
@UploadedFile('file', ValidImportFile) file: Express.Multer.File,
@Body() body: UploadRequestDto,
@Param('templateId', ValidateTemplate) templateId: string
@Param('templateId', ValidateTemplate) templateId: string,
@UploadedFile('file', ValidImportFile) file: Express.Multer.File
) {
return this.makeUploadEntry.execute(
MakeUploadEntryCommand.create({
file: file,
templateId,
extra: body.extra,
authHeaderValue: body.authHeaderValue,
schema: body.schema,
output: body.output,
authHeaderValue: body.authHeaderValue,
selectedSheetName: body.selectedSheetName,
})
);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/app/upload/usecases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const USE_CASES = [
];

export {
MakeUploadEntry,
GetUpload,
MakeUploadEntry,
TerminateUpload,
GetUploadColumns,
GetOriginalFileContent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ export class MakeUploadEntryCommand extends BaseCommand {
@IsOptional()
@IsString()
authHeaderValue?: string;

@IsString()
@IsOptional()
selectedSheetName?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,21 @@ export class MakeUploadEntry {
private customizationRepository: CustomizationRepository
) {}

async execute({ file, templateId, extra, authHeaderValue, schema, output }: MakeUploadEntryCommand) {
async execute({
file,
templateId,
extra,
authHeaderValue,
schema,
output,
selectedSheetName,
}: MakeUploadEntryCommand) {
const fileOriginalName = file.originalname;
let csvFile: string | Express.Multer.File = file;
if (file.mimetype === FileMimeTypesEnum.EXCEL || file.mimetype === FileMimeTypesEnum.EXCELX) {
try {
const fileService = new ExcelFileService();
csvFile = await fileService.convertToCsv(file);
csvFile = await fileService.convertToCsv(file, selectedSheetName);
} catch (error) {
throw new FileParseException();
}
Expand Down
30 changes: 0 additions & 30 deletions apps/widget/src/components/widget/Phases/ConfirmModal/Styles.tsx

This file was deleted.

28 changes: 21 additions & 7 deletions apps/widget/src/components/widget/Phases/Phase1/Phase1.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { TEXTS, variables } from '@config';
import { Group } from '@mantine/core';
import { Controller } from 'react-hook-form';

import { Download } from '@icons';
import { PhasesEnum } from '@types';
import { Select } from '@ui/Select';
import { Button } from '@ui/Button';
import { Dropzone } from '@ui/Dropzone';
import { TEXTS, variables } from '@config';
import { LoadingOverlay } from '@ui/LoadingOverlay';
import { Group } from '@mantine/core';
import { Download } from '@icons';
import { usePhase1 } from '@hooks/Phase1/usePhase1';

import useStyles from './Styles';
import { Footer } from 'components/Common/Footer';
import { usePhase1 } from '@hooks/Phase1/usePhase1';
import { Controller } from 'react-hook-form';
import { PhasesEnum } from '@types';
import { SheetSelectModal } from './SheetSelectModal';

interface IPhase1Props {
onNextClick: () => void;
Expand All @@ -21,14 +24,17 @@ export function Phase1(props: IPhase1Props) {
const {
onSubmit,
control,
setError,
templates,
onDownload,
setError,
excelSheetNames,
isUploadLoading,
onTemplateChange,
onSelectExcelSheet,
showSelectTemplate,
isInitialDataLoaded,
isDownloadInProgress,
onSelectSheetModalReset,
} = usePhase1({
goNext,
});
Expand Down Expand Up @@ -93,6 +99,14 @@ export function Phase1(props: IPhase1Props) {
)}
/>

<SheetSelectModal
control={control}
onSubmit={onSelectExcelSheet}
excelSheetNames={excelSheetNames}
opened={!!excelSheetNames.length}
onClose={onSelectSheetModalReset}
/>

<Footer
primaryButtonLoading={isUploadLoading}
onNextClick={onSubmit}
Expand Down
Loading
Loading