Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8bd91903f | |||
| c7d4e6c76f | |||
| 9ad35b39ed | |||
| db9a906130 | |||
| 218cb798e6 | |||
| a364692925 | |||
| 1991f0d3c0 | |||
| ce428985aa |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-e2e-testing",
|
||||
"version": "0.42.0-canary",
|
||||
"version": "0.42.18",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-emails",
|
||||
"version": "0.42.0-canary",
|
||||
"version": "0.42.18",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="en" translate="no">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-front",
|
||||
"version": "0.42.0-canary",
|
||||
"version": "0.42.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
+5
-3
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-server",
|
||||
"version": "0.42.0-canary",
|
||||
"version": "0.42.18",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
+37
-20
@@ -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
-2
@@ -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],
|
||||
})
|
||||
|
||||
+13
-5
@@ -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,
|
||||
),
|
||||
|
||||
+12
-4
@@ -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,
|
||||
),
|
||||
|
||||
+4
-2
@@ -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!) {
|
||||
|
||||
+13
-4
@@ -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,
|
||||
),
|
||||
|
||||
+13
-5
@@ -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,
|
||||
),
|
||||
|
||||
+13
-4
@@ -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,
|
||||
),
|
||||
|
||||
+9
-4
@@ -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 } } };
|
||||
|
||||
+13
-4
@@ -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,
|
||||
),
|
||||
|
||||
+46
-7
@@ -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 },
|
||||
]),
|
||||
|
||||
+34
-4
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
+80
-7
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
+4
-5
@@ -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`,
|
||||
);
|
||||
}
|
||||
|
||||
+4
-4
@@ -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`,
|
||||
);
|
||||
}
|
||||
|
||||
+30
-2
@@ -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']`,
|
||||
|
||||
+49
-6
@@ -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: [
|
||||
|
||||
+3
-5
@@ -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(',')
|
||||
|
||||
+6
-7
@@ -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(
|
||||
|
||||
+3
-3
@@ -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;
|
||||
};
|
||||
|
||||
+31
-16
@@ -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,
|
||||
|
||||
+71
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -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,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,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-ui",
|
||||
"version": "0.42.0-canary",
|
||||
"version": "0.42.18",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user