diff --git a/apps/api/src/app/mapping/mapping.controller.ts b/apps/api/src/app/mapping/mapping.controller.ts index 1b566940e..97c55d459 100644 --- a/apps/api/src/app/mapping/mapping.controller.ts +++ b/apps/api/src/app/mapping/mapping.controller.ts @@ -13,10 +13,9 @@ import { DoMapping, GetMappings, FinalizeUpload, - ReanameFileHeadings, - DoMappingCommand, UpdateMappings, ValidateMapping, + ReanameFileHeadings, } from './usecases'; @Controller('/mapping') @@ -56,13 +55,11 @@ export class MappingController { ]); if (uploadInformation.status === UploadStatusEnum.UPLOADED) { - await this.doMapping.execute( - DoMappingCommand.create({ - headings: uploadInformation.headings, - _templateId: uploadInformation._templateId, - _uploadId: uploadId, - }) - ); + await this.doMapping.execute({ + headings: uploadInformation.headings, + _templateId: uploadInformation._templateId, + _uploadId: uploadId, + }); } return this.getMappings.execute(uploadId); diff --git a/apps/api/src/app/mapping/usecases/do-mapping/do-mapping.command.ts b/apps/api/src/app/mapping/usecases/do-mapping/do-mapping.command.ts index 726a5fced..8ae2dc58a 100644 --- a/apps/api/src/app/mapping/usecases/do-mapping/do-mapping.command.ts +++ b/apps/api/src/app/mapping/usecases/do-mapping/do-mapping.command.ts @@ -1,16 +1,5 @@ -import { IsArray, IsDefined, IsMongoId } from 'class-validator'; -import { BaseCommand } from '@shared/commands/base.command'; - -export class DoMappingCommand extends BaseCommand { - @IsDefined() - @IsMongoId() +export class DoMappingCommand { _uploadId: string; - - @IsDefined() - @IsMongoId() _templateId: string; - - @IsDefined() - @IsArray() headings: string[]; } diff --git a/apps/api/src/app/review/usecases/do-review/base-review.usecase.ts b/apps/api/src/app/review/usecases/do-review/base-review.usecase.ts index cab846e38..b6e6b2d3e 100644 --- a/apps/api/src/app/review/usecases/do-review/base-review.usecase.ts +++ b/apps/api/src/app/review/usecases/do-review/base-review.usecase.ts @@ -31,6 +31,7 @@ interface IBatchItem { interface IRunData { extra: any; uploadId: string; + headerRow: number; headings: string[]; csvFileStream: any; dataStream: Writable; @@ -392,6 +393,7 @@ export class BaseReview { headings, dateFormats, dataStream, + headerRow, uniqueCombinations, numberColumnHeadings, validationErrorMessages, @@ -408,7 +410,7 @@ export class BaseReview { totalRecords++; const record = results.data; - if (totalRecords >= 1) { + if (totalRecords > headerRow) { const recordObj: { checkRecord: Record; passRecord: Record; @@ -574,8 +576,9 @@ export class BaseReview { validator, uploadId, extra, - csvFileStream, + headerRow, dateFormats, + csvFileStream, uniqueCombinations, numberColumnHeadings, validationErrorMessages, @@ -598,7 +601,7 @@ export class BaseReview { checkRecord: Record; passRecord: Record; } = this.formatRecord({ headings, multiSelectColumnHeadings, record, numberColumnHeadings }); - if (recordsCount >= 1) { + if (recordsCount > headerRow) { const validationResultItem = this.validateRecord({ index: recordsCount, checkRecord: recordObj.checkRecord, diff --git a/apps/api/src/app/review/usecases/do-review/do-review.usecase.ts b/apps/api/src/app/review/usecases/do-review/do-review.usecase.ts index bddb1663f..b92211c42 100644 --- a/apps/api/src/app/review/usecases/do-review/do-review.usecase.ts +++ b/apps/api/src/app/review/usecases/do-review/do-review.usecase.ts @@ -139,6 +139,7 @@ export class DoReview extends BaseReview { numberColumnHeadings, multiSelectColumnHeadings, validationErrorMessages, + headerRow: uploadInfo.headerRow, }); await this.processBatches({ @@ -185,6 +186,7 @@ export class DoReview extends BaseReview { numberColumnHeadings, validationErrorMessages, multiSelectColumnHeadings, + headerRow: uploadInfo.headerRow, }); response.invalidRecords = invalidRecords; response.totalRecords = totalRecords; diff --git a/apps/api/src/app/upload/dtos/set-header.dto.ts b/apps/api/src/app/upload/dtos/set-header.dto.ts new file mode 100644 index 000000000..e960f84fb --- /dev/null +++ b/apps/api/src/app/upload/dtos/set-header.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional } from 'class-validator'; + +export class SetHeaderDto { + @ApiProperty({ + description: 'Index of the header row', + }) + @IsOptional() + @IsNumber() + index?: number; + + @ApiProperty({ + description: 'Headings of the header row', + type: [String], + }) + @IsArray() + headings: string[]; +} diff --git a/apps/api/src/app/upload/upload.controller.ts b/apps/api/src/app/upload/upload.controller.ts index 1652890ae..ec46a3b7a 100644 --- a/apps/api/src/app/upload/upload.controller.ts +++ b/apps/api/src/app/upload/upload.controller.ts @@ -11,6 +11,7 @@ import { Get, Param, Post, + Put, Query, Res, UploadedFile, @@ -21,15 +22,18 @@ import { ApiTags, ApiSecurity, ApiConsumes, ApiOperation, ApiParam, ApiQuery, Ap import { GetUpload, GetAsset } from './usecases'; import { JwtAuthGuard } from '@shared/framework/auth.gaurd'; -import { getAssetMimeType, validateNotFound } from '@shared/helpers/common.helper'; import { validateUploadStatus } from '@shared/helpers/upload.helpers'; import { PaginationResponseDto } from '@shared/dtos/pagination-response.dto'; import { ValidateMongoId } from '@shared/validations/valid-mongo-id.validation'; import { ValidateTemplate } from '@shared/validations/valid-template.validation'; import { ValidImportFile } from '@shared/validations/valid-import-file.validation'; +import { getAssetMimeType, validateNotFound } from '@shared/helpers/common.helper'; +import { SetHeaderDto } from './dtos/set-header.dto'; import { UploadRequestDto } from './dtos/upload-request.dto'; import { + SetHeaderRow, + GetPreviewRows, TerminateUpload, MakeUploadEntry, GetUploadColumns, @@ -46,6 +50,8 @@ export class UploadController { constructor( private getAsset: GetAsset, private getUpload: GetUpload, + private setHeaderRow: SetHeaderRow, + private getPreviewRows: GetPreviewRows, private terminateUpload: TerminateUpload, private makeUploadEntry: MakeUploadEntry, private getUploadColumns: GetUploadColumns, @@ -151,6 +157,27 @@ export class UploadController { content.pipe(res); } + @Get(':uploadId/preview') + @ApiOperation({ + summary: 'Get rows of the uploaded file', + }) + async getPreviewRowsRoute(@Param('uploadId') uploadId: string) { + const uploadData = await this.getUploadProcessInfo.execute(uploadId); + + // throw error if upload information not found + validateNotFound(uploadData, 'upload'); + + return this.getPreviewRows.execute((uploadData._uploadedFileId as unknown as FileEntity).path); + } + + @Put(':uploadId/header') + @ApiOperation({ + summary: 'Set header row of the uploaded file', + }) + async setHeaderRowRoute(@Param('uploadId') uploadId: string, @Body() body: SetHeaderDto) { + await this.setHeaderRow.execute(uploadId, body); + } + @Get(':uploadId/rows/valid') @ApiOperation({ summary: 'Get valid rows of the uploaded file', diff --git a/apps/api/src/app/upload/usecases/get-preview-rows/get-preview-rows.usecase.ts b/apps/api/src/app/upload/usecases/get-preview-rows/get-preview-rows.usecase.ts new file mode 100644 index 000000000..b3485917e --- /dev/null +++ b/apps/api/src/app/upload/usecases/get-preview-rows/get-preview-rows.usecase.ts @@ -0,0 +1,26 @@ +import * as Papa from 'papaparse'; +import { Injectable } from '@nestjs/common'; +import { StorageService } from '@impler/services'; + +@Injectable() +export class GetPreviewRows { + constructor(private storageServie: StorageService) {} + + async execute(_uploadedFilePath: string) { + const csvFileStream = await this.storageServie.getFileStream(_uploadedFilePath); + + return new Promise((resolve, reject) => { + Papa.parse(csvFileStream, { + dynamicTyping: false, + skipEmptyLines: true, + preview: 15, + complete({ data }) { + resolve(data); + }, + error(error) { + reject(error); + }, + }); + }); + } +} diff --git a/apps/api/src/app/upload/usecases/index.ts b/apps/api/src/app/upload/usecases/index.ts index 9da9548ca..b2dd20b13 100644 --- a/apps/api/src/app/upload/usecases/index.ts +++ b/apps/api/src/app/upload/usecases/index.ts @@ -1,6 +1,8 @@ import { GetAsset } from './get-asset/get-asset.usecase'; import { GetUploadColumns } from './get-columns/get-columns.usecase'; +import { SetHeaderRow } from './set-header-row/set-header-row.usecase'; import { GetUpload } from '@shared/usecases/get-upload/get-upload.usecase'; +import { GetPreviewRows } from './get-preview-rows/get-preview-rows.usecase'; import { TerminateUpload } from './terminate-upload/terminate-upload.usecase'; import { MakeUploadEntry } from './make-upload-entry/make-upload-entry.usecase'; import { PaginateFileContent } from './paginate-file-content/paginate-file-content.usecase'; @@ -8,8 +10,10 @@ import { GetOriginalFileContent } from './get-original-file-content/get-original import { GetUploadProcessInformation } from './get-upload-process-info/get-upload-process-info.usecase'; export const USE_CASES = [ + GetPreviewRows, GetAsset, GetUpload, + SetHeaderRow, TerminateUpload, MakeUploadEntry, GetUploadColumns, @@ -20,8 +24,10 @@ export const USE_CASES = [ ]; export { + GetPreviewRows, GetAsset, GetUpload, + SetHeaderRow, MakeUploadEntry, TerminateUpload, GetUploadColumns, diff --git a/apps/api/src/app/upload/usecases/set-header-row/set-header-row.usecase.ts b/apps/api/src/app/upload/usecases/set-header-row/set-header-row.usecase.ts new file mode 100644 index 000000000..5bc43fe0f --- /dev/null +++ b/apps/api/src/app/upload/usecases/set-header-row/set-header-row.usecase.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@nestjs/common'; +import { UploadRepository } from '@impler/dal'; +import { ISetHeaderData } from '@impler/shared'; + +@Injectable() +export class SetHeaderRow { + constructor(private uploadRepository: UploadRepository) {} + + async execute(_uploadId: string, data: ISetHeaderData) { + return this.uploadRepository.update( + { _id: _uploadId }, + { + $set: { + headings: data.headings, + headerRow: typeof data.index !== 'undefined' ? data.index : -1, + }, + } + ); + } +} diff --git a/apps/widget/src/components/Common/Container/Container.tsx b/apps/widget/src/components/Common/Container/Container.tsx index d108a55e7..0e9e46510 100644 --- a/apps/widget/src/components/Common/Container/Container.tsx +++ b/apps/widget/src/components/Common/Container/Container.tsx @@ -208,9 +208,9 @@ export function Container({ children }: PropsWithChildren<{}>) { styles: { root: { borderRadius: 'var(--border-radius)', - backgroundColor: `var(--secondary-background)`, + backgroundColor: `var(--stepper-background)`, '&:hover': { - backgroundColor: `var(--secondary-background-hover)`, + backgroundColor: `var(--secondary-background)`, }, }, }, diff --git a/apps/widget/src/components/Common/Footer/Footer.tsx b/apps/widget/src/components/Common/Footer/Footer.tsx index 7177df030..8850de555 100644 --- a/apps/widget/src/components/Common/Footer/Footer.tsx +++ b/apps/widget/src/components/Common/Footer/Footer.tsx @@ -85,6 +85,21 @@ export function Footer({ {texts['PHASE0-1'].GENERATE_TEMPLATE} ), + [PhasesEnum.SELECT_HEADER]: ( + <> + + + + ), [PhasesEnum.MAPPING]: ( <>