Skip to content

Commit

Permalink
feat duplicate journey (#486)
Browse files Browse the repository at this point in the history
closes #466
  • Loading branch information
chavda-bhavik authored Feb 2, 2024
2 parents 7c86106 + 3a27024 commit 334adfc
Show file tree
Hide file tree
Showing 23 changed files with 391 additions and 38 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/reusable-api-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ jobs:
- name: Install dependencies
run: pnpm install
env:
CI: false

# Runs a set of commands using the runners shell
- name: Run a test
Expand Down
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 @@ -9,6 +9,7 @@ export const APIMessages = {
INVALID_TEMPLATE_ID_CODE_SUFFIX: 'is not valid TemplateId or CODE.',
FILE_MAPPING_REMAINING: 'File mapping is not yet done, please finalize mapping before.',
UPLOAD_NOT_FOUND: 'Upload information not found with specified uploadId.',
TEMPLATE_NOT_FOUND: 'Template not found with specified templateId.',
FILE_NOT_FOUND_IN_STORAGE:
"File not found, make sure you're using the same storage provider, that you were using before.",
DO_MAPPING_FIRST: 'You may landed to wrong place, Please finalize mapping and proceed ahead.',
Expand Down
4 changes: 3 additions & 1 deletion apps/api/src/app/shared/helpers/common.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { BadRequestException } from '@nestjs/common';
import { APIMessages } from '../constants';
import { PaginationResult, Defaults } from '@impler/shared';

export function validateNotFound(data: any, entityName: 'upload'): boolean {
export function validateNotFound(data: any, entityName: 'upload' | 'template'): boolean {
if (data) return true;
else {
switch (entityName) {
case 'upload':
throw new BadRequestException(APIMessages.UPLOAD_NOT_FOUND);
case 'template':
throw new BadRequestException(APIMessages.TEMPLATE_NOT_FOUND);
default:
throw new BadRequestException();
}
Expand Down
46 changes: 46 additions & 0 deletions apps/api/src/app/template/dtos/duplicate-template-request.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsDefined, IsString, IsOptional, IsBoolean } from 'class-validator';

export class DuplicateTemplateRequestDto {
@ApiProperty({
description: 'Name of the template',
})
@IsDefined()
@IsString()
name: string;

@ApiProperty({
description: '_projectId where the template will be created',
})
@IsString()
@IsDefined()
_projectId: string;

@ApiProperty({
description: 'Whether to duplicate columns?',
})
@IsBoolean()
@IsOptional()
duplicateColumns?: boolean;

@ApiProperty({
description: 'Whether to duplicate output?',
})
@IsBoolean()
@IsOptional()
duplicateOutput?: boolean;

@ApiProperty({
description: 'Whether to duplicate webhook?',
})
@IsBoolean()
@IsOptional()
duplicateWebhook?: boolean;

@ApiProperty({
description: 'Whether to duplicate validation code?',
})
@IsBoolean()
@IsOptional()
duplicateValidator?: boolean;
}
18 changes: 18 additions & 0 deletions apps/api/src/app/template/template.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DocumentNotFoundException } from '@shared/exceptions/document-not-found

import {
GetUploads,
DuplicateTemplate,
GetTemplateColumns,
CreateTemplate,
DeleteTemplate,
Expand All @@ -27,6 +28,7 @@ import {
GetValidations,
DownloadSample,
UpdateValidations,
DuplicateTemplateCommand,
UpdateValidationsCommand,
} from './usecases';

Expand All @@ -38,6 +40,7 @@ import { ValidationsResponseDto } from './dtos/validations-response.dto';
import { CustomizationResponseDto } from './dtos/customization-response.dto';
import { CreateTemplateRequestDto } from './dtos/create-template-request.dto';
import { UpdateTemplateRequestDto } from './dtos/update-template-request.dto';
import { DuplicateTemplateRequestDto } from './dtos/duplicate-template-request.dto';
import { UpdateValidationResponseDto } from './dtos/update-validation-response.dto';
import { UpdateValidationsRequestDto } from './dtos/update-validations-request.dto';
import { UpdateCustomizationRequestDto } from './dtos/update-customization-request.dto';
Expand All @@ -54,6 +57,7 @@ export class TemplateController {
private getCustomization: GetCustomization,
private updateValidations: UpdateValidations,
private syncCustomization: SyncCustomization,
private duplicateTemplate: DuplicateTemplate,
private createTemplateUsecase: CreateTemplate,
private updateTemplateUsecase: UpdateTemplate,
private deleteTemplateUsecase: DeleteTemplate,
Expand Down Expand Up @@ -121,6 +125,20 @@ export class TemplateController {
);
}

@Post(':templateId/duplicate')
@ApiOperation({
summary: 'Duplicate template',
})
@ApiOkResponse({
type: TemplateResponseDto,
})
async duplicateTemplateRoute(
@Param('templateId', ValidateMongoId) templateId: string,
@Body() body: DuplicateTemplateRequestDto
): Promise<TemplateResponseDto> {
return this.duplicateTemplate.execute(templateId, DuplicateTemplateCommand.create(body));
}

@Put(':templateId')
@ApiOperation({
summary: 'Update template',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { IsBoolean, IsDefined, IsOptional, IsString } from 'class-validator';
import { BaseCommand } from '@shared/commands/base.command';

export class DuplicateTemplateCommand extends BaseCommand {
@IsDefined()
@IsString()
name: string;

@IsString()
@IsDefined()
_projectId: string;

@IsBoolean()
@IsOptional()
duplicateColumns?: boolean;

@IsBoolean()
@IsOptional()
duplicateOutput?: boolean;

@IsBoolean()
@IsOptional()
duplicateWebhook?: boolean;

@IsBoolean()
@IsOptional()
duplicateValidator?: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Injectable } from '@nestjs/common';
import { TemplateRepository, CustomizationRepository, ColumnRepository, ValidatorRepository } from '@impler/dal';
import { validateNotFound } from '@shared/helpers/common.helper';
import { DuplicateTemplateCommand } from './duplicate-template.command';
import { SaveSampleFile } from '@shared/usecases/save-sample-file/save-sample-file.usecase';

@Injectable()
export class DuplicateTemplate {
constructor(
private saveSampleFile: SaveSampleFile,
private columnRepository: ColumnRepository,
private templateRepository: TemplateRepository,
private validatorRepository: ValidatorRepository,
private customizationRepository: CustomizationRepository
) {}

async execute(_templateId: string, command: DuplicateTemplateCommand) {
const templateData = await this.templateRepository.findById(
_templateId,
'name callbackUrl chunkSize authHeaderName chunkSize'
);

validateNotFound(templateData, 'template');

const newTemplate = await this.templateRepository.create({
name: command.name,
_projectId: command._projectId,
chunkSize: templateData.chunkSize,
...(command.duplicateWebhook && {
callbackUrl: templateData.callbackUrl,
authHeaderName: templateData.authHeaderName,
}),
});

if (command.duplicateColumns) {
const columns = await this.columnRepository.find(
{
_templateId,
},
'-_id name key alternateKeys isRequired isUnique type regex regexDescription selectValues dateFormats sequence defaultValue'
);

await this.columnRepository.createMany(
columns.map((column) => {
return {
...column,
_templateId: newTemplate._id,
};
})
);

await this.saveSampleFile.execute(columns, newTemplate._id);
}

if (command.duplicateOutput) {
const validation = await this.customizationRepository.findOne(
{
_templateId,
},
'-_id recordVariables chunkVariables recordFormat chunkFormat combinedFormat isRecordFormatUpdated isChunkFormatUpdated isCombinedFormatUpdated'
);

await this.customizationRepository.create({
_templateId: newTemplate._id,
...validation,
});
}

if (command.duplicateValidator) {
const validator = await this.validatorRepository.findOne({ _templateId }, '-_id onBatchInitialize');
await this.validatorRepository.create({
_templateId: newTemplate._id,
...validator,
});
}

return newTemplate;
}
}
5 changes: 5 additions & 0 deletions apps/api/src/app/template/usecases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { GetValidations } from './get-validations/get-validations.usecase';
import { GetCustomization } from './get-customization/get-customization.usecase';
import { SyncCustomization } from './sync-customization/sync-customization.usecase';
import { UpdateValidations } from './update-validations/update-validations.usecase';
import { DuplicateTemplate } from './duplicate-template/duplicate-template.usecase';
import { GetTemplateDetails } from './get-template-details/get-template-details.usecase';
import { UpdateCustomization } from './update-customization/update-customization.usecase';
import { SaveSampleFile } from '@shared/usecases/save-sample-file/save-sample-file.usecase';
Expand All @@ -16,13 +17,15 @@ import { UpdateTemplateColumns } from './update-template-columns/update-template
import { GetUploadsCommand } from './get-uploads/get-uploads.command';
import { CreateTemplateCommand } from './create-template/create-template.command';
import { UpdateTemplateCommand } from './update-template/update-template.command';
import { DuplicateTemplateCommand } from './duplicate-template/duplicate-template.command';
import { UpdateValidationsCommand } from './update-validations/update-validations.command';
import { UpdateCustomizationCommand } from './update-customization/update-customization.command';

export const USE_CASES = [
CreateTemplate,
UpdateTemplate,
DeleteTemplate,
DuplicateTemplate,
GetTemplateDetails,
GetUploads,
SyncCustomization,
Expand All @@ -44,6 +47,7 @@ export {
SyncCustomization,
GetTemplateDetails,
GetUploads,
DuplicateTemplate,
GetTemplateColumns,
UpdateTemplateColumns,
UpdateCustomization,
Expand All @@ -57,5 +61,6 @@ export {
UpdateValidationsCommand,
UpdateTemplateCommand,
GetUploadsCommand,
DuplicateTemplateCommand,
UpdateCustomizationCommand,
};
3 changes: 2 additions & 1 deletion apps/web/assets/icons/Copy.icon.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { IconType } from '@types';
import { IconSizes } from 'config';

export const CopyIcon = ({ size = 'sm', color }: IconType) => {
export const CopyIcon = ({ size = 'sm', color, className }: IconType) => {
return (
<svg
width={IconSizes[size]}
height={IconSizes[size]}
color={color}
viewBox="0 0 20 20"
fill="none"
className={className}
xmlns="http://www.w3.org/2000/svg"
>
<path
Expand Down
64 changes: 64 additions & 0 deletions apps/web/components/imports/forms/DuplicateImportForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useForm } from 'react-hook-form';
import { useFocusTrap } from '@mantine/hooks';
import { SimpleGrid, Stack } from '@mantine/core';

import { Input } from '@ui/input';
import { Button } from '@ui/button';
import { Select } from '@ui/select';
import { Checkbox } from '@ui/checkbox';

import { IProjectPayload } from '@impler/shared';

interface DuplicateImportFormProps {
profile?: IProfileData;
projects?: IProjectPayload[];
onSubmit: (data: IDuplicateTemplateData) => void;
}

export function DuplicateImportForm({ onSubmit, profile, projects }: DuplicateImportFormProps) {
const focusTrapRef = useFocusTrap();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<IDuplicateTemplateData>({
defaultValues: {
_projectId: profile?._projectId,
duplicateColumns: true,
duplicateOutput: true,
duplicateWebhook: true,
duplicateValidator: true,
},
});

return (
<form onSubmit={handleSubmit(onSubmit)} ref={focusTrapRef}>
<Stack spacing="sm">
<Input
autoFocus
required
label="Import Title"
placeholder="Import title"
register={register('name')}
error={errors.name?.message}
/>
<Select
required
placeholder="Project"
label="Project"
register={register('_projectId')}
data={projects?.map((project) => ({ label: project.name, value: project._id })) || []}
/>
<SimpleGrid cols={3}>
<Checkbox label="Copy Columns?" register={register('duplicateColumns')} />
<Checkbox label="Copy Output?" register={register('duplicateOutput')} />
<Checkbox label="Copy Webhook?" register={register('duplicateWebhook')} />
<Checkbox label="Copy Validator?" register={register('duplicateValidator')} />
</SimpleGrid>
<Button type="submit" fullWidth>
Duplicate & Continue
</Button>
</Stack>
</form>
);
}
4 changes: 4 additions & 0 deletions apps/web/config/constants.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const VARIABLES = {
};

export const MODAL_KEYS = {
IMPORT_DUPLICATE: 'IMPORT_DUPLICATE',
IMPORT_CREATE: 'IMPORT_CREATE',
IMPORT_UPDATE: 'IMPORT_UPDATE',
COLUMN_CREATE: 'COLUMN_CREATE',
Expand All @@ -31,6 +32,7 @@ export const MODAL_KEYS = {
};

export const MODAL_TITLES = {
IMPORT_DUPLICATE: 'Duplicate Import',
IMPORT_CREATE: 'Start with a new Import',
COLUMN_CREATE: 'Add a new Column',
IMPORT_UPDATE: 'Update Import',
Expand Down Expand Up @@ -59,6 +61,7 @@ export const API_KEYS = {
TEMPLATE_DETAILS: 'TEMPLATE_DETAILS',
TEMPLATE_UPDATE: 'TEMPLATE_UPDATE',
TEMPLATE_DELETE: 'TEMPLATE_DELETE',
TEMPLATES_DUPLICATE: 'TEMPLATES_DUPLICATE',
TEMPLATE_COLUMNS_LIST: 'TEMPLATE_COLUMNS_LIST',
TEMPLATE_CUSTOMIZATION_GET: 'CUSTOMIZATION_GET',
TEMPLATE_COLUMNS_UPDATE: 'TEMPLATE_COLUMNS_UPDATE',
Expand All @@ -81,6 +84,7 @@ export const API_KEYS = {
};

export const NOTIFICATION_KEYS = {
IMPORT_DUPLICATED: 'IMPORT_DUPLICATED',
IMPORT_UPDATED: 'IMPORT_UPDATED',
IMPORT_CREATED: 'IMPORT_CREATED',
IMPORT_DELETED: 'IMPORT_DELETED',
Expand Down
Loading

0 comments on commit 334adfc

Please sign in to comment.