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

fix: show the unique row value in the import preview #392

Merged
merged 1 commit into from
Mar 28, 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
1 change: 1 addition & 0 deletions packages/server/src/interfaces/Model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface IModelMetaFieldCommon {
required?: boolean;
importHint?: string;
order?: number;
unique?: number;
}

export interface IModelMetaFieldText {
Expand Down
1 change: 1 addition & 0 deletions packages/server/src/models/Account.Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default {
importable: true,
minLength: 3,
maxLength: 6,
unique: true,
importHint: 'Unique number to identify the account.',
},
rootType: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,11 @@ export class CommandAccountValidators {
query.whereNot('id', notAccountId);
}
});

if (account.length > 0) {
throw new ServiceError(ERRORS.ACCOUNT_CODE_NOT_UNIQUE);
throw new ServiceError(
ERRORS.ACCOUNT_CODE_NOT_UNIQUE,
'Account code is not unique.'
);
}
}

Expand All @@ -124,7 +126,10 @@ export class CommandAccountValidators {
}
});
if (foundAccount) {
throw new ServiceError(ERRORS.ACCOUNT_NAME_NOT_UNIQUE);
throw new ServiceError(
ERRORS.ACCOUNT_NAME_NOT_UNIQUE,
'Account name is not unique.'
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ERRORS } from './constants';
@Service()
export default class CashflowDeleteAccount {
@Inject()
tenancy: HasTenancyService;
private tenancy: HasTenancyService;

/**
* Validate the account has no associated cashflow transactions.
Expand Down
27 changes: 21 additions & 6 deletions packages/server/src/services/Import/ImportFileCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import { first } from 'lodash';
import { ImportFileDataValidator } from './ImportFileDataValidator';
import { Knex } from 'knex';
import {
ImportInsertError,
ImportOperError,
ImportOperSuccess,
ImportableContext,
} from './interfaces';
import { ServiceError } from '@/exceptions';
import { trimObject } from './_utils';
import { getUniqueImportableValue, trimObject } from './_utils';
import { ImportableResources } from './ImportableResources';
import ResourceService from '../Resource/ResourceService';
import HasTenancyService from '../Tenancy/TenancyService';
Expand Down Expand Up @@ -88,7 +89,12 @@ export class ImportFileCommon {
import: importFile,
};
const transformedDTO = importable.transform(objectDTO, context);

const rowNumber = index + 1;
const uniqueValue = getUniqueImportableValue(importableFields, objectDTO);
const errorContext = {
rowNumber,
uniqueValue,
};
try {
// Validate the DTO object before passing it to the service layer.
await this.importFileValidator.validateData(
Expand All @@ -105,18 +111,27 @@ export class ImportFileCommon {
success.push({ index, data });
} catch (err) {
if (err instanceof ServiceError) {
const error = [
const error: ImportInsertError[] = [
{
errorCode: 'ValidationError',
errorCode: 'ServiceError',
errorMessage: err.message || err.errorType,
rowNumber: index + 1,
...errorContext,
},
];
failed.push({ index, error });
} else {
const error: ImportInsertError[] = [
{
errorCode: 'UnknownError',
errorMessage: 'Unknown error occurred',
...errorContext,
},
];
failed.push({ index, error });
}
}
} catch (errors) {
const error = errors.map((er) => ({ ...er, rowNumber: index + 1 }));
const error = errors.map((er) => ({ ...er, ...errorContext }));
failed.push({ index, error });
}
};
Expand Down
12 changes: 8 additions & 4 deletions packages/server/src/services/Import/ImportFileDataValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@ export class ImportFileDataValidator {
try {
await YupSchema.validate(_data, { abortEarly: false });
} catch (validationError) {
const errors = validationError.inner.map((error) => ({
errorCode: 'ValidationError',
errorMessage: error.errors,
}));
const errors = validationError.inner.reduce((errors, error) => {
const newErrors = error.errors.map((errMsg) => ({
errorCode: 'ValidationError',
errorMessage: errMsg,
}));
return [...errors, ...newErrors];
}, []);

throw errors;
}
}
Expand Down
23 changes: 22 additions & 1 deletion packages/server/src/services/Import/_utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as Yup from 'yup';
import { upperFirst, camelCase, first, isUndefined } from 'lodash';
import { defaultTo, upperFirst, camelCase, first, isUndefined, pickBy } from 'lodash';
import pluralize from 'pluralize';
import { ResourceMetaFieldsMap } from './interfaces';
import { IModelMetaField } from '@/interfaces';
Expand Down Expand Up @@ -101,3 +101,24 @@ export const sanitizeResourceName = (resourceName: string) => {
export const getSheetColumns = (sheetData: unknown[]) => {
return Object.keys(first(sheetData));
};

/**
* Retrieves the unique value from the given imported object DTO based on the
* configured unique resource field.
* @param {{ [key: string]: IModelMetaField }} importableFields -
* @param {<Record<string, any>}
* @returns {string}
*/
export const getUniqueImportableValue = (
importableFields: { [key: string]: IModelMetaField },
objectDTO: Record<string, any>
) => {
const uniqueImportableValue = pickBy(
importableFields,
(field) => field.unique
);
const uniqueImportableKeys = Object.keys(uniqueImportableValue);
const uniqueImportableKey = first(uniqueImportableKeys);

return defaultTo(objectDTO[uniqueImportableKey], '');
};
2 changes: 1 addition & 1 deletion packages/server/src/services/Import/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export interface ImportOperSuccess {
}

export interface ImportOperError {
error: ImportInsertError;
error: ImportInsertError[];
index: number;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,13 @@
padding-left: 2rem;
}

.skippedTable {
table.skippedTable {
width: 100%;

thead{
th{
padding-top: 0;
padding-bottom: 8px;
color: #738091;
font-weight: 500;
}
}


tbody{
tr:first-child td {
box-shadow: 0 0 0 0;
}
tr td {
vertical-align: middle;
padding: 7px;
Expand Down
15 changes: 2 additions & 13 deletions packages/webapp/src/containers/Import/ImportFilePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,12 @@ function ImportFilePreviewSkipped() {
>
<SectionCard padded={true}>
<table className={clsx('bp4-html-table', styles.skippedTable)}>
<thead>
<tr>
<th className={'number'}>#</th>
<th className={'name'}>Name</th>
<th>Error</th>
</tr>
</thead>
<tbody>
{importPreview?.errors.map((error, key) => (
<tr key={key}>
<td>{error.rowNumber}</td>
<td>{error.rowNumber}</td>
<td>
{error.errorMessage.map((message) => (
<div>{message}</div>
))}
</td>
<td>{error.uniqueValue}</td>
<td>{error.errorMessage}</td>
</tr>
))}
</tbody>
Expand Down
Loading