Fix featureFlag not matching gql type (#9585)

## Context

<img width="1349" alt="Screenshot 2025-01-13 at 17 18 24"
src="https://github.com/user-attachments/assets/4f5da0e9-0245-41c6-bde2-4d52e0ba34ed"
/>

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)
This commit is contained in:
Weiko
2025-01-13 18:30:16 +01:00
committed by GitHub
parent 55bae60f69
commit f9a90a6561
6 changed files with 29 additions and 28 deletions

View File

@ -313,12 +313,6 @@ export type FeatureFlag = {
workspaceId: Scalars['String'];
};
export type FeatureFlagFilter = {
and?: InputMaybe<Array<FeatureFlagFilter>>;
id?: InputMaybe<UuidFilterComparison>;
or?: InputMaybe<Array<FeatureFlagFilter>>;
};
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<SortNulls>;
};
export enum FeatureFlagSortFields {
Id = 'id'
}
export type FieldConnection = {
__typename?: 'FieldConnection';
/** Array of edges. */
@ -1544,12 +1528,6 @@ export type WorkspaceBillingSubscriptionsArgs = {
sorting?: Array<BillingSubscriptionSort>;
};
export type WorkspaceFeatureFlagsArgs = {
filter?: FeatureFlagFilter;
sorting?: Array<FeatureFlagSort>;
};
export enum WorkspaceActivationStatus {
Active = 'ACTIVE',
Inactive = 'INACTIVE',

View File

@ -26,7 +26,7 @@ export class FeatureFlagEntity {
@Field(() => FeatureFlagKey)
@Column({ nullable: false, type: 'text' })
key: `${FeatureFlagKey}`;
key: FeatureFlagKey;
@Field()
@Column({ nullable: false, type: 'uuid' })

View File

@ -30,10 +30,15 @@ export class FeatureFlagService {
public async getWorkspaceFeatureFlags(
workspaceId: string,
): Promise<FeatureFlagEntity[]> {
return this.featureFlagRepository.find({ where: { workspaceId } });
}
public async getWorkspaceFeatureFlagsMap(
workspaceId: string,
): Promise<FeatureFlagMap> {
const workspaceFeatureFlags = await this.featureFlagRepository.find({
where: { workspaceId },
});
const workspaceFeatureFlags =
await this.getWorkspaceFeatureFlags(workspaceId);
const workspaceFeatureFlagsMap = workspaceFeatureFlags.reduce(
(result, currentFeatureFlag) => {

View File

@ -35,7 +35,6 @@ registerEnumType(WorkspaceActivationStatus, {
@Entity({ name: 'workspace', schema: 'core' })
@ObjectType('Workspace')
@UnPagedRelation('featureFlags', () => FeatureFlagEntity, { nullable: true })
@UnPagedRelation('billingSubscriptions', () => BillingSubscription, {
nullable: true,
})

View File

@ -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<FeatureFlagEntity[]> {
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) {

View File

@ -73,7 +73,7 @@ export class WorkspaceSyncMetadataService {
// Retrieve feature flags
const workspaceFeatureFlagsMap =
await this.featureFlagService.getWorkspaceFeatureFlags(
await this.featureFlagService.getWorkspaceFeatureFlagsMap(
context.workspaceId,
);