Upsert endpoint and CSV import upsert (#5970)
This PR introduces an `upsert` parameter (along the existing `data` param) for `createOne` and `createMany` mutations. When upsert is set to `true`, the function will look for records with the same id if an id was passed. If not id was passed, it will leverage the existing duplicate check mechanism to find a duplicate. If a record is found, then the function will perform an update instead of a create. Unfortunately I had to remove some nice tests that existing on the args factory. Those tests where mostly testing the duplication rule generation logic but through a GraphQL angle. Since I moved the duplication rule logic to a dedicated service, if I kept the tests but mocked the service we wouldn't really be testing anything useful. The right path would be to create new tests for this service that compare the JSON output and not the GraphQL output but I chose not to work on this as it's equivalent to rewriting the tests from scratch and I have other competing priorities.
This commit is contained in:
@ -22,8 +22,16 @@ const mockObjectMetadata: ObjectMetadataInterface = {
|
||||
|
||||
describe('objectRecordChangedValues', () => {
|
||||
it('detects changes in scalar values correctly', () => {
|
||||
const oldRecord = { id: 1, name: 'Original Name', updatedAt: new Date() };
|
||||
const newRecord = { id: 1, name: 'Updated Name', updatedAt: new Date() };
|
||||
const oldRecord = {
|
||||
id: '74316f58-29b0-4a6a-b8fa-d2b506d5516m',
|
||||
name: 'Original Name',
|
||||
updatedAt: new Date().toString(),
|
||||
};
|
||||
const newRecord = {
|
||||
id: '74316f58-29b0-4a6a-b8fa-d2b506d5516m',
|
||||
name: 'Updated Name',
|
||||
updatedAt: new Date().toString(),
|
||||
};
|
||||
|
||||
const result = objectRecordChangedValues(
|
||||
oldRecord,
|
||||
@ -38,8 +46,14 @@ describe('objectRecordChangedValues', () => {
|
||||
});
|
||||
|
||||
it('ignores changes to the updatedAt field', () => {
|
||||
const oldRecord = { id: 1, updatedAt: new Date('2020-01-01') };
|
||||
const newRecord = { id: 1, updatedAt: new Date('2024-01-01') };
|
||||
const oldRecord = {
|
||||
id: '74316f58-29b0-4a6a-b8fa-d2b506d5516d',
|
||||
updatedAt: new Date('2020-01-01').toDateString(),
|
||||
};
|
||||
const newRecord = {
|
||||
id: '74316f58-29b0-4a6a-b8fa-d2b506d5516d',
|
||||
updatedAt: new Date('2024-01-01').toDateString(),
|
||||
};
|
||||
|
||||
const result = objectRecordChangedValues(
|
||||
oldRecord,
|
||||
@ -51,8 +65,16 @@ it('ignores changes to the updatedAt field', () => {
|
||||
});
|
||||
|
||||
it('returns an empty object when there are no changes', () => {
|
||||
const oldRecord = { id: 1, name: 'Name', value: 100 };
|
||||
const newRecord = { id: 1, name: 'Name', value: 100 };
|
||||
const oldRecord = {
|
||||
id: '74316f58-29b0-4a6a-b8fa-d2b506d5516k',
|
||||
name: 'Name',
|
||||
value: 100,
|
||||
};
|
||||
const newRecord = {
|
||||
id: '74316f58-29b0-4a6a-b8fa-d2b506d5516k',
|
||||
name: 'Name',
|
||||
value: 100,
|
||||
};
|
||||
|
||||
const result = objectRecordChangedValues(
|
||||
oldRecord,
|
||||
@ -65,17 +87,17 @@ it('returns an empty object when there are no changes', () => {
|
||||
|
||||
it('correctly handles a mix of changed, unchanged, and special case values', () => {
|
||||
const oldRecord = {
|
||||
id: 1,
|
||||
id: '74316f58-29b0-4a6a-b8fa-d2b506d5516l',
|
||||
name: 'Original',
|
||||
status: 'active',
|
||||
updatedAt: new Date(2020, 1, 1),
|
||||
updatedAt: new Date(2020, 1, 1).toDateString(),
|
||||
config: { theme: 'dark' },
|
||||
};
|
||||
const newRecord = {
|
||||
id: 1,
|
||||
id: '74316f58-29b0-4a6a-b8fa-d2b506d5516l',
|
||||
name: 'Updated',
|
||||
status: 'active',
|
||||
updatedAt: new Date(2021, 1, 1),
|
||||
updatedAt: new Date(2021, 1, 1).toDateString(),
|
||||
config: { theme: 'light' },
|
||||
};
|
||||
const expectedChanges = {
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
import deepEqual from 'deep-equal';
|
||||
|
||||
export const objectRecordChangedProperties = (
|
||||
oldRecord: Record<string, any>,
|
||||
newRecord: Record<string, any>,
|
||||
import { Record } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
|
||||
export const objectRecordChangedProperties = <
|
||||
PRecord extends Partial<Record | BaseWorkspaceEntity> = Partial<Record>,
|
||||
>(
|
||||
oldRecord: PRecord,
|
||||
newRecord: PRecord,
|
||||
) => {
|
||||
const changedProperties = Object.keys(newRecord).filter(
|
||||
(key) => !deepEqual(oldRecord[key], newRecord[key]),
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import deepEqual from 'deep-equal';
|
||||
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
|
||||
export const objectRecordChangedValues = (
|
||||
oldRecord: Record<string, any>,
|
||||
newRecord: Record<string, any>,
|
||||
oldRecord: Partial<IRecord>,
|
||||
newRecord: Partial<IRecord>,
|
||||
objectMetadata: ObjectMetadataInterface,
|
||||
) => {
|
||||
const changedValues = Object.keys(newRecord).reduce(
|
||||
|
||||
@ -39,7 +39,7 @@ export class SyncDriver implements MessageQueueDriver {
|
||||
});
|
||||
}
|
||||
|
||||
async removeCron(queueName: MessageQueue, jobName: string) {
|
||||
async removeCron(queueName: MessageQueue) {
|
||||
this.logger.log(`Removing '${queueName}' cron job with SyncDriver`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user