refactor(admin-panel): standardize FeatureFlagKey usage (#9548)

Replaced string-based feature flag keys with the typed FeatureFlagKey
enum across the admin panel module and related front-end hooks. This
ensures stronger type safety, reduces potential errors, and improves
consistency in handling feature flags.
This commit is contained in:
Antoine Moreaux
2025-01-10 18:38:50 +01:00
committed by GitHub
parent ed51bff2f4
commit b40f58a512
5 changed files with 20 additions and 13 deletions

View File

@ -4,6 +4,7 @@ import { isDefined } from 'twenty-ui';
import { import {
useUpdateWorkspaceFeatureFlagMutation, useUpdateWorkspaceFeatureFlagMutation,
useUserLookupAdminPanelMutation, useUserLookupAdminPanelMutation,
FeatureFlagKey,
} from '~/generated/graphql'; } from '~/generated/graphql';
export const useFeatureFlagsManagement = () => { export const useFeatureFlagsManagement = () => {
@ -42,7 +43,7 @@ export const useFeatureFlagsManagement = () => {
const handleFeatureFlagUpdate = async ( const handleFeatureFlagUpdate = async (
workspaceId: string, workspaceId: string,
featureFlag: string, featureFlag: FeatureFlagKey,
value: boolean, value: boolean,
) => { ) => {
setError(null); setError(null);

View File

@ -1,4 +1,6 @@
import { FeatureFlagKey } from '~/generated/graphql';
export type FeatureFlag = { export type FeatureFlag = {
key: string; key: FeatureFlagKey;
value: boolean; value: boolean;
}; };

View File

@ -8,7 +8,6 @@ import { UpdateWorkspaceFeatureFlagInput } from 'src/engine/core-modules/admin-p
import { UserLookup } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.entity'; import { UserLookup } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.entity';
import { UserLookupInput } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.input'; import { UserLookupInput } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.input';
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter'; import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { ImpersonateGuard } from 'src/engine/guards/impersonate-guard'; import { ImpersonateGuard } from 'src/engine/guards/impersonate-guard';
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
@ -41,7 +40,7 @@ export class AdminPanelResolver {
): Promise<boolean> { ): Promise<boolean> {
await this.adminService.updateWorkspaceFeatureFlags( await this.adminService.updateWorkspaceFeatureFlags(
updateFlagInput.workspaceId, updateFlagInput.workspaceId,
FeatureFlagKey[updateFlagInput.featureFlag], updateFlagInput.featureFlag,
updateFlagInput.value, updateFlagInput.value,
); );

View File

@ -118,7 +118,7 @@ export class AdminPanelService {
async updateWorkspaceFeatureFlags( async updateWorkspaceFeatureFlags(
workspaceId: string, workspaceId: string,
featureFlag: string, featureFlag: FeatureFlagKey,
value: boolean, value: boolean,
) { ) {
featureFlagValidator.assertIsFeatureFlagKey( featureFlagValidator.assertIsFeatureFlagKey(
@ -140,14 +140,14 @@ export class AdminPanelService {
); );
const existingFlag = workspace.featureFlags?.find( const existingFlag = workspace.featureFlags?.find(
(flag) => flag.key === featureFlag, (flag) => flag.key === FeatureFlagKey[featureFlag],
); );
if (existingFlag) { if (existingFlag) {
await this.featureFlagRepository.update(existingFlag.id, { value }); await this.featureFlagRepository.update(existingFlag.id, { value });
} else { } else {
await this.featureFlagRepository.save({ await this.featureFlagRepository.save({
key: featureFlag, key: FeatureFlagKey[featureFlag],
value, value,
workspaceId: workspace.id, workspaceId: workspace.id,
}); });

View File

@ -12,6 +12,7 @@ import {
AuthException, AuthException,
AuthExceptionCode, AuthExceptionCode,
} from 'src/engine/core-modules/auth/auth.exception'; } from 'src/engine/core-modules/auth/auth.exception';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
const UserFindOneMock = jest.fn(); const UserFindOneMock = jest.fn();
const WorkspaceFindOneMock = jest.fn(); const WorkspaceFindOneMock = jest.fn();
@ -74,9 +75,13 @@ describe('AdminPanelService', () => {
it('should update an existing feature flag if it exists', async () => { it('should update an existing feature flag if it exists', async () => {
const workspaceId = 'workspace-id'; const workspaceId = 'workspace-id';
const featureFlag = 'IsFlagEnabled'; const featureFlag = 'IsFlagEnabled' as FeatureFlagKey;
const value = true; const value = true;
const existingFlag = { id: 'flag-id', key: featureFlag, value: false }; const existingFlag = {
id: 'flag-id',
key: 'IS_FLAG_ENABLED',
value: false,
};
WorkspaceFindOneMock.mockReturnValueOnce({ WorkspaceFindOneMock.mockReturnValueOnce({
id: workspaceId, id: workspaceId,
@ -93,7 +98,7 @@ describe('AdminPanelService', () => {
it('should create a new feature flag if it does not exist', async () => { it('should create a new feature flag if it does not exist', async () => {
const workspaceId = 'workspace-id'; const workspaceId = 'workspace-id';
const featureFlag = 'IsFlagEnabled'; const featureFlag = 'IsFlagEnabled' as FeatureFlagKey;
const value = true; const value = true;
WorkspaceFindOneMock.mockReturnValueOnce({ WorkspaceFindOneMock.mockReturnValueOnce({
@ -104,7 +109,7 @@ describe('AdminPanelService', () => {
await service.updateWorkspaceFeatureFlags(workspaceId, featureFlag, value); await service.updateWorkspaceFeatureFlags(workspaceId, featureFlag, value);
expect(FeatureFlagSaveMock).toHaveBeenCalledWith({ expect(FeatureFlagSaveMock).toHaveBeenCalledWith({
key: featureFlag, key: 'IS_FLAG_ENABLED',
value, value,
workspaceId, workspaceId,
}); });
@ -113,7 +118,7 @@ describe('AdminPanelService', () => {
it('should throw an exception if the workspace is not found', async () => { it('should throw an exception if the workspace is not found', async () => {
const workspaceId = 'non-existent-workspace'; const workspaceId = 'non-existent-workspace';
const featureFlag = 'IsFlagEnabled'; const featureFlag = 'IsFlagEnabled' as FeatureFlagKey;
const value = true; const value = true;
WorkspaceFindOneMock.mockReturnValueOnce(null); WorkspaceFindOneMock.mockReturnValueOnce(null);
@ -127,7 +132,7 @@ describe('AdminPanelService', () => {
it('should throw an exception if the flag is not found', async () => { it('should throw an exception if the flag is not found', async () => {
const workspaceId = 'non-existent-workspace'; const workspaceId = 'non-existent-workspace';
const featureFlag = 'IsUnknownFlagEnabled'; const featureFlag = 'IsUnknownFlagEnabled' as FeatureFlagKey;
const value = true; const value = true;
WorkspaceFindOneMock.mockReturnValueOnce(null); WorkspaceFindOneMock.mockReturnValueOnce(null);