From f9a90a656193592c1ed898939dafe126dee74a20 Mon Sep 17 00:00:00 2001 From: Weiko Date: Mon, 13 Jan 2025 18:30:16 +0100 Subject: [PATCH] Fix featureFlag not matching gql type (#9585) ## Context Screenshot 2025-01-13 at 17 18 24 Feature flags are stored in DB and then cast as FeatureFlag gql type from its corresponding enum. This means if a value from the DB does not match that enum type, the gql server will reject the call when returning the object in the resolver. (see screenshot above) To solve that, we want to do 2 things: - The ORM should still return the feature flag even if it's not valid, this is actually in the DB so we don't want to "hide" that, however we now have a warning message. - The service is not changed for the same reason, the limitation comes from gql behaviour so this is not the goal of the service nor the ORM to act on it (except the warning message) - The resolver should be updated, here we want to filter-out non-valid feature flags so it does not break the API. Because featureFlags used to be auto-generated by nestjsquery and we want to change its behavior, I had to manually create a resolveField for featureFlags and remove the auto-generated one. That means we lose some features such as filter/sort coming from nestjs-query pagination (which is something we will want to implement once we will remove nestjs-query but that's a whole other subject) --- .../twenty-front/src/generated/graphql.tsx | 22 ------------------- .../feature-flag/feature-flag.entity.ts | 2 +- .../services/feature-flag.service.ts | 11 +++++++--- .../workspace/workspace.entity.ts | 1 - .../workspace/workspace.resolver.ts | 19 ++++++++++++++++ .../workspace-sync-metadata.service.ts | 2 +- 6 files changed, 29 insertions(+), 28 deletions(-) diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index ae9a2baf9cb..17a7a5f2969 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -313,12 +313,6 @@ export type FeatureFlag = { workspaceId: Scalars['String']; }; -export type FeatureFlagFilter = { - and?: InputMaybe>; - id?: InputMaybe; - or?: InputMaybe>; -}; - export enum FeatureFlagKey { IsAdvancedFiltersEnabled = 'IsAdvancedFiltersEnabled', IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled', @@ -338,16 +332,6 @@ export enum FeatureFlagKey { IsWorkflowEnabled = 'IsWorkflowEnabled' } -export type FeatureFlagSort = { - direction: SortDirection; - field: FeatureFlagSortFields; - nulls?: InputMaybe; -}; - -export enum FeatureFlagSortFields { - Id = 'id' -} - export type FieldConnection = { __typename?: 'FieldConnection'; /** Array of edges. */ @@ -1544,12 +1528,6 @@ export type WorkspaceBillingSubscriptionsArgs = { sorting?: Array; }; - -export type WorkspaceFeatureFlagsArgs = { - filter?: FeatureFlagFilter; - sorting?: Array; -}; - export enum WorkspaceActivationStatus { Active = 'ACTIVE', Inactive = 'INACTIVE', diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts index 5fc9422e8f0..218dc047f5a 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts @@ -26,7 +26,7 @@ export class FeatureFlagEntity { @Field(() => FeatureFlagKey) @Column({ nullable: false, type: 'text' }) - key: `${FeatureFlagKey}`; + key: FeatureFlagKey; @Field() @Column({ nullable: false, type: 'uuid' }) diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/services/feature-flag.service.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/services/feature-flag.service.ts index cd2e4ca4d25..7acd8612e6f 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/services/feature-flag.service.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/services/feature-flag.service.ts @@ -30,10 +30,15 @@ export class FeatureFlagService { public async getWorkspaceFeatureFlags( workspaceId: string, + ): Promise { + return this.featureFlagRepository.find({ where: { workspaceId } }); + } + + public async getWorkspaceFeatureFlagsMap( + workspaceId: string, ): Promise { - const workspaceFeatureFlags = await this.featureFlagRepository.find({ - where: { workspaceId }, - }); + const workspaceFeatureFlags = + await this.getWorkspaceFeatureFlags(workspaceId); const workspaceFeatureFlagsMap = workspaceFeatureFlags.reduce( (result, currentFeatureFlag) => { diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts index 1986e8f6beb..d2fc348bc42 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts @@ -35,7 +35,6 @@ registerEnumType(WorkspaceActivationStatus, { @Entity({ name: 'workspace', schema: 'core' }) @ObjectType('Workspace') -@UnPagedRelation('featureFlags', () => FeatureFlagEntity, { nullable: true }) @UnPagedRelation('billingSubscriptions', () => BillingSubscription, { nullable: true, }) diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts index d79d6188f3a..0c191b954ba 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts @@ -16,6 +16,9 @@ import { BillingSubscription } from 'src/engine/core-modules/billing/entities/bi import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service'; import { FileService } from 'src/engine/core-modules/file/services/file.service'; import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; @@ -59,6 +62,7 @@ export class WorkspaceResolver { private readonly fileUploadService: FileUploadService, private readonly fileService: FileService, private readonly billingSubscriptionService: BillingSubscriptionService, + private readonly featureFlagService: FeatureFlagService, ) {} @Query(() => Workspace) @@ -134,6 +138,21 @@ export class WorkspaceResolver { return `${paths[0]}?token=${workspaceLogoToken}`; } + @ResolveField(() => [FeatureFlagEntity], { nullable: true }) + async featureFlags( + @Parent() workspace: Workspace, + ): Promise { + const featureFlags = await this.featureFlagService.getWorkspaceFeatureFlags( + workspace.id, + ); + + const filteredFeatureFlags = featureFlags.filter((flag) => + Object.values(FeatureFlagKey).includes(flag.key), + ); + + return filteredFeatureFlags; + } + @Mutation(() => Workspace) @UseGuards(DemoEnvGuard, WorkspaceAuthGuard) async deleteCurrentWorkspace(@AuthWorkspace() { id }: Workspace) { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service.ts index 16f01b999ad..429acfe3e42 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service.ts @@ -73,7 +73,7 @@ export class WorkspaceSyncMetadataService { // Retrieve feature flags const workspaceFeatureFlagsMap = - await this.featureFlagService.getWorkspaceFeatureFlags( + await this.featureFlagService.getWorkspaceFeatureFlagsMap( context.workspaceId, );