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: facility to provide custom date-formats #378

Closed
wants to merge 8 commits into from
Closed
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
4 changes: 3 additions & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"compression": "^1.7.4",
"cookie-parser": "^1.4.6",
"date-fns": "^2.30.0",
"dayjs": "^1.11.10",
"dotenv": "^16.0.2",
"envalid": "^7.3.1",
"exceljs": "^4.3.0",
Expand All @@ -55,7 +56,8 @@
"passport-oauth2": "^1.6.1",
"rimraf": "^3.0.2",
"source-map-support": "^0.5.21",
"uuid": "^9.0.0"
"uuid": "^9.0.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.0/xlsx-0.20.0.tgz"
},
"devDependencies": {
"@nestjs/cli": "^9.1.5",
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/app/column/column.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class ColumnController {
regex: body.regex,
regexDescription: body.regexDescription,
selectValues: body.selectValues,
dateFormats: body.dateFormats,
sequence: body.sequence,
_templateId,
type: body.type,
Expand Down
5 changes: 5 additions & 0 deletions apps/api/src/app/column/commands/add-column.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export class AddColumnCommand extends BaseCommand {
@Type(() => Array<string>)
selectValues: string[];

@IsArray()
@IsOptional()
@Type(() => Array<string>)
dateFormats: string[];

@IsNumber()
@IsOptional()
sequence: number;
Expand Down
5 changes: 5 additions & 0 deletions apps/api/src/app/column/commands/update-column.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export class UpdateColumnCommand extends BaseCommand {
@Type(() => Array<string>)
selectValues: string[];

@IsArray()
@IsOptional()
@Type(() => Array<string>)
dateFormats: string[];

@IsNumber()
@IsOptional()
sequence: number;
Expand Down
9 changes: 8 additions & 1 deletion apps/api/src/app/column/dtos/column-request.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
Validate,
} from 'class-validator';
import { Type } from 'class-transformer';
import { ColumnTypesEnum } from '@impler/shared';
import { ColumnTypesEnum, Defaults } from '@impler/shared';
import { IsValidRegex } from '@shared/framework/is-valid-regex.validator';

export class ColumnRequestDto {
Expand Down Expand Up @@ -83,6 +83,13 @@ export class ColumnRequestDto {
@Type(() => Array<string>)
selectValues: string[] = [];

@ApiPropertyOptional({
description: 'List of date formats for column if type is Date',
})
@ValidateIf((object) => object.type === ColumnTypesEnum.DATE)
@Type(() => Array<string>)
dateFormats: string[] = Defaults.DATE_FORMATS;

@ApiProperty({
description: 'Sequence of column',
})
Expand Down
5 changes: 5 additions & 0 deletions apps/api/src/app/column/dtos/column-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ export class ColumnResponseDto {
})
selectValues?: string[];

@ApiPropertyOptional({
description: 'List of possible date formats for column if type is Date',
})
dateFormats?: string[];

@ApiProperty({
description: 'Sequence of column',
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export class UpdateColumn {
const isTypeUpdated = command.type !== column.type;
const isFieldConditionUpdated =
JSON.stringify(column.selectValues) !== JSON.stringify(command.selectValues) ||
JSON.stringify(column.dateFormats) !== JSON.stringify(command.dateFormats) ||
column.isRequired !== command.isRequired;

column = await this.columnRepository.findOneAndUpdate({ _id }, command);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export class DoReview {
const mappings = await this.mappingRepository.find({ _uploadId: uploadId }, '_columnId columnHeading');
const columns = await this.columnRepository.find(
{ _templateId: uploadInfo._templateId },
'isRequired isUnique selectValues type regex'
'isRequired isUnique selectValues dateFormats type regex'
);
const schema = this.buildAJVSchema(columns, mappings);
const validator = ajv.compile(schema);
Expand Down
32 changes: 22 additions & 10 deletions apps/api/src/app/review/usecases/do-review/do-review.usecase.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as fs from 'fs';
import * as dayjs from 'dayjs';
import * as Papa from 'papaparse';
import * as ExcelJS from 'exceljs';
import addFormats from 'ajv-formats';
Expand Down Expand Up @@ -39,12 +40,12 @@ const ajv = new Ajv({
});
addFormats(ajv, ['email']);
addKeywords(ajv);
ajv.addFormat('custom-date-time', function (dateTimeString) {
if (typeof dateTimeString === 'object') {
dateTimeString = (dateTimeString as Date).toISOString();
}

return !isNaN(Date.parse(dateTimeString)); // any test that returns true/false
let dateFormats: Record<string, string[]> = {};
ajv.addKeyword('customDateChecker', {
keyword: 'customDateChecker',
validate: function (_valid, date, _schema, dataPath) {
return dayjs(date, dateFormats[dataPath.parentDataProperty]).isValid();
},
});

// Empty keyword
Expand Down Expand Up @@ -120,6 +121,7 @@ export class DoReview {

// resetting uniqueItems
uniqueItems = {};
dateFormats = {};

return response;
}
Expand All @@ -146,6 +148,13 @@ export class DoReview {
if (formattedColumns[column.key].isUnique) {
uniqueItems[column.key] = new Set();
}
if (
formattedColumns[column.key].type === ColumnTypesEnum.DATE &&
Array.isArray(formattedColumns[column.key].dateFormats) &&
formattedColumns[column.key].dateFormats.length > 0
) {
dateFormats[column.key] = formattedColumns[column.key].dateFormats;
}
});

return {
Expand Down Expand Up @@ -194,7 +203,7 @@ export class DoReview {
};
break;
case ColumnTypesEnum.DATE:
property = { type: ['string', 'null'], format: 'custom-date-time', default: null };
property = { type: ['string', 'null'], customDateChecker: true, default: null };
break;
case ColumnTypesEnum.ANY:
property = { type: ['string', 'number', 'object', 'null'] };
Expand Down Expand Up @@ -226,6 +235,10 @@ export class DoReview {
case error.keyword === 'emptyCheck':
message = ` must not be empty`;
break;
// customDateChecker
case error.keyword === 'customDateChecker':
message = ` must match format from [${dateFormats[field].toString()}]`;
break;
// uniqueCheck
case error.keyword === 'uniqueCheck':
message = ` must be unique`;
Expand Down Expand Up @@ -600,15 +613,14 @@ export class DoReview {
headings,
});

const batchProcess = await Promise.all(
const batchProcessData = await Promise.all(
batches.map((batch) => this.executeBatchInSandbox(batch, this.sandboxManager, onBatchInitialize))
);
const processedArray = batchProcess.flat();
let totalRecords = 0,
validRecords = 0,
invalidRecords = 0;
let processOutput, message: string;
for (const processData of processedArray) {
for (const processData of batchProcessData) {
if (
processData &&
processData.output &&
Expand Down
25 changes: 4 additions & 21 deletions apps/api/src/app/shared/services/file/file.service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as XLSX from 'xlsx';
import * as ExcelJS from 'exceljs';
import { ParseConfig, parse } from 'papaparse';
import { ColumnTypesEnum, Defaults, FileEncodingsEnum } from '@impler/shared';
Expand All @@ -8,28 +9,10 @@ import { IExcelFileHeading } from '@shared/types/file.types';
export class ExcelFileService {
async convertToCsv(file: Express.Multer.File): Promise<string> {
return new Promise(async (resolve) => {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(file.buffer);
workbook.csv
.writeBuffer({
map(value) {
if (typeof value === 'object' && value && value.text) {
return value.text;
}
const wb = XLSX.read(file.buffer);
const ws = wb.Sheets[wb.SheetNames[0]];

return value;
},
includeEmptyRows: true,
formatterOptions: {
escape: '',
headers: true,
},
})
.then((buffer) => {
resolve(buffer.toString());

return '';
});
resolve(XLSX.utils.sheet_to_csv(ws));
});
}
formatName(name: string): string {
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/app/template/template.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export class TemplateController {
regex: columnData.regex,
regexDescription: columnData.regexDescription,
selectValues: columnData.selectValues,
dateFormats: columnData.dateFormats,
sequence: columnData.sequence,
_templateId,
type: columnData.type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class GetTemplateColumns {
async execute(_templateId: string) {
return this.columnRepository.find(
{ _templateId },
'_id name key type alternateKeys isRequired isUnique selectValues sequence'
'_id name key type alternateKeys isRequired isUnique selectValues dateFormats sequence'
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class MakeUploadEntry {
{
_templateId: templateId,
},
'name key isRequired isUnique selectValues type regex sequence',
'name key isRequired isUnique selectValues dateFormats type regex sequence',
{
sort: 'sequence',
}
Expand All @@ -68,7 +68,8 @@ export class MakeUploadEntry {
key: schemaItem.key,
type: schemaItem.type || ColumnTypesEnum.STRING,
regex: schemaItem.regex,
selectValues: schemaItem.selectValues || [],
selectValues: Array.isArray(schemaItem.selectValues) ? schemaItem.selectValues : [],
dateFormats: Array.isArray(schemaItem.dateFormats) ? schemaItem.dateFormats : Defaults.DATE_FORMATS,
isUnique: schemaItem.isUnique || false,

sequence: Object.keys(formattedColumns).length,
Expand Down
5 changes: 1 addition & 4 deletions apps/web/.example.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,4 @@ NEXT_PUBLIC_API_BASE_URL=http://localhost:3000
NEXT_PUBLIC_EMBED_URL=http://localhost:4701/embed.umd.min.js
NEXT_PUBLIC_AMPLITUDE_ID=
NEXT_PUBLIC_TAWK_PROPERTY_ID=
NEXT_PUBLIC_TAWK_WIDGET_ID=
NEXT_PUBLIC_CUSTOM_VALIDATION_ENABLED=
NEXT_PUBLIC_HYPERDX_URL=
NEXT_PUBLIC_HYPERDX_KEY=
NEXT_PUBLIC_TAWK_WIDGET_ID=
29 changes: 27 additions & 2 deletions apps/web/components/imports/forms/ColumnForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function ColumnForm({ onSubmit, data, isLoading }: ColumnFormProps) {
regex: data.regex,
regexDescription: data.regexDescription,
selectValues: data.selectValues,
dateFormats: data.dateFormats,
type: data.type,
alternateKeys: data.alternateKeys,
});
Expand Down Expand Up @@ -89,7 +90,7 @@ export function ColumnForm({ onSubmit, data, isLoading }: ColumnFormProps) {
/>
</>
)}
{typeValue === 'Select' && (
{typeValue === 'Select' ? (
<Controller
name="selectValues"
control={control}
Expand All @@ -111,7 +112,31 @@ export function ColumnForm({ onSubmit, data, isLoading }: ColumnFormProps) {
/>
)}
/>
)}
) : null}

{typeValue === 'Date' ? (
<Controller
name="dateFormats"
control={control}
render={({ field: { value, onChange } }) => (
<MultiSelect
creatable
clearable
searchable
value={value}
placeholder="Valid Date Formats, i.e. DD/MM/YYYY, DD/MM/YY"
data={Array.isArray(value) ? value : []}
getCreateLabel={(query) => `Add "${query}"`}
onCreate={(newItem) => {
onChange([...(Array.isArray(value) ? value : []), newItem]);

return newItem;
}}
onChange={onChange}
/>
)}
/>
) : null}
<Group spacing="xs">
<Checkbox label="Is Required?" register={register('isRequired')} />
{typeValue !== 'Select' && <Checkbox label="Is Unique?" register={register('isUnique')} />}
Expand Down
3 changes: 3 additions & 0 deletions apps/web/design-system/multi-select/MultiSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface MultiSelectProps {
clearable?: boolean;
getCreateLabel?: (value: string) => string;
onChange?: (value: string[]) => void;
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
}

export function MultiSelect({
Expand All @@ -25,6 +26,7 @@ export function MultiSelect({
noFoundText,
clearable,
creatable,
onKeyDown,
getCreateLabel,
}: MultiSelectProps) {
const { classes } = useStyles();
Expand All @@ -42,6 +44,7 @@ export function MultiSelect({
value={value}
onCreate={onCreate}
onChange={onChange}
onKeyDown={onKeyDown}
/>
);
}
6 changes: 0 additions & 6 deletions apps/web/hooks/useApp.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useRouter } from 'next/router';
import HyperDX from '@hyperdx/browser';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';

import { commonApi } from '@libs/api';
Expand All @@ -19,11 +18,6 @@ export function useApp() {
{
onSuccess(profileData) {
setProfileInfo(profileData);
HyperDX.setGlobalAttributes({
userId: profileData._id,
userEmail: profileData.email,
userName: profileData.firstName + profileData.lastName,
});
},
}
);
Expand Down
2 changes: 2 additions & 0 deletions apps/web/hooks/useColumnsEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export function useColumnsEditor({ templateId }: UseSchemaProps) {
regex,
regexDescription,
selectValues,
dateFormats,
sequence,
}) => ({
key,
Expand All @@ -53,6 +54,7 @@ export function useColumnsEditor({ templateId }: UseSchemaProps) {
regex,
regexDescription,
selectValues,
dateFormats,
sequence,
})
);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/hooks/useValidator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export function useValidator({ templateId }: UseSchemaProps) {
{
onSuccess(output) {
setTestCodeResult(output);
if (!output.passed) {
if (output && !output.passed) {
modals.open({
modalId: MODAL_KEYS.VALIDATIONS_OUTPUT,
title: MODAL_TITLES.VALIDATIONS_OUTPUT,
Expand Down
1 change: 0 additions & 1 deletion apps/web/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const nextConfig = {
NEXT_PUBLIC_TAWK_PROPERTY_ID: process.env.NEXT_PUBLIC_TAWK_PROPERTY_ID,
NEXT_PUBLIC_TAWK_WIDGET_ID: process.env.NEXT_PUBLIC_TAWK_WIDGET_ID,
NEXT_PUBLIC_GTM_ID: process.env.NEXT_PUBLIC_GTM_ID,
NEXT_PUBLIC_CUSTOM_VALIDATION_ENABLED: process.env.NEXT_PUBLIC_CUSTOM_VALIDATION_ENABLED,
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID: process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID,
},
};
Expand Down
Loading