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 ae9a2baf9..17a7a5f29 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 5fc9422e8..218dc047f 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 cd2e4ca4d..7acd8612e 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 1986e8f6b..d2fc348bc 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 d79d6188f..0c191b954 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 16f01b999..429acfe3e 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, );