Skip to content
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
6 changes: 2 additions & 4 deletions packages/_example/src/forest/customizations/rental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ export default (collection: Collection<Schema, 'rental'>) =>
dependencies: ['startDate', 'endDate'],
getValues: records =>
records.map(record => {
// Datasource is sending dates, typing is expecting strings
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const timeDifference = record.endDate.getTime() - record.startDate.getTime();
const timeDifference =
new Date(record.endDate).getTime() - new Date(record.startDate).getTime();

return Math.trunc(timeDifference / (1000 * 60 * 60 * 24));
}),
Expand Down
5 changes: 3 additions & 2 deletions packages/datasource-sequelize/src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import AggregationUtils from './utils/aggregation';
import ModelConverter from './utils/model-to-collection-schema-converter';
import QueryConverter from './utils/query-converter';
import Serializer from './utils/serializer';

export default class SequelizeCollection extends BaseCollection {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -48,7 +49,7 @@ export default class SequelizeCollection extends BaseCollection {
async create(caller: Caller, data: RecordData[]): Promise<RecordData[]> {
const records = await this.model.bulkCreate(data);

return records.map(record => record.get({ plain: true }));
return records.map(record => Serializer.serialize(record.get({ plain: true })));
}

async list(
Expand Down Expand Up @@ -82,7 +83,7 @@ export default class SequelizeCollection extends BaseCollection {

const records = await this.model.findAll(query);

return records.map(record => record.get({ plain: true }));
return records.map(record => Serializer.serialize(record.get({ plain: true })));
}

async update(caller: Caller, filter: Filter, patch: RecordData): Promise<void> {
Expand Down
5 changes: 4 additions & 1 deletion packages/datasource-sequelize/src/utils/aggregation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { Fn } from 'sequelize/types/utils';

import DateAggregationConverter from './date-aggregation-converter';
import Serializer from './serializer';

export default class AggregationUtils {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -125,7 +126,9 @@ export default class AggregationUtils {
};

aggregationQueryGroup?.forEach(({ field }) => {
aggregateResult.group[field] = aggregate[this.getGroupFieldName(field)];
aggregateResult.group[field] = Serializer.serializeValue(
aggregate[this.getGroupFieldName(field)],
);
});

return aggregateResult;
Expand Down
26 changes: 26 additions & 0 deletions packages/datasource-sequelize/src/utils/serializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { RecordData } from '@forestadmin/datasource-toolkit';

export default class Serializer {
static serialize(record: RecordData): RecordData {
Object.entries(record).forEach(([name, value]) => {
if (value instanceof Date) record[name] = this.serializeValue(value);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have keep the condition instead remove it because I do not want to assign a new value for each attribute.

if (Array.isArray(value)) this.serializeValue(value); // the change is by references
if (value instanceof Object) return this.serialize(record[name]);
});

return record;
}

static serializeValue(value: unknown): unknown {
if (value instanceof Date) return value.toISOString();

if (Array.isArray(value)) {
value.forEach((v, i) => {
// serialize by reference to improve performances by avoiding the copies
if (value instanceof Date) value[i] = v.toISOString();
});
}

return value;
}
}
115 changes: 80 additions & 35 deletions packages/datasource-sequelize/test/collection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,11 @@ describe('SequelizeDataSource > Collection', () => {
});

describe('create', () => {
const setup = () => {
const setup = (recordData: RecordData[]) => {
const { dataSource, name, sequelize } = makeConstructorParams();
const sequelizeCollection = new SequelizeCollection(name, dataSource, sequelize.models[name]);
const recordData = Symbol('recordData');
const record = {
get: jest.fn(() => recordData),
};
const bulkCreate = jest.fn().mockResolvedValue([record]);
const records = recordData.map(r => ({ get: jest.fn().mockReturnValueOnce(r) }));
const bulkCreate = jest.fn().mockResolvedValue(records);
// eslint-disable-next-line @typescript-eslint/dot-notation
sequelizeCollection['model'] = {
...sequelize.models[name],
Expand All @@ -62,24 +59,53 @@ describe('SequelizeDataSource > Collection', () => {

return {
bulkCreate,
recordData,
sequelizeCollection,
};
};

it('should delegate work to `sequelize.model.bulkCreate`', async () => {
const { bulkCreate, recordData, sequelizeCollection } = setup();
const data = Symbol('data') as unknown as RecordData[];
const data = [{ data: 'data' }, { data: 10 }, { data: ['Enum'] }];
const { bulkCreate, sequelizeCollection } = setup(data);

await expect(sequelizeCollection.create(factories.caller.build(), data)).resolves.toEqual(
data,
);
expect(bulkCreate).toHaveBeenCalledWith(data);
});

it('should serialize date as iso string', async () => {
const data = [{ date: new Date('2000-01-02') }];
const { bulkCreate, sequelizeCollection } = setup(data);

await expect(sequelizeCollection.create(factories.caller.build(), data)).resolves.toEqual([
recordData,
{ date: '2000-01-02T00:00:00.000Z' },
]);
expect(bulkCreate).toHaveBeenCalledWith(data);
});

it('should serialize date as iso string with many to one relation', async () => {
const data = [{ manyToOne: { date: new Date('2000-01-02') } }];
const { bulkCreate, sequelizeCollection } = setup(data);

await expect(sequelizeCollection.create(factories.caller.build(), data)).resolves.toEqual([
{ manyToOne: { date: '2000-01-02T00:00:00.000Z' } },
]);
expect(bulkCreate).toHaveBeenCalledWith(data);
});

it('should serialize array of date as iso string', async () => {
const data = [{ dates: [new Date('2000-01-02')] }];
const { bulkCreate, sequelizeCollection } = setup(data);

await expect(sequelizeCollection.create(factories.caller.build(), data)).resolves.toEqual([
{ dates: ['2000-01-02T00:00:00.000Z'] },
]);
expect(bulkCreate).toHaveBeenCalledWith(data);
});
});

describe('list', () => {
const setup = () => {
const setup = (recordData: RecordData[]) => {
const { dataSource, name, sequelize } = makeConstructorParams();

const relation = sequelize.define('relation', {
Expand All @@ -89,33 +115,31 @@ describe('SequelizeDataSource > Collection', () => {
const model = sequelize.model(name);
model.belongsTo(relation);

const recordData = Symbol('recordData');
const record = {
get: jest.fn(() => recordData),
};
const findAll = jest.fn().mockResolvedValue([record]);
const records = recordData.map(r => ({ get: jest.fn().mockReturnValueOnce(r) }));
const findAll = jest.fn().mockResolvedValue(records);

model.findAll = findAll;

const sequelizeCollection = new SequelizeCollection(name, dataSource, model);

return {
findAll,
record,
recordData,
sequelizeCollection,
records,
};
};

it('should delegate work to `sequelize.model.findAll`', async () => {
const { findAll, recordData, sequelizeCollection } = setup();
const recordData = [{ data: 'data' }];
const { findAll, sequelizeCollection } = setup(recordData);
const filter = new Filter({});
const projection = new Projection();

const result = await sequelizeCollection.list(factories.caller.build(), filter, projection);

expect(result).toBeArrayOfSize(1);
expect(result[0]).toBe(recordData);
expect(result[0]).toBe(recordData[0]);
expect(findAll).toHaveBeenCalledWith(
expect.objectContaining({
attributes: projection,
Expand All @@ -124,19 +148,35 @@ describe('SequelizeDataSource > Collection', () => {
});

it('should resolve with plain records', async () => {
const { record, recordData, sequelizeCollection } = setup();
const recordData = [{ data: 'data' }];
const { sequelizeCollection, records } = setup(recordData);
const filter = new Filter({});
const projection = new Projection();

const result = await sequelizeCollection.list(factories.caller.build(), filter, projection);

expect(result).toBeArrayOfSize(1);
expect(result[0]).toBe(recordData);
expect(record.get).toHaveBeenCalledWith({ plain: true });
expect(result[0]).toBe(recordData[0]);
expect(records[0].get).toHaveBeenCalledWith({ plain: true });
});

it('should serialize date as iso string', async () => {
const recordData = [{ data: new Date('2000-10-01') }, { data: new Date('2000-10-02') }];
const { sequelizeCollection } = setup(recordData);
const filter = new Filter({});
const projection = new Projection();

const result = await sequelizeCollection.list(factories.caller.build(), filter, projection);

expect(result).toEqual([
{ data: '2000-10-01T00:00:00.000Z' },
{ data: '2000-10-02T00:00:00.000Z' },
]);
});

it('should add include from condition tree, sort and projection', async () => {
const { findAll, sequelizeCollection } = setup();
const recordData = [{ data: 'data' }];
const { findAll, sequelizeCollection } = setup(recordData);
const filter = new PaginatedFilter({
sort: new Sort({ field: 'relation1:field1', ascending: true }),
conditionTree: new ConditionTreeLeaf('relation:aField', 'Equal', 42),
Expand Down Expand Up @@ -274,7 +314,7 @@ describe('SequelizeDataSource > Collection', () => {
renamed__field____grouped__: 'renamed__field__:value',
'relations:as__field____grouped__': 'relations:as__field__:value',
'relations:renamed__as__field____grouped__': 'relations:renamed__as__field__:value',
date__field____grouped__: 'date__field__:value',
date__field____grouped__: new Date('2000-10-01'),
},
]);

Expand All @@ -292,7 +332,7 @@ describe('SequelizeDataSource > Collection', () => {
};
};

describe('whitout aggregate field', () => {
describe('without aggregate field', () => {
it('should aggregate on *', async () => {
const { findAll, sequelizeCollection } = setup();
const aggregation = new Aggregation({
Expand Down Expand Up @@ -356,7 +396,7 @@ describe('SequelizeDataSource > Collection', () => {
});
});

describe('when field name is deferent as column', () => {
describe('when field name is different as column', () => {
it('should aggregate properly', async () => {
const { findAll, sequelizeCollection } = setup();
const aggregation = new Aggregation({
Expand Down Expand Up @@ -451,7 +491,7 @@ describe('SequelizeDataSource > Collection', () => {
});
});

describe('when field name is deferent as column', () => {
describe('when field name is different as column', () => {
it('should aggregate properly', async () => {
const { findAll, sequelizeCollection } = setup();
const aggregation = new Aggregation({
Expand Down Expand Up @@ -531,7 +571,7 @@ describe('SequelizeDataSource > Collection', () => {
);
});

describe('when field name is deferent as column', () => {
describe('when field name is different as column', () => {
it('should compute group properly', async () => {
const { findAll, sequelizeCollection } = setup();
const aggregation = new Aggregation({
Expand Down Expand Up @@ -622,7 +662,7 @@ describe('SequelizeDataSource > Collection', () => {
);
});

describe('when field name is deferent as column', () => {
describe('when field name is different as column', () => {
it('should aggregate properly', async () => {
const { findAll, sequelizeCollection } = setup();
const aggregation = new Aggregation({
Expand Down Expand Up @@ -698,7 +738,8 @@ describe('SequelizeDataSource > Collection', () => {
await expect(
sequelizeCollection.aggregate(factories.caller.build(), filter, aggregation),
).resolves.toEqual([
{ group: { date__field__: 'date__field__:value' }, value: '__aggregate__:value' },
// date should be serialize in iso string
{ group: { date__field__: '2000-10-01T00:00:00.000Z' }, value: '__aggregate__:value' },
]);

expect(findAll).toHaveBeenCalledTimes(1);
Expand All @@ -722,7 +763,7 @@ describe('SequelizeDataSource > Collection', () => {
});

describe('when dialect is mssql', () => {
it('should add date agregation to group', async () => {
it('should add date aggregation to group', async () => {
const { findAll, sequelizeCollection } = setup('mssql');

const aggregation = new Aggregation({
Expand All @@ -734,7 +775,11 @@ describe('SequelizeDataSource > Collection', () => {
await expect(
sequelizeCollection.aggregate(factories.caller.build(), filter, aggregation),
).resolves.toEqual([
{ group: { date__field__: 'date__field__:value' }, value: '__aggregate__:value' },
{
// date should be serialize in iso string
group: { date__field__: '2000-10-01T00:00:00.000Z' },
value: '__aggregate__:value',
},
]);

const aggregateFunction = {
Expand All @@ -758,7 +803,7 @@ describe('SequelizeDataSource > Collection', () => {

describe('on sort', () => {
describe('when dialect is postgres', () => {
it('should sort on aggragate by default', async () => {
it('should sort on aggregate by default', async () => {
const { findAll, sequelizeCollection } = setup();
const aggregation = new Aggregation({
operation: 'Count',
Expand All @@ -779,7 +824,7 @@ describe('SequelizeDataSource > Collection', () => {
});

describe('when dialect is mssql', () => {
it('should sort on aggragate by default', async () => {
it('should sort on aggregate by default', async () => {
const { findAll, sequelizeCollection } = setup('mssql');
const aggregation = new Aggregation({
operation: 'Count',
Expand All @@ -799,7 +844,7 @@ describe('SequelizeDataSource > Collection', () => {
});
});

it('should sort on aggragate by default', async () => {
it('should sort on aggregate by default', async () => {
const { findAll, sequelizeCollection } = setup('mysql');
const aggregation = new Aggregation({
operation: 'Count',
Expand Down