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 imported data on frontend #686

Merged
merged 7 commits into from
Jul 15, 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
21 changes: 8 additions & 13 deletions apps/api/src/app/mapping/mapping.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { validateNotFound } from '@shared/helpers/common.helper';
import { validateUploadStatus } from '@shared/helpers/upload.helpers';
import { GetUpload } from '@shared/usecases/get-upload/get-upload.usecase';
import { ValidateMongoId } from '@shared/validations/valid-mongo-id.validation';
import { GetUploadCommand } from '@shared/usecases/get-upload/get-upload.command';

import { UpdateMappingDto } from './dtos/update-columns.dto';
import {
Expand Down Expand Up @@ -42,12 +41,10 @@ export class MappingController {
async getMappingInformation(
@Param('uploadId', ValidateMongoId) uploadId: string
): Promise<Partial<ITemplateSchemaItem>[]> {
const uploadInformation = await this.getUpload.execute(
GetUploadCommand.create({
uploadId,
select: 'status headings _templateId',
})
);
const uploadInformation = await this.getUpload.execute({
uploadId,
select: 'status headings _templateId',
});

// throw error if upload information not found
validateNotFound(uploadInformation, 'upload');
Expand Down Expand Up @@ -80,12 +77,10 @@ export class MappingController {
@Param('uploadId', ValidateMongoId) _uploadId: string,
@Body(new ParseArrayPipe({ items: UpdateMappingDto, optional: true })) body: UpdateMappingDto[]
) {
const uploadInformation = await this.getUpload.execute(
GetUploadCommand.create({
uploadId: _uploadId,
select: 'status customSchema headings',
})
);
const uploadInformation = await this.getUpload.execute({
uploadId: _uploadId,
select: 'status customSchema headings',
});

// throw error if upload information not found
validateNotFound(uploadInformation, 'upload');
Expand Down
14 changes: 5 additions & 9 deletions apps/api/src/app/review/review.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ApiOperation, ApiTags, ApiSecurity, ApiQuery, ApiOkResponse } from '@nestjs/swagger';
import { BadRequestException, Body, Controller, Delete, Get, Param, Post, Put, Query, UseGuards } from '@nestjs/common';

import { UploadEntity } from '@impler/dal';
import { APIMessages } from '@shared/constants';
import { JwtAuthGuard } from '@shared/framework/auth.gaurd';
import { validateUploadStatus } from '@shared/helpers/upload.helpers';
Expand All @@ -23,7 +22,6 @@ import { UpdateCellDto } from './dtos/update-cell.dto';
import { validateNotFound } from '@shared/helpers/common.helper';
import { PaginationResponseDto } from '@shared/dtos/pagination-response.dto';
import { ValidateMongoId } from '@shared/validations/valid-mongo-id.validation';
import { GetUploadCommand } from '@shared/usecases/get-upload/get-upload.command';
import { ValidateIndexes } from '@shared/validations/valid-indexes.validation';

@Controller('/review')
Expand Down Expand Up @@ -114,13 +112,11 @@ export class ReviewController {
@ApiOperation({
summary: 'Confirm review data for uploaded file',
})
async doConfirmReview(@Param('uploadId', ValidateMongoId) _uploadId: string): Promise<UploadEntity> {
const uploadInformation = await this.getUpload.execute(
GetUploadCommand.create({
uploadId: _uploadId,
select: 'status _validDataFileId _invalidDataFileId totalRecords invalidRecords _templateId',
})
);
async doConfirmReview(@Param('uploadId', ValidateMongoId) _uploadId: string) {
const uploadInformation = await this.getUpload.execute({
uploadId: _uploadId,
select: 'status _validDataFileId _invalidDataFileId totalRecords invalidRecords _templateId',
});

// throw error if upload information not found
validateNotFound(uploadInformation, 'upload');
Expand Down

This file was deleted.

This file was deleted.

3 changes: 0 additions & 3 deletions apps/api/src/app/review/usecases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { UpdateRecord } from './update-cell/update-cell.usecase';
import { DeleteRecord } from './delete-record/delete-record.usecase';
import { DoReReview } from './do-review/re-review-data.usecase';
import { StartProcess } from './start-process/start-process.usecase';
import { ConfirmReview } from './confirm-review/confirm-review.usecase';
import { GetUpload } from '@shared/usecases/get-upload/get-upload.usecase';
import { UpdateImportCount } from './update-import-count/update-import-count.usecase';
import { GetUploadData } from './get-upload-data/get-upload-data.usecase';
Expand All @@ -17,7 +16,6 @@ export const USE_CASES = [
DeleteRecord,
UpdateRecord,
StartProcess,
ConfirmReview,
GetUploadData,
UpdateImportCount,
PaymentAPIService,
Expand All @@ -30,7 +28,6 @@ export {
DeleteRecord,
UpdateRecord,
StartProcess,
ConfirmReview,
GetUploadData,
UpdateImportCount,
PaymentAPIService,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,100 @@
import { Injectable } from '@nestjs/common';
import { QueuesEnum, UploadStatusEnum } from '@impler/shared';
import {
ColumnTypesEnum,
PaymentAPIService,
ITemplateSchemaItem,
QueuesEnum,
UploadStatusEnum,
replaceVariablesInObject,
DestinationsEnum,
} from '@impler/shared';
import { QueueService } from '@shared/services/queue.service';
import { TemplateEntity, UploadEntity, UploadRepository } from '@impler/dal';
import { DalService, TemplateEntity, UploadRepository } from '@impler/dal';

@Injectable()
export class StartProcess {
constructor(
private dalService: DalService,
private queueService: QueueService,
private uploadRepository: UploadRepository,
private queueService: QueueService
private paymentAPIService: PaymentAPIService
) {}

async execute(_uploadId: string): Promise<UploadEntity> {
let upload = await this.uploadRepository.getUploadWithTemplate(_uploadId, ['destination']);
const destination = (upload._templateId as unknown as TemplateEntity)?.destination;
async execute(_uploadId: string) {
let uploadInfo = await this.uploadRepository.getUploadWithTemplate(_uploadId, ['destination']);
let importedData;
const destination = (uploadInfo._templateId as unknown as TemplateEntity)?.destination;
const userEmail = await this.uploadRepository.getUserEmailFromUploadId(_uploadId);
const dataProcessingAllowed = await this.paymentAPIService.checkEvent(userEmail);

if (
dataProcessingAllowed &&
(uploadInfo._templateId as unknown as TemplateEntity).destination === DestinationsEnum.FRONTEND
) {
importedData = await this.getImportedData({
uploadId: _uploadId,
customSchema: uploadInfo.customSchema,
recordFormat: uploadInfo.customRecordFormat,
});
}

// if template destination has callbackUrl then start sending data to the callbackUrl
if (destination) {
upload = await this.uploadRepository.findOneAndUpdate(
uploadInfo = await this.uploadRepository.findOneAndUpdate(
{ _id: _uploadId },
{ status: UploadStatusEnum.PROCESSING }
);
} else {
// else complete the upload process
upload = await this.uploadRepository.findOneAndUpdate({ _id: _uploadId }, { status: UploadStatusEnum.COMPLETED });
uploadInfo = await this.uploadRepository.findOneAndUpdate(
{ _id: _uploadId },
{ status: UploadStatusEnum.COMPLETED }
);
}
this.queueService.publishToQueue(QueuesEnum.END_IMPORT, {
uploadId: _uploadId,
destination: destination,
});

return upload;
return { uploadInfo, importedData };
}

async getImportedData({
uploadId,
customSchema,
recordFormat,
}: {
uploadId: string;
customSchema: string;
recordFormat?: string;
}) {
let importedData: any[] = await this.dalService.getAllRecords(uploadId);
const defaultValuesObj = {};
const multiSelectHeadings = {};
const parsesdCustomSchema = JSON.parse(customSchema) as ITemplateSchemaItem[];
if (Array.isArray(parsesdCustomSchema)) {
parsesdCustomSchema.forEach((item: ITemplateSchemaItem) => {
if (item.defaultValue) defaultValuesObj[item.key] = item.defaultValue;
if (item.type === ColumnTypesEnum.SELECT && item.allowMultiSelect)
multiSelectHeadings[item.key] = item.delimiter;
});
}

if (multiSelectHeadings && Object.keys(multiSelectHeadings).length > 0) {
importedData = importedData.map((obj) => {
Object.keys(multiSelectHeadings).forEach((heading) => {
obj.record[heading] = obj.record[heading] ? obj.record[heading].split(multiSelectHeadings[heading]) : [];
});

return obj;
});
}
if (recordFormat)
importedData = importedData.map((obj) =>
replaceVariablesInObject(JSON.parse(recordFormat), obj.record, defaultValuesObj)
);
else importedData = importedData.map((obj) => obj.record);

return importedData;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import { IsDefined, IsMongoId, IsOptional, IsString } from 'class-validator';
import { BaseCommand } from '../../commands/base.command';

export class GetUploadCommand extends BaseCommand {
@IsDefined()
@IsMongoId()
export class GetUploadCommand {
uploadId: string;

@IsOptional()
@IsString()
select?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class UpdateCustomization {
combinedFormat,
internal: true,
});
} else if (destination == DestinationsEnum.BUBBLEIO) {
} else if (destination == DestinationsEnum.BUBBLEIO || destination == DestinationsEnum.FRONTEND) {
const recordFormat = createRecordFormat(recordVariables, CONSTANTS.BUBBLEIO_PROPS);

return this.execute(_templateId, {
Expand Down
15 changes: 6 additions & 9 deletions apps/api/src/app/upload/upload.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ import { validateUploadStatus } from '@shared/helpers/upload.helpers';
import { PaginationResponseDto } from '@shared/dtos/pagination-response.dto';
import { GetUpload } from '@shared/usecases/get-upload/get-upload.usecase';
import { ValidateMongoId } from '@shared/validations/valid-mongo-id.validation';
import { ValidImportFile } from '@shared/validations/valid-import-file.validation';
import { GetUploadCommand } from '@shared/usecases/get-upload/get-upload.command';
import { ValidateTemplate } from '@shared/validations/valid-template.validation';
import { ValidImportFile } from '@shared/validations/valid-import-file.validation';

import { UploadRequestDto } from './dtos/upload-request.dto';
import { MakeUploadEntryCommand } from './usecases/make-upload-entry/make-upload-entry.command';
Expand Down Expand Up @@ -90,20 +89,18 @@ export class UploadController {
summary: 'Get Upload information',
})
getUploadInformation(@Param('uploadId', ValidateMongoId) uploadId: string) {
return this.getUpload.execute(GetUploadCommand.create({ uploadId }));
return this.getUpload.execute({ uploadId });
}

@Get(':uploadId/headings')
@ApiOperation({
summary: 'Get headings for the uploaded file',
})
async getHeadings(@Param('uploadId', ValidateMongoId) uploadId: string): Promise<string[]> {
const uploadInfo = await this.getUpload.execute(
GetUploadCommand.create({
uploadId,
select: 'headings',
})
);
const uploadInfo = await this.getUpload.execute({
uploadId,
select: 'headings',
});

return uploadInfo.headings;
}
Expand Down
11 changes: 6 additions & 5 deletions apps/queue-manager/src/consumers/send-webhook-data.consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,10 @@ export class SendWebhookDataConsumer extends BaseConsumer {
Math.max((page - DEFAULT_PAGE) * chunkSize, MIN_LIMIT),
Math.min(page * chunkSize, data.length)
);
if (Array.isArray(multiSelectHeadings) && multiSelectHeadings.length > 0) {
if (multiSelectHeadings && Object.keys(multiSelectHeadings).length > 0) {
slicedData = slicedData.map((obj) => {
multiSelectHeadings.forEach((heading) => {
obj.record[heading] = obj.record[heading] ? obj.record[heading].split(',') : [];
Object.keys(multiSelectHeadings).forEach((heading) => {
obj.record[heading] = obj.record[heading] ? obj.record[heading].split(multiSelectHeadings[heading]) : [];
});

return obj;
Expand Down Expand Up @@ -166,12 +166,13 @@ export class SendWebhookDataConsumer extends BaseConsumer {
const webhookDestination = await this.webhookDestinationRepository.findOne({ _templateId: uploadata._templateId });

const defaultValueObj = {};
const multiSelectHeadings = [];
const multiSelectHeadings = {};
const customSchema = JSON.parse(uploadata.customSchema) as ITemplateSchemaItem[];
if (Array.isArray(customSchema)) {
customSchema.forEach((item: ITemplateSchemaItem) => {
if (item.defaultValue) defaultValueObj[item.key] = item.defaultValue;
if (item.type === ColumnTypesEnum.SELECT && item.allowMultiSelect) multiSelectHeadings.push(item.key);
if (item.type === ColumnTypesEnum.SELECT && item.allowMultiSelect)
multiSelectHeadings[item.key] = item.delimiter || ',';
});
}

Expand Down
2 changes: 1 addition & 1 deletion apps/queue-manager/src/types/file-processing.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface IBuildSendDataParameters extends IBaseSendDataParameters {
defaultValues: string;
recordFormat?: string;
chunkFormat?: string;
multiSelectHeadings?: string[];
multiSelectHeadings?: Record<string, string>;
}
export interface ISendDataResponse {
statusCode: number;
Expand Down
11 changes: 11 additions & 0 deletions apps/web/components/imports/destination/Destination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export function Destination({ template }: DestinationProps) {
destination,
setDestination,
resetDestination,
updateDestination,
mapBubbleIoColumnsClick,
isUpdateImportLoading,
isMapBubbleIoColumnsLoading,
Expand All @@ -38,12 +39,22 @@ export function Destination({ template }: DestinationProps) {
else {
setDestination(newDestination);
setValue('destination', newDestination);
if (newDestination === DestinationsEnum.FRONTEND) {
setDestination(newDestination);
updateDestination({ destination: DestinationsEnum.FRONTEND, webhook: undefined, bubbleIo: undefined });
}
}
};
const bubbleDestinationEnvironment = watch('bubbleIo.environment');

return (
<Stack>
<DestinationItem
title="Get Imported Data in Frontend"
subtitle="User imported data will be sent to frontend"
active={destination === DestinationsEnum.FRONTEND}
onClick={() => swithDestination(DestinationsEnum.FRONTEND)}
/>
<DestinationItem
title="Webhook"
subtitle="Provide webhook to receive data"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const DestinationItem = ({ title, subtitle, onClick, children, active }:
const { classes } = useStyles({ colorScheme });

return (
<Stack className={classes.container} p="lg">
<Stack className={classes.container} p="lg" spacing={children ? 'sm' : 0}>
<Flex justify="space-between" align="center">
<Stack spacing={0}>
<Title color={colorScheme === 'dark' ? colors.white : colors.black} order={4}>
Expand Down
4 changes: 4 additions & 0 deletions apps/web/components/imports/editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ const titles = {
subtitle:
'Following format represents the format of individual rows that will send to Bubble.io. You can mention dynamic properties too.',
},
[DestinationsEnum.FRONTEND]: {
title: 'Customize how you want to get in Frontend',
subtitle: 'Following format represents the format of individual rows that will send to Frontend.',
},
};

export function OutputEditor({ templateId, switchToDestination }: OutputEditorProps) {
Expand Down
1 change: 1 addition & 0 deletions apps/web/hooks/useDestination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export function useDestination({ template }: UseDestinationProps) {
destination,
setDestination,
resetDestination,
updateDestination,
mapBubbleIoColumns,
mapBubbleIoColumnsClick,
isMapBubbleIoColumnsLoading,
Expand Down
Loading
Loading