Compare commits

...

8 Commits

Author SHA1 Message Date
Weiko c8bd91903f Bump version 2025-02-27 11:10:19 +01:00
Weiko c7d4e6c76f Fix filter rest api (#10537)
## Context
Rest API is now using cache metadata but the change was not done
everywhere which was breaking the API
<img width="953" alt="Screenshot 2025-02-27 at 10 48 31"
src="https://github.com/user-attachments/assets/84f549a6-59b5-4989-b1ba-2d5c515ee47c"
/>
2025-02-27 11:07:44 +01:00
Charles Bochet 9ad35b39ed Bump version 2025-02-27 10:32:34 +01:00
Charles Bochet db9a906130 Fix field empty value check for Person standard object (#10533)
The previous code was not correct if labelIdentifier is a FULL_NAME.
We already have an util for this

Fixes: https://github.com/twentyhq/twenty/issues/10532
2025-02-27 10:32:08 +01:00
Weiko 218cb798e6 translate = no 2025-02-26 18:03:51 +01:00
Weiko a364692925 Bump version 2025-02-26 18:02:35 +01:00
Weiko 1991f0d3c0 Fix REST API not using metadata cache (#10521) 2025-02-26 18:00:07 +01:00
Charles Bochet ce428985aa Bump version 2025-02-21 18:37:03 +01:00
34 changed files with 527 additions and 136 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-e2e-testing",
"version": "0.42.0-canary",
"version": "0.42.18",
"description": "",
"author": "",
"private": true,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-emails",
"version": "0.42.0-canary",
"version": "0.42.18",
"description": "",
"author": "",
"private": true,
+1 -1
View File
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html lang="en" translate="no">
<head>
<meta charset="UTF-8" />
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-front",
"version": "0.42.0-canary",
"version": "0.42.18",
"private": true,
"type": "module",
"scripts": {
@@ -16,6 +16,7 @@ import {
} from '@/object-record/record-field/contexts/FieldContext';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
import { RecordIdentifierChip } from '@/object-record/record-index/components/RecordIndexRecordChip';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { recordIndexOpenRecordInSelector } from '@/object-record/record-index/states/selectors/recordIndexOpenRecordInSelector';
@@ -95,9 +96,10 @@ export const RecordBoardCardHeader = ({
isRecordBoardCompactModeActiveComponentState,
);
const isIdentifierEmpty =
(record?.[identifierFieldDefinition.metadata.fieldName] || '').trim() ===
'';
const isIdentifierEmpty = isFieldValueEmpty({
fieldDefinition: identifierFieldDefinition,
fieldValue: record?.[identifierFieldDefinition.metadata.fieldName],
});
const { checkIfLastUnselectAndCloseDropdown } =
useRecordBoardSelection(recordBoardId);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-server",
"version": "0.42.0-canary",
"version": "0.42.18",
"description": "",
"author": "",
"private": true,
@@ -19,9 +19,12 @@ import { parseCoreBatchPath } from 'src/engine/api/rest/core/query-builder/utils
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
import { Query } from 'src/engine/api/rest/core/types/query.type';
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { getObjectMetadataMapItemByNamePlural } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-plural.util';
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
@Injectable()
export class CoreQueryBuilderFactory {
@@ -38,25 +41,35 @@ export class CoreQueryBuilderFactory {
private readonly updateVariablesFactory: UpdateVariablesFactory,
private readonly getVariablesFactory: GetVariablesFactory,
private readonly findDuplicatesVariablesFactory: FindDuplicatesVariablesFactory,
private readonly objectMetadataService: ObjectMetadataService,
private readonly accessTokenService: AccessTokenService,
private readonly domainManagerService: DomainManagerService,
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
) {}
async getObjectMetadata(
request: Request,
parsedObject: string,
): Promise<{
objectMetadataItems: ObjectMetadataEntity[];
objectMetadataItem: ObjectMetadataEntity;
objectMetadataMaps: ObjectMetadataMaps;
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
}> {
const { workspace } =
await this.accessTokenService.validateTokenByRequest(request);
const objectMetadataItems =
await this.objectMetadataService.findManyWithinWorkspace(workspace.id);
const currentCacheVersion =
await this.workspaceCacheStorageService.getMetadataVersion(workspace.id);
if (!objectMetadataItems.length) {
if (currentCacheVersion === undefined) {
throw new BadRequestException('No cacheVersion');
}
const objectMetadataMaps =
await this.workspaceCacheStorageService.getObjectMetadataMaps(
workspace.id,
currentCacheVersion,
);
if (!objectMetadataMaps) {
throw new BadRequestException(
`No object was found for the workspace associated with this API key. You may generate a new one here ${this.domainManagerService
.buildWorkspaceURL({
@@ -67,19 +80,21 @@ export class CoreQueryBuilderFactory {
);
}
const [objectMetadata] = objectMetadataItems.filter(
(object) => object.namePlural === parsedObject,
const objectMetadataItem = getObjectMetadataMapItemByNamePlural(
objectMetadataMaps,
parsedObject,
);
if (!objectMetadata) {
const [wrongObjectMetadata] = objectMetadataItems.filter(
(object) => object.nameSingular === parsedObject,
if (!objectMetadataItem) {
const wrongObjectMetadataItem = getObjectMetadataMapItemByNameSingular(
objectMetadataMaps,
parsedObject,
);
let hint = 'eg: companies';
if (wrongObjectMetadata) {
hint = `Did you mean '${wrongObjectMetadata.namePlural}'?`;
if (wrongObjectMetadataItem) {
hint = `Did you mean '${wrongObjectMetadataItem.namePlural}'?`;
}
throw new BadRequestException(
@@ -88,8 +103,8 @@ export class CoreQueryBuilderFactory {
}
return {
objectMetadataItems,
objectMetadataItem: objectMetadata,
objectMetadataMaps,
objectMetadataMapItem: objectMetadataItem,
};
}
@@ -101,12 +116,14 @@ export class CoreQueryBuilderFactory {
if (!id) {
throw new BadRequestException(
`delete ${objectMetadata.objectMetadataItem.nameSingular} query invalid. Id missing. eg: /rest/${objectMetadata.objectMetadataItem.namePlural}/0d4389ef-ea9c-4ae8-ada1-1cddc440fb56`,
`delete ${objectMetadata.objectMetadataMapItem.nameSingular} query invalid. Id missing. eg: /rest/${objectMetadata.objectMetadataMapItem.namePlural}/0d4389ef-ea9c-4ae8-ada1-1cddc440fb56`,
);
}
return {
query: this.deleteQueryFactory.create(objectMetadata.objectMetadataItem),
query: this.deleteQueryFactory.create(
objectMetadata.objectMetadataMapItem,
),
variables: this.deleteVariablesFactory.create(id),
};
}
@@ -144,7 +161,7 @@ export class CoreQueryBuilderFactory {
if (!id) {
throw new BadRequestException(
`update ${objectMetadata.objectMetadataItem.nameSingular} query invalid. Id missing. eg: /rest/${objectMetadata.objectMetadataItem.namePlural}/0d4389ef-ea9c-4ae8-ada1-1cddc440fb56`,
`update ${objectMetadata.objectMetadataMapItem.nameSingular} query invalid. Id missing. eg: /rest/${objectMetadata.objectMetadataMapItem.namePlural}/0d4389ef-ea9c-4ae8-ada1-1cddc440fb56`,
);
}
@@ -2,12 +2,12 @@ import { Module } from '@nestjs/common';
import { CoreQueryBuilderFactory } from 'src/engine/api/rest/core/query-builder/core-query-builder.factory';
import { coreQueryBuilderFactories } from 'src/engine/api/rest/core/query-builder/factories/factories';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
@Module({
imports: [ObjectMetadataModule, AuthModule, DomainManagerModule],
imports: [AuthModule, DomainManagerModule, WorkspaceCacheStorageModule],
providers: [...coreQueryBuilderFactories, CoreQueryBuilderFactory],
exports: [CoreQueryBuilderFactory],
})
@@ -3,25 +3,33 @@ import { Injectable } from '@nestjs/common';
import { capitalize } from 'twenty-shared';
import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
@Injectable()
export class CreateManyQueryFactory {
create(objectMetadata, depth?: number): string {
create(
objectMetadata: {
objectMetadataMaps: ObjectMetadataMaps;
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
},
depth?: number,
): string {
const objectNamePlural = capitalize(
objectMetadata.objectMetadataItem.namePlural,
objectMetadata.objectMetadataMapItem.namePlural,
);
const objectNameSingular = capitalize(
objectMetadata.objectMetadataItem.nameSingular,
objectMetadata.objectMetadataMapItem.nameSingular,
);
return `
mutation Create${objectNamePlural}($data: [${objectNameSingular}CreateInput!]) {
create${objectNamePlural}(data: $data) {
id
${objectMetadata.objectMetadataItem.fields
${objectMetadata.objectMetadataMapItem.fields
.map((field) =>
mapFieldMetadataToGraphqlQuery(
objectMetadata.objectMetadataItems,
objectMetadata.objectMetadataMaps,
field,
depth,
),
@@ -3,22 +3,30 @@ import { Injectable } from '@nestjs/common';
import { capitalize } from 'twenty-shared';
import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
@Injectable()
export class CreateOneQueryFactory {
create(objectMetadata, depth?: number): string {
create(
objectMetadata: {
objectMetadataMaps: ObjectMetadataMaps;
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
},
depth?: number,
): string {
const objectNameSingular = capitalize(
objectMetadata.objectMetadataItem.nameSingular,
objectMetadata.objectMetadataMapItem.nameSingular,
);
return `
mutation Create${objectNameSingular}($data: ${objectNameSingular}CreateInput!) {
create${objectNameSingular}(data: $data) {
id
${objectMetadata.objectMetadataItem.fields
${objectMetadata.objectMetadataMapItem.fields
.map((field) =>
mapFieldMetadataToGraphqlQuery(
objectMetadata.objectMetadataItems,
objectMetadata.objectMetadataMaps,
field,
depth,
),
@@ -2,10 +2,12 @@ import { Injectable } from '@nestjs/common';
import { capitalize } from 'twenty-shared';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
@Injectable()
export class DeleteQueryFactory {
create(objectMetadataItem): string {
const objectNameSingular = capitalize(objectMetadataItem.nameSingular);
create(objectMetadataMapItem: ObjectMetadataItemWithFieldMaps): string {
const objectNameSingular = capitalize(objectMetadataMapItem.nameSingular);
return `
mutation Delete${objectNameSingular}($id: ID!) {
@@ -3,11 +3,20 @@ import { Injectable } from '@nestjs/common';
import { capitalize } from 'twenty-shared';
import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
@Injectable()
export class FindDuplicatesQueryFactory {
create(objectMetadata, depth?: number): string {
const objectNameSingular = objectMetadata.objectMetadataItem.nameSingular;
create(
objectMetadata: {
objectMetadataMaps: ObjectMetadataMaps;
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
},
depth?: number,
): string {
const objectNameSingular =
objectMetadata.objectMetadataMapItem.nameSingular;
return `
query FindDuplicate${capitalize(
@@ -22,10 +31,10 @@ export class FindDuplicatesQueryFactory {
}
edges{
node {
${objectMetadata.objectMetadataItem.fields
${objectMetadata.objectMetadataMapItem.fields
.map((field) =>
mapFieldMetadataToGraphqlQuery(
objectMetadata.objectMetadataItems,
objectMetadata.objectMetadataMaps,
field,
depth,
),
@@ -3,14 +3,22 @@ import { Injectable } from '@nestjs/common';
import { capitalize } from 'twenty-shared';
import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
@Injectable()
export class FindManyQueryFactory {
create(objectMetadata, depth?: number): string {
create(
objectMetadata: {
objectMetadataMaps: ObjectMetadataMaps;
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
},
depth?: number,
): string {
const objectNameSingular = capitalize(
objectMetadata.objectMetadataItem.nameSingular,
objectMetadata.objectMetadataMapItem.nameSingular,
);
const objectNamePlural = objectMetadata.objectMetadataItem.namePlural;
const objectNamePlural = objectMetadata.objectMetadataMapItem.namePlural;
return `
query FindMany${capitalize(objectNamePlural)}(
@@ -32,10 +40,10 @@ export class FindManyQueryFactory {
edges {
node {
id
${objectMetadata.objectMetadataItem.fields
${objectMetadata.objectMetadataMapItem.fields
.map((field) =>
mapFieldMetadataToGraphqlQuery(
objectMetadata.objectMetadataItems,
objectMetadata.objectMetadataMaps,
field,
depth,
),
@@ -3,11 +3,20 @@ import { Injectable } from '@nestjs/common';
import { capitalize } from 'twenty-shared';
import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
@Injectable()
export class FindOneQueryFactory {
create(objectMetadata, depth?: number): string {
const objectNameSingular = objectMetadata.objectMetadataItem.nameSingular;
create(
objectMetadata: {
objectMetadataMaps: ObjectMetadataMaps;
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
},
depth?: number,
): string {
const objectNameSingular =
objectMetadata.objectMetadataMapItem.nameSingular;
return `
query FindOne${capitalize(objectNameSingular)}(
@@ -15,10 +24,10 @@ export class FindOneQueryFactory {
) {
${objectNameSingular}(filter: $filter) {
id
${objectMetadata.objectMetadataItem.fields
${objectMetadata.objectMetadataMapItem.fields
.map((field) =>
mapFieldMetadataToGraphqlQuery(
objectMetadata.objectMetadataItems,
objectMetadata.objectMetadataMaps,
field,
depth,
),
@@ -2,12 +2,14 @@ import { Injectable } from '@nestjs/common';
import { Request } from 'express';
import { LimitInputFactory } from 'src/engine/api/rest/input-factories/limit-input.factory';
import { OrderByInputFactory } from 'src/engine/api/rest/input-factories/order-by-input.factory';
import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-input.factory';
import { QueryVariables } from 'src/engine/api/rest/core/types/query-variables.type';
import { EndingBeforeInputFactory } from 'src/engine/api/rest/input-factories/ending-before-input.factory';
import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-input.factory';
import { LimitInputFactory } from 'src/engine/api/rest/input-factories/limit-input.factory';
import { OrderByInputFactory } from 'src/engine/api/rest/input-factories/order-by-input.factory';
import { StartingAfterInputFactory } from 'src/engine/api/rest/input-factories/starting-after-input.factory';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
@Injectable()
export class GetVariablesFactory {
@@ -22,7 +24,10 @@ export class GetVariablesFactory {
create(
id: string | undefined,
request: Request,
objectMetadata,
objectMetadata: {
objectMetadataMaps: ObjectMetadataMaps;
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
},
): QueryVariables {
if (id) {
return { filter: { id: { eq: id } } };
@@ -3,11 +3,20 @@ import { Injectable } from '@nestjs/common';
import { capitalize } from 'twenty-shared';
import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
@Injectable()
export class UpdateQueryFactory {
create(objectMetadata, depth?: number): string {
const objectNameSingular = objectMetadata.objectMetadataItem.nameSingular;
create(
objectMetadata: {
objectMetadataMaps: ObjectMetadataMaps;
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
},
depth?: number,
): string {
const objectNameSingular =
objectMetadata.objectMetadataMapItem.nameSingular;
return `
mutation Update${capitalize(
@@ -15,10 +24,10 @@ export class UpdateQueryFactory {
)}($id: ID!, $data: ${capitalize(objectNameSingular)}UpdateInput!) {
update${capitalize(objectNameSingular)}(id: $id, data: $data) {
id
${objectMetadata.objectMetadataItem.fields
${Object.values(objectMetadata.objectMetadataMapItem.fieldsById)
.map((field) =>
mapFieldMetadataToGraphqlQuery(
objectMetadata.objectMetadataItems,
objectMetadata.objectMetadataMaps,
field,
depth,
),
@@ -1,31 +1,70 @@
import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import {
fieldNumberMock,
objectMetadataItemMock,
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { checkFields } from 'src/engine/api/rest/core/query-builder/utils/check-fields.utils';
import { checkArrayFields } from 'src/engine/api/rest/core/query-builder/utils/check-order-by.utils';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
describe('checkFields', () => {
const completeFieldNumberMock: FieldMetadataInterface = {
id: 'field-number-id',
type: fieldNumberMock.type,
name: fieldNumberMock.name,
label: 'Field Number',
objectMetadataId: 'object-metadata-id',
isNullable: fieldNumberMock.isNullable,
defaultValue: fieldNumberMock.defaultValue,
};
const fieldsById: FieldMetadataMap = {
'field-number-id': completeFieldNumberMock,
};
const fieldsByName: FieldMetadataMap = {
[completeFieldNumberMock.name]: completeFieldNumberMock,
};
const mockObjectMetadataWithFieldMaps = {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
};
it('should check field types', () => {
expect(() =>
checkFields(objectMetadataItemMock, ['fieldNumber']),
checkFields(mockObjectMetadataWithFieldMaps, ['fieldNumber']),
).not.toThrow();
expect(() => checkFields(objectMetadataItemMock, ['wrongField'])).toThrow();
expect(() =>
checkFields(mockObjectMetadataWithFieldMaps, ['wrongField']),
).toThrow();
expect(() =>
checkFields(objectMetadataItemMock, ['fieldNumber', 'wrongField']),
checkFields(mockObjectMetadataWithFieldMaps, [
'fieldNumber',
'wrongField',
]),
).toThrow();
});
it('should check field types from array of fields', () => {
expect(() =>
checkArrayFields(objectMetadataItemMock, [{ fieldNumber: undefined }]),
checkArrayFields(mockObjectMetadataWithFieldMaps, [
{ fieldNumber: undefined },
]),
).not.toThrow();
expect(() =>
checkArrayFields(objectMetadataItemMock, [{ wrongField: undefined }]),
checkArrayFields(mockObjectMetadataWithFieldMaps, [
{ wrongField: undefined },
]),
).toThrow();
expect(() =>
checkArrayFields(objectMetadataItemMock, [
checkArrayFields(mockObjectMetadataWithFieldMaps, [
{ fieldNumber: undefined },
{ wrongField: undefined },
]),
@@ -1,12 +1,42 @@
import { FieldMetadataType } from 'twenty-shared';
import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import {
fieldNumberMock,
objectMetadataItemMock,
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { getFieldType } from 'src/engine/api/rest/core/query-builder/utils/get-field-type.utils';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
describe('getFieldType', () => {
const completeFieldNumberMock: FieldMetadataInterface = {
id: 'field-number-id',
type: fieldNumberMock.type,
name: fieldNumberMock.name,
label: 'Field Number',
objectMetadataId: 'object-metadata-id',
isNullable: fieldNumberMock.isNullable,
defaultValue: fieldNumberMock.defaultValue,
};
const fieldsById: FieldMetadataMap = {
'field-number-id': completeFieldNumberMock,
};
const fieldsByName: FieldMetadataMap = {
[completeFieldNumberMock.name]: completeFieldNumberMock,
};
const mockObjectMetadataWithFieldMaps = {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
};
it('should get field type', () => {
expect(getFieldType(objectMetadataItemMock, 'fieldNumber')).toEqual(
FieldMetadataType.NUMBER,
);
expect(
getFieldType(mockObjectMetadataWithFieldMaps, 'fieldNumber'),
).toEqual(FieldMetadataType.NUMBER);
});
});
@@ -1,5 +1,7 @@
import { FieldMetadataType } from 'twenty-shared';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import {
fieldCurrencyMock,
fieldNumberMock,
@@ -8,19 +10,85 @@ import {
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils';
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
describe('mapFieldMetadataToGraphqlQuery', () => {
const typedFieldNumberMock: FieldMetadataInterface = {
id: 'field-number-id',
name: fieldNumberMock.name,
type: fieldNumberMock.type,
label: 'Field Number',
objectMetadataId: 'object-metadata-id',
isNullable: fieldNumberMock.isNullable,
defaultValue: fieldNumberMock.defaultValue,
};
const typedFieldTextMock: FieldMetadataInterface = {
id: 'field-text-id',
name: fieldTextMock.name,
type: fieldTextMock.type,
label: 'Field Text',
objectMetadataId: 'object-metadata-id',
isNullable: fieldTextMock.isNullable,
defaultValue: fieldTextMock.defaultValue,
};
const typedFieldCurrencyMock: FieldMetadataInterface = {
id: 'field-currency-id',
name: fieldCurrencyMock.name,
type: fieldCurrencyMock.type,
label: 'Field Currency',
objectMetadataId: 'object-metadata-id',
isNullable: fieldCurrencyMock.isNullable,
defaultValue: fieldCurrencyMock.defaultValue,
};
const fieldsById: FieldMetadataMap = {
'field-number-id': typedFieldNumberMock,
'field-text-id': typedFieldTextMock,
'field-currency-id': typedFieldCurrencyMock,
};
const fieldsByName: FieldMetadataMap = {
[typedFieldNumberMock.name]: typedFieldNumberMock,
[typedFieldTextMock.name]: typedFieldTextMock,
[typedFieldCurrencyMock.name]: typedFieldCurrencyMock,
};
const typedObjectMetadataItem: ObjectMetadataItemWithFieldMaps = {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
};
const objectMetadataMapsMock: ObjectMetadataMaps = {
byId: {
[objectMetadataItemMock.id]: typedObjectMetadataItem,
},
idByNameSingular: {
[objectMetadataItemMock.nameSingular]: objectMetadataItemMock.id,
},
};
it('should map properly', () => {
expect(
mapFieldMetadataToGraphqlQuery([objectMetadataItemMock], fieldNumberMock),
mapFieldMetadataToGraphqlQuery(
objectMetadataMapsMock,
typedFieldNumberMock,
),
).toEqual('fieldNumber');
expect(
mapFieldMetadataToGraphqlQuery([objectMetadataItemMock], fieldTextMock),
mapFieldMetadataToGraphqlQuery(
objectMetadataMapsMock,
typedFieldTextMock,
),
).toEqual('fieldText');
expect(
mapFieldMetadataToGraphqlQuery(
[objectMetadataItemMock],
fieldCurrencyMock,
objectMetadataMapsMock,
typedFieldCurrencyMock,
),
).toEqual(`
fieldCurrency
@@ -30,19 +98,24 @@ describe('mapFieldMetadataToGraphqlQuery', () => {
}
`);
});
describe('should handle all field metadata types', () => {
Object.values(FieldMetadataType).forEach((fieldMetadataType) => {
it(`with field type ${fieldMetadataType}`, () => {
const field = {
const field: FieldMetadataInterface = {
id: 'test-field-id',
type: fieldMetadataType,
name: 'toObjectMetadataName',
label: 'Test Field',
objectMetadataId: 'object-metadata-id',
fromRelationMetadata: {
relationType: RelationMetadataType.ONE_TO_MANY,
},
toObjectMetadataId: objectMetadataItemMock.id,
} as any,
};
expect(
mapFieldMetadataToGraphqlQuery([objectMetadataItemMock], field),
mapFieldMetadataToGraphqlQuery(objectMetadataMapsMock, field),
).toBeDefined();
});
});
@@ -1,16 +1,15 @@
import { BadRequestException } from '@nestjs/common';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
export const checkFields = (
objectMetadata: ObjectMetadataInterface,
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
fieldNames: string[],
): void => {
const fieldMetadataNames = objectMetadata.fields
const fieldMetadataNames = objectMetadataItem.fields
.map((field) => {
if (isCompositeFieldMetadataType(field.type)) {
const compositeType = compositeTypeDefinitions.get(field.type);
@@ -38,7 +37,7 @@ export const checkFields = (
if (!fieldMetadataNames.includes(fieldName)) {
throw new BadRequestException(
`field '${fieldName}' does not exist in '${computeObjectTargetTable(
objectMetadata,
objectMetadataItem,
)}' object`,
);
}
@@ -1,17 +1,17 @@
import { BadRequestException } from '@nestjs/common';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
export const checkArrayFields = (
objectMetadata: ObjectMetadataInterface,
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
fields: Array<Partial<ObjectRecord>>,
): void => {
const fieldMetadataNames = objectMetadata.fields
const fieldMetadataNames = objectMetadataItem.fields
.map((field) => {
if (isCompositeFieldMetadataType(field.type)) {
const compositeType = compositeTypeDefinitions.get(field.type);
@@ -39,7 +39,7 @@ export const checkArrayFields = (
if (!fieldMetadataNames.includes(fieldName)) {
throw new BadRequestException(
`field '${fieldName}' does not exist in '${computeObjectTargetTable(
objectMetadata,
objectMetadataItem,
)}' object`,
);
}
@@ -1,19 +1,47 @@
import { FieldMetadataType } from 'twenty-shared';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import {
fieldSelectMock,
objectMetadataItemMock,
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { checkFilterEnumValues } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/check-filter-enum-values';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
describe('checkFilterEnumValues', () => {
const completeFieldSelectMock: FieldMetadataInterface = {
id: 'field-select-id',
type: fieldSelectMock.type,
name: fieldSelectMock.name,
label: 'Field Select',
objectMetadataId: 'object-metadata-id',
isNullable: fieldSelectMock.isNullable,
defaultValue: fieldSelectMock.defaultValue,
options: fieldSelectMock.options,
};
const fieldsById: FieldMetadataMap = {
'field-select-id': completeFieldSelectMock,
};
const fieldsByName: FieldMetadataMap = {
[completeFieldSelectMock.name]: completeFieldSelectMock,
};
const mockObjectMetadataWithFieldMaps = {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
};
it('should check properly', () => {
expect(() =>
checkFilterEnumValues(
FieldMetadataType.SELECT,
fieldSelectMock.name,
'OPTION_1',
objectMetadataItemMock,
mockObjectMetadataWithFieldMaps,
),
).not.toThrow();
@@ -22,7 +50,7 @@ describe('checkFilterEnumValues', () => {
FieldMetadataType.SELECT,
fieldSelectMock.name,
'MISSING_OPTION',
objectMetadataItemMock,
mockObjectMetadataWithFieldMaps,
),
).toThrow(
`'filter' enum value 'MISSING_OPTION' not available in '${fieldSelectMock.name}' enum. Available enum values are ['OPTION_1', 'OPTION_2']`,
@@ -1,12 +1,55 @@
import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import {
fieldNumberMock,
fieldTextMock,
objectMetadataItemMock,
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { parseFilter } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-filter.utils';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
describe('parseFilter', () => {
const completeFieldNumberMock: FieldMetadataInterface = {
id: 'field-number-id',
type: fieldNumberMock.type,
name: fieldNumberMock.name,
label: 'Field Number',
objectMetadataId: 'object-metadata-id',
isNullable: fieldNumberMock.isNullable,
defaultValue: fieldNumberMock.defaultValue,
};
const completeFieldTextMock: FieldMetadataInterface = {
id: 'field-text-id',
type: fieldTextMock.type,
name: fieldTextMock.name,
label: 'Field Text',
objectMetadataId: 'object-metadata-id',
isNullable: fieldTextMock.isNullable,
defaultValue: fieldTextMock.defaultValue,
};
const fieldsById: FieldMetadataMap = {
'field-number-id': completeFieldNumberMock,
'field-text-id': completeFieldTextMock,
};
const fieldsByName: FieldMetadataMap = {
[completeFieldNumberMock.name]: completeFieldNumberMock,
[completeFieldTextMock.name]: completeFieldTextMock,
};
const mockObjectMetadataWithFieldMaps = {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
};
it('should parse string filter test 1', () => {
expect(
parseFilter(
'and(fieldNumber[eq]:1,fieldNumber[eq]:2)',
objectMetadataItemMock,
mockObjectMetadataWithFieldMaps,
),
).toEqual({
and: [{ fieldNumber: { eq: 1 } }, { fieldNumber: { eq: 2 } }],
@@ -17,7 +60,7 @@ describe('parseFilter', () => {
expect(
parseFilter(
'and(fieldNumber[eq]:1,or(fieldNumber[eq]:2,fieldNumber[eq]:3))',
objectMetadataItemMock,
mockObjectMetadataWithFieldMaps,
),
).toEqual({
and: [
@@ -31,7 +74,7 @@ describe('parseFilter', () => {
expect(
parseFilter(
'and(fieldNumber[eq]:1,or(fieldNumber[eq]:2,fieldNumber[eq]:3,and(fieldNumber[eq]:6,fieldNumber[eq]:7)),or(fieldNumber[eq]:4,fieldNumber[eq]:5))',
objectMetadataItemMock,
mockObjectMetadataWithFieldMaps,
),
).toEqual({
and: [
@@ -52,7 +95,7 @@ describe('parseFilter', () => {
expect(
parseFilter(
'and(fieldText[gt]:"val,ue",or(fieldNumber[is]:NOT_NULL,not(fieldText[startsWith]:"val"),and(fieldNumber[eq]:6,fieldText[ilike]:"%val%")),or(fieldNumber[eq]:4,fieldText[is]:NULL))',
objectMetadataItemMock,
mockObjectMetadataWithFieldMaps,
),
).toEqual({
and: [
@@ -78,7 +121,7 @@ describe('parseFilter', () => {
expect(
parseFilter(
'and(fieldNumber[eq]:1,not(fieldNumber[eq]:2))',
objectMetadataItemMock,
mockObjectMetadataWithFieldMaps,
),
).toEqual({
and: [
@@ -2,13 +2,13 @@ import { BadRequestException } from '@nestjs/common';
import { FieldMetadataType } from 'twenty-shared';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
export const checkFilterEnumValues = (
fieldType: FieldMetadataType | undefined,
fieldName: string,
value: string,
objectMetadataItem: ObjectMetadataInterface,
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
): void => {
if (
!fieldType ||
@@ -18,9 +18,7 @@ export const checkFilterEnumValues = (
) {
return;
}
const field = objectMetadataItem.fields.find(
(field) => field.name === fieldName,
);
const field = objectMetadataItem.fieldsByName[fieldName];
const values = /^\[.*\]$/.test(value)
? value.slice(1, -1).split(',')
@@ -1,14 +1,13 @@
import { BadRequestException } from '@nestjs/common';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
import { parseFilterContent } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-filter-content.utils';
import { parseBaseFilter } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-base-filter.utils';
import { checkFields } from 'src/engine/api/rest/core/query-builder/utils/check-fields.utils';
import { formatFieldValue } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/format-field-values.utils';
import { FieldValue } from 'src/engine/api/rest/core/types/field-value.type';
import { checkFilterEnumValues } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/check-filter-enum-values';
import { formatFieldValue } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/format-field-values.utils';
import { parseBaseFilter } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-base-filter.utils';
import { parseFilterContent } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-filter-content.utils';
import { getFieldType } from 'src/engine/api/rest/core/query-builder/utils/get-field-type.utils';
import { FieldValue } from 'src/engine/api/rest/core/types/field-value.type';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
export enum Conjunctions {
or = 'or',
@@ -18,7 +17,7 @@ export enum Conjunctions {
export const parseFilter = (
filterQuery: string,
objectMetadataItem: ObjectMetadataInterface,
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
): Record<string, FieldValue> => {
const result = {};
const match = filterQuery.match(
@@ -1,10 +1,10 @@
import { FieldMetadataType } from 'twenty-shared';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
export const getFieldType = (
objectMetadata: ObjectMetadataInterface,
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
fieldName: string,
): FieldMetadataType | undefined => {
return objectMetadata.fields.find((field) => field.name === fieldName)?.type;
return objectMetadataItem.fieldsByName[fieldName]?.type;
};
@@ -1,13 +1,16 @@
import { FieldMetadataType } from 'twenty-shared';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
const DEFAULT_DEPTH_VALUE = 1;
// TODO: Should be properly type and based on composite type definitions
export const mapFieldMetadataToGraphqlQuery = (
objectMetadataItems,
field,
objectMetadataMaps: ObjectMetadataMaps,
field: FieldMetadataInterface,
maxDepthForRelations = DEFAULT_DEPTH_VALUE,
): string | undefined => {
if (maxDepthForRelations < 0) {
@@ -41,19 +44,25 @@ export const mapFieldMetadataToGraphqlQuery = (
fieldType === FieldMetadataType.RELATION &&
field.toRelationMetadata?.relationType === RelationMetadataType.ONE_TO_MANY
) {
const relationMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id ===
(field.toRelationMetadata as any)?.fromObjectMetadataId,
);
const fromObjectMetadataId = field.toRelationMetadata?.fromObjectMetadataId;
if (!fromObjectMetadataId) {
return '';
}
const relationMetadataItem = objectMetadataMaps.byId[fromObjectMetadataId];
if (!relationMetadataItem) {
return '';
}
return `${field.name}
{
id
${(relationMetadataItem?.fields ?? [])
${Object.values(relationMetadataItem.fieldsById)
.map((field) =>
mapFieldMetadataToGraphqlQuery(
objectMetadataItems,
objectMetadataMaps,
field,
maxDepthForRelations - 1,
),
@@ -66,21 +75,27 @@ export const mapFieldMetadataToGraphqlQuery = (
field.fromRelationMetadata?.relationType ===
RelationMetadataType.ONE_TO_MANY
) {
const relationMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id ===
(field.fromRelationMetadata as any)?.toObjectMetadataId,
);
const toObjectMetadataId = field.fromRelationMetadata?.toObjectMetadataId;
if (!toObjectMetadataId) {
return '';
}
const relationMetadataItem = objectMetadataMaps.byId[toObjectMetadataId];
if (!relationMetadataItem) {
return '';
}
return `${field.name}
{
edges {
node {
id
${(relationMetadataItem?.fields ?? [])
${Object.values(relationMetadataItem.fieldsById)
.map((field) =>
mapFieldMetadataToGraphqlQuery(
objectMetadataItems,
objectMetadataMaps,
field,
maxDepthForRelations - 1,
),
@@ -106,7 +106,7 @@ export class RestApiCoreServiceV2 {
}
const objectMetadataNameSingular =
objectMetadata.objectMetadataItem.nameSingular;
objectMetadata.objectMetadataMapItem.nameSingular;
const repository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
workspace.id,
@@ -1,10 +1,79 @@
import { Test, TestingModule } from '@nestjs/testing';
import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import {
fieldCurrencyMock,
fieldNumberMock,
fieldTextMock,
objectMetadataItemMock,
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-input.factory';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
describe('FilterInputFactory', () => {
const objectMetadata = { objectMetadataItem: objectMetadataItemMock };
const completeFieldNumberMock: FieldMetadataInterface = {
id: 'field-number-id',
type: fieldNumberMock.type,
name: fieldNumberMock.name,
label: 'Field Number',
objectMetadataId: 'object-metadata-id',
isNullable: fieldNumberMock.isNullable,
defaultValue: fieldNumberMock.defaultValue,
};
const completeFieldTextMock: FieldMetadataInterface = {
id: 'field-text-id',
type: fieldTextMock.type,
name: fieldTextMock.name,
label: 'Field Text',
objectMetadataId: 'object-metadata-id',
isNullable: fieldTextMock.isNullable,
defaultValue: fieldTextMock.defaultValue,
};
const completeFieldCurrencyMock: FieldMetadataInterface = {
id: 'field-currency-id',
type: fieldCurrencyMock.type,
name: fieldCurrencyMock.name,
label: 'Field Currency',
objectMetadataId: 'object-metadata-id',
isNullable: fieldCurrencyMock.isNullable,
defaultValue: fieldCurrencyMock.defaultValue,
};
const fieldsById: FieldMetadataMap = {
'field-number-id': completeFieldNumberMock,
'field-text-id': completeFieldTextMock,
'field-currency-id': completeFieldCurrencyMock,
};
const fieldsByName: FieldMetadataMap = {
[completeFieldNumberMock.name]: completeFieldNumberMock,
[completeFieldTextMock.name]: completeFieldTextMock,
[completeFieldCurrencyMock.name]: completeFieldCurrencyMock,
};
const objectMetadataMapItem = {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
};
const objectMetadataMaps = {
byId: {
[objectMetadataItemMock.id || 'mock-id']: objectMetadataMapItem,
},
idByNameSingular: {
[objectMetadataItemMock.nameSingular]:
objectMetadataItemMock.id || 'mock-id',
},
};
const objectMetadata = {
objectMetadataMaps,
objectMetadataMapItem,
};
let service: FilterInputFactory;
@@ -6,10 +6,18 @@ import { addDefaultConjunctionIfMissing } from 'src/engine/api/rest/core/query-b
import { checkFilterQuery } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/check-filter-query.utils';
import { parseFilter } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-filter.utils';
import { FieldValue } from 'src/engine/api/rest/core/types/field-value.type';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
@Injectable()
export class FilterInputFactory {
create(request: Request, objectMetadata): Record<string, FieldValue> {
create(
request: Request,
objectMetadata: {
objectMetadataMaps: ObjectMetadataMaps;
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
},
): Record<string, FieldValue> {
let filterQuery = request.query.filter;
if (typeof filterQuery !== 'string') {
@@ -20,6 +28,6 @@ export class FilterInputFactory {
filterQuery = addDefaultConjunctionIfMissing(filterQuery);
return parseFilter(filterQuery, objectMetadata.objectMetadataItem);
return parseFilter(filterQuery, objectMetadata.objectMetadataMapItem);
}
}
@@ -0,0 +1,13 @@
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
export const getObjectMetadataMapItemByNamePlural = (
objectMetadataMaps: ObjectMetadataMaps,
namePlural: string,
): ObjectMetadataItemWithFieldMaps | undefined => {
const objectMetadataItems = Object.values(objectMetadataMaps.byId);
return objectMetadataItems.find(
(objectMetadata) => objectMetadata.namePlural === namePlural,
);
};
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-shared",
"version": "0.42.0-canary",
"version": "0.42.18",
"license": "AGPL-3.0",
"main": "./dist/index.js",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-ui",
"version": "0.42.0-canary",
"version": "0.42.18",
"type": "module",
"main": "./src/index.ts",
"exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-website",
"version": "0.42.0-canary",
"version": "0.42.18",
"private": true,
"scripts": {
"nx": "NX_DEFAULT_PROJECT=twenty-website node ../../node_modules/nx/bin/nx.js",