fix IndexFieldMetadata availability in IndexMetadata/ObjectMetadata in front (#12886)
Context : - IndexFieldMetadata was no longer available on 'objects' gql query ([since this PR](https://github.com/twentyhq/twenty/pull/12785)). Then, unicity checks on import do not work anymore. Fix : - Add a dataloader logic in indexFieldMetadata - Add extra check in unicity hook on import
This commit is contained in:
@ -1,31 +0,0 @@
|
||||
import { lowercaseDomainAndRemoveTrailingSlash } from 'src/engine/api/graphql/workspace-query-runner/utils/query-runner-links.util';
|
||||
|
||||
describe('queryRunner LINKS util', () => {
|
||||
it('should leave lowcased domain unchanged', () => {
|
||||
const primaryLinkUrl = 'https://www.example.com/test';
|
||||
const result = lowercaseDomainAndRemoveTrailingSlash(primaryLinkUrl);
|
||||
|
||||
expect(result).toBe('https://www.example.com/test');
|
||||
});
|
||||
|
||||
it('should lowercase the domain of the primary link url', () => {
|
||||
const primaryLinkUrl = 'htTps://wwW.exAmple.coM/TEST';
|
||||
const result = lowercaseDomainAndRemoveTrailingSlash(primaryLinkUrl);
|
||||
|
||||
expect(result).toBe('https://www.example.com/TEST');
|
||||
});
|
||||
|
||||
it('should not add a trailing slash', () => {
|
||||
const primaryLinkUrl = 'https://www.example.com';
|
||||
const result = lowercaseDomainAndRemoveTrailingSlash(primaryLinkUrl);
|
||||
|
||||
expect(result).toBe('https://www.example.com');
|
||||
});
|
||||
|
||||
it('should not add a trailing slash', () => {
|
||||
const primaryLinkUrl = 'https://www.example.com/toto/';
|
||||
const result = lowercaseDomainAndRemoveTrailingSlash(primaryLinkUrl);
|
||||
|
||||
expect(result).toBe('https://www.example.com/toto');
|
||||
});
|
||||
});
|
||||
@ -1,7 +0,0 @@
|
||||
export const lowercaseDomainAndRemoveTrailingSlash = (url: string) => {
|
||||
try {
|
||||
return new URL(url).toString().replace(/\/$/, '');
|
||||
} catch {
|
||||
return url;
|
||||
}
|
||||
};
|
||||
@ -1,7 +1,9 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
isDefined,
|
||||
lowercaseUrlAndRemoveTrailingSlash,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { lowercaseDomainAndRemoveTrailingSlash } from 'src/engine/api/graphql/workspace-query-runner/utils/query-runner-links.util';
|
||||
import { removeEmptyLinks } from 'src/engine/core-modules/record-transformer/utils/remove-empty-links';
|
||||
import { LinkMetadataNullable } from 'src/engine/metadata-modules/field-metadata/composite-types/links.composite-type';
|
||||
|
||||
@ -46,14 +48,14 @@ export const transformLinksValue = (
|
||||
return {
|
||||
...value,
|
||||
primaryLinkUrl: isDefined(primaryLinkUrl)
|
||||
? lowercaseDomainAndRemoveTrailingSlash(primaryLinkUrl)
|
||||
? lowercaseUrlAndRemoveTrailingSlash(primaryLinkUrl)
|
||||
: primaryLinkUrl,
|
||||
primaryLinkLabel,
|
||||
secondaryLinks: JSON.stringify(
|
||||
secondaryLinks?.map((link) => ({
|
||||
...link,
|
||||
url: isDefined(link.url)
|
||||
? lowercaseDomainAndRemoveTrailingSlash(link.url)
|
||||
? lowercaseUrlAndRemoveTrailingSlash(link.url)
|
||||
: link.url,
|
||||
})),
|
||||
),
|
||||
|
||||
@ -2,11 +2,13 @@ import DataLoader from 'dataloader';
|
||||
|
||||
import {
|
||||
FieldMetadataLoaderPayload,
|
||||
IndexFieldMetadataLoaderPayload,
|
||||
IndexMetadataLoaderPayload,
|
||||
RelationLoaderPayload,
|
||||
} from 'src/engine/dataloaders/dataloader.service';
|
||||
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { IndexFieldMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-field-metadata.dto';
|
||||
import { IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
@ -30,4 +32,9 @@ export interface IDataloaders {
|
||||
IndexMetadataLoaderPayload,
|
||||
IndexMetadataDTO[]
|
||||
>;
|
||||
|
||||
indexFieldMetadataLoader: DataLoader<
|
||||
IndexFieldMetadataLoaderPayload,
|
||||
IndexFieldMetadataDTO[]
|
||||
>;
|
||||
}
|
||||
|
||||
@ -2,15 +2,18 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import DataLoader from 'dataloader';
|
||||
import { APP_LOCALES } from 'twenty-shared/translations';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
import { IndexMetadataInterface } from 'src/engine/metadata-modules/index-metadata/interfaces/index-metadata.interface';
|
||||
|
||||
import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
||||
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
|
||||
import { FieldMetadataRelationService } from 'src/engine/metadata-modules/field-metadata/relation/field-metadata-relation.service';
|
||||
import { IndexFieldMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-field-metadata.dto';
|
||||
import { IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||
@ -46,6 +49,12 @@ export type IndexMetadataLoaderPayload = {
|
||||
objectMetadata: Pick<ObjectMetadataInterface, 'id'>;
|
||||
};
|
||||
|
||||
export type IndexFieldMetadataLoaderPayload = {
|
||||
workspaceId: string;
|
||||
objectMetadata: Pick<ObjectMetadataInterface, 'id'>;
|
||||
indexMetadata: Pick<IndexMetadataInterface, 'id'>;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class DataloaderService {
|
||||
constructor(
|
||||
@ -58,11 +67,13 @@ export class DataloaderService {
|
||||
const relationLoader = this.createRelationLoader();
|
||||
const fieldMetadataLoader = this.createFieldMetadataLoader();
|
||||
const indexMetadataLoader = this.createIndexMetadataLoader();
|
||||
const indexFieldMetadataLoader = this.createIndexFieldMetadataLoader();
|
||||
|
||||
return {
|
||||
relationLoader,
|
||||
fieldMetadataLoader,
|
||||
indexMetadataLoader,
|
||||
indexFieldMetadataLoader,
|
||||
};
|
||||
}
|
||||
|
||||
@ -179,4 +190,49 @@ export class DataloaderService {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private createIndexFieldMetadataLoader() {
|
||||
return new DataLoader<
|
||||
IndexFieldMetadataLoaderPayload,
|
||||
IndexFieldMetadataDTO[]
|
||||
>(async (dataLoaderParams: IndexFieldMetadataLoaderPayload[]) => {
|
||||
const workspaceId = dataLoaderParams[0].workspaceId;
|
||||
|
||||
const { objectMetadataMaps } =
|
||||
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
return dataLoaderParams.map(
|
||||
({
|
||||
objectMetadata: { id: objectMetadataId },
|
||||
indexMetadata: { id: indexMetadataId },
|
||||
}) => {
|
||||
const indexMetadataEntity = objectMetadataMaps.byId[
|
||||
objectMetadataId
|
||||
].indexMetadatas.find(
|
||||
(indexMetadata) => indexMetadata.id === indexMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(indexMetadataEntity)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return indexMetadataEntity.indexFieldMetadatas.map(
|
||||
(indexFieldMetadata) => {
|
||||
return {
|
||||
id: indexFieldMetadata.id,
|
||||
fieldMetadataId: indexFieldMetadata.fieldMetadataId,
|
||||
order: indexFieldMetadata.order,
|
||||
createdAt: new Date(indexFieldMetadata.createdAt),
|
||||
updatedAt: new Date(indexFieldMetadata.updatedAt),
|
||||
indexMetadataId,
|
||||
workspaceId,
|
||||
};
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsDateString,
|
||||
IsDate,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
@ -81,11 +81,11 @@ export class IndexMetadataDTO {
|
||||
|
||||
objectMetadataId: string;
|
||||
|
||||
@IsDateString()
|
||||
@IsDate()
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
|
||||
@IsDateString()
|
||||
@IsDate()
|
||||
@Field()
|
||||
updatedAt: Date;
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto';
|
||||
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
|
||||
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
import { IndexMetadataResolver } from 'src/engine/metadata-modules/index-metadata/index-metadata.resolver';
|
||||
import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service';
|
||||
import { ObjectMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/object-metadata/interceptors/object-metadata-graphql-api-exception.interceptor';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
||||
@ -46,7 +47,7 @@ import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-
|
||||
],
|
||||
}),
|
||||
],
|
||||
providers: [IndexMetadataService],
|
||||
providers: [IndexMetadataService, IndexMetadataResolver],
|
||||
exports: [IndexMetadataService],
|
||||
})
|
||||
export class IndexMetadataModule {}
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
|
||||
import { Context, Parent, ResolveField, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { IndexFieldMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-field-metadata.dto';
|
||||
import { IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto';
|
||||
import { objectMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@Resolver(() => IndexMetadataDTO)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@UseFilters(
|
||||
PreventNestToAutoLogGraphqlErrorsFilter,
|
||||
PermissionsGraphqlApiExceptionFilter,
|
||||
)
|
||||
export class IndexMetadataResolver {
|
||||
@ResolveField(() => [IndexFieldMetadataDTO], { nullable: false })
|
||||
async indexFieldMetadataList(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Parent() indexMetadata: IndexMetadataDTO,
|
||||
@Context() context: { loaders: IDataloaders },
|
||||
): Promise<IndexFieldMetadataDTO[]> {
|
||||
try {
|
||||
const indexFieldMetadataItems =
|
||||
await context.loaders.indexFieldMetadataLoader.load({
|
||||
objectMetadata: { id: indexMetadata.objectMetadataId },
|
||||
indexMetadata,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
return indexFieldMetadataItems;
|
||||
} catch (error) {
|
||||
objectMetadataGraphqlApiExceptionHandler(error);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,10 +5,10 @@ import axios, { AxiosInstance } from 'axios';
|
||||
import uniqBy from 'lodash.uniqby';
|
||||
import { TWENTY_COMPANIES_BASE_URL } from 'twenty-shared/constants';
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { lowercaseUrlAndRemoveTrailingSlash } from 'twenty-shared/utils';
|
||||
import { DeepPartial, ILike, Repository } from 'typeorm';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { lowercaseDomainAndRemoveTrailingSlash } from 'src/engine/api/graphql/workspace-query-runner/utils/query-runner-links.util';
|
||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
@ -79,7 +79,7 @@ export class CreateCompanyService {
|
||||
const companiesWithoutTrailingSlash = companies.map((company) => ({
|
||||
...company,
|
||||
domainName: company.domainName
|
||||
? lowercaseDomainAndRemoveTrailingSlash(company.domainName)
|
||||
? lowercaseUrlAndRemoveTrailingSlash(company.domainName)
|
||||
: undefined,
|
||||
}));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user