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:
@ -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',
|
||||
|
||||
@ -26,7 +26,7 @@ export class FeatureFlagEntity {
|
||||
|
||||
@Field(() => FeatureFlagKey)
|
||||
@Column({ nullable: false, type: 'text' })
|
||||
key: `${FeatureFlagKey}`;
|
||||
key: FeatureFlagKey;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -35,7 +35,6 @@ registerEnumType(WorkspaceActivationStatus, {
|
||||
|
||||
@Entity({ name: 'workspace', schema: 'core' })
|
||||
@ObjectType('Workspace')
|
||||
@UnPagedRelation('featureFlags', () => FeatureFlagEntity, { nullable: true })
|
||||
@UnPagedRelation('billingSubscriptions', () => BillingSubscription, {
|
||||
nullable: true,
|
||||
})
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -73,7 +73,7 @@ export class WorkspaceSyncMetadataService {
|
||||
|
||||
// Retrieve feature flags
|
||||
const workspaceFeatureFlagsMap =
|
||||
await this.featureFlagService.getWorkspaceFeatureFlags(
|
||||
await this.featureFlagService.getWorkspaceFeatureFlagsMap(
|
||||
context.workspaceId,
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user